Merge "TouchpadInputMapper: rotate multi-finger swipes with display"
diff --git a/include/input/MotionPredictor.h b/include/input/MotionPredictor.h
index 6c07849..045e61b 100644
--- a/include/input/MotionPredictor.h
+++ b/include/input/MotionPredictor.h
@@ -63,14 +63,10 @@
     MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
                     std::function<bool()> checkEnableMotionPrediction = isMotionPredictionEnabled);
     void record(const MotionEvent& event);
-    std::vector<std::unique_ptr<MotionEvent>> predict();
+    std::vector<std::unique_ptr<MotionEvent>> predict(nsecs_t timestamp);
     bool isPredictionAvailable(int32_t deviceId, int32_t source);
-    void setExpectedPresentationTimeNanos(int64_t expectedPresentationTimeNanos);
 
 private:
-    std::mutex mLock;
-    int64_t mExpectedPresentationTimeNanos GUARDED_BY(mLock) = 0;
-    int64_t getExpectedPresentationTimeNanos();
     std::vector<MotionEvent> mEvents;
     const nsecs_t mPredictionTimestampOffsetNanos;
     const std::function<bool()> mCheckMotionPredictionEnabled;
diff --git a/libs/binder/BinderRecordReplay.cpp b/libs/binder/BinderRecordReplay.cpp
index 90c02a8..9463785 100644
--- a/libs/binder/BinderRecordReplay.cpp
+++ b/libs/binder/BinderRecordReplay.cpp
@@ -16,10 +16,12 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/unique_fd.h>
 #include <binder/BinderRecordReplay.h>
 #include <algorithm>
 
 using android::Parcel;
+using android::base::borrowed_fd;
 using android::base::unique_fd;
 using android::binder::debug::RecordedTransaction;
 
@@ -30,22 +32,78 @@
 static_assert(PADDING8(7) == 1);
 static_assert(PADDING8(8) == 0);
 
-// Transactions are sequentially recorded to the file descriptor in the following format:
+// Transactions are sequentially recorded to a file descriptor.
 //
-// RecordedTransaction.TransactionHeader  (32 bytes)
-// Sent Parcel data                       (getDataSize() bytes)
-// padding                                (enough bytes to align the reply Parcel data to 8 bytes)
-// Reply Parcel data                      (getReplySize() bytes)
-// padding                                (enough bytes to align the next header to 8 bytes)
-// [repeats with next transaction]
+// An individual RecordedTransaction is written with the following format:
 //
-// Warning: This format is non-stable
+// WARNING: Though the following format is designed to be stable and
+// extensible, it is under active development and should be considered
+// unstable until this warning is removed.
+//
+// A RecordedTransaction is written to a file as a sequence of Chunks.
+//
+// A Chunk consists of a ChunkDescriptor, Data, and Padding.
+//
+// Data and Padding may each be zero-length as specified by the
+// ChunkDescriptor.
+//
+// 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.
+//
+// ┌───────────────────────────┐
+// │Chunk                      │
+// │┌─────────────────────────┐│
+// ││ChunkDescriptor          ││
+// ││┌───────────┬───────────┐││
+// │││chunkType  │paddingSize│││
+// │││uint32_t   │uint32_t   ├┼┼───┐
+// ││├───────────┴───────────┤││   │
+// │││dataSize               │││   │
+// │││uint64_t               ├┼┼─┐ │
+// ││└───────────────────────┘││ │ │
+// │└─────────────────────────┘│ │ │
+// │┌─────────────────────────┐│ │ │
+// ││Data                     ││ │ │
+// ││bytes * dataSize         │◀─┘ │
+// │└─────────────────────────┘│   │
+// │┌─────────────────────────┐│   │
+// ││Padding                  ││   │
+// ││bytes * paddingSize      │◀───┘
+// │└─────────────────────────┘│
+// └───────────────────────────┘
+//
+// A RecordedTransaction is written as a Header Chunk with fields about the
+// transaction, a Data Parcel chunk, a Reply Parcel Chunk, and an End Chunk.
+// ┌──────────────────────┐
+// │     Header Chunk     │
+// ├──────────────────────┤
+// │  Sent Parcel Chunk   │
+// ├──────────────────────┤
+// │  Reply Parcel Chunk  │
+// ├──────────────────────┤
+// ║      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:
+//
+// 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.
+// 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.
 
 RecordedTransaction::RecordedTransaction(RecordedTransaction&& t) noexcept {
-    mHeader = {t.getCode(),      t.getFlags(),          t.getDataSize(),
-               t.getReplySize(), t.getReturnedStatus(), t.getVersion()};
-    mSent.setData(t.getDataParcel().data(), t.getDataSize());
-    mReply.setData(t.getReplyParcel().data(), t.getReplySize());
+    mHeader = {t.getCode(), t.getFlags(), t.getReturnedStatus(), t.getVersion()};
+    mSent.setData(t.getDataParcel().data(), t.getDataParcel().dataSize());
+    mReply.setData(t.getReplyParcel().data(), t.getReplyParcel().dataSize());
 }
 
 std::optional<RecordedTransaction> RecordedTransaction::fromDetails(uint32_t code, uint32_t flags,
@@ -53,19 +111,15 @@
                                                                     const Parcel& replyParcel,
                                                                     status_t err) {
     RecordedTransaction t;
-    t.mHeader = {code,
-                 flags,
-                 static_cast<uint64_t>(dataParcel.dataSize()),
-                 static_cast<uint64_t>(replyParcel.dataSize()),
-                 static_cast<int32_t>(err),
+    t.mHeader = {code, flags, static_cast<int32_t>(err),
                  dataParcel.isForRpc() ? static_cast<uint32_t>(1) : static_cast<uint32_t>(0)};
 
-    if (t.mSent.setData(dataParcel.data(), t.getDataSize()) != android::NO_ERROR) {
+    if (t.mSent.setData(dataParcel.data(), dataParcel.dataSize()) != android::NO_ERROR) {
         LOG(INFO) << "Failed to set sent parcel data.";
         return std::nullopt;
     }
 
-    if (t.mReply.setData(replyParcel.data(), t.getReplySize()) != android::NO_ERROR) {
+    if (t.mReply.setData(replyParcel.data(), replyParcel.dataSize()) != android::NO_ERROR) {
         LOG(INFO) << "Failed to set reply parcel data.";
         return std::nullopt;
     }
@@ -73,80 +127,154 @@
     return std::optional<RecordedTransaction>(std::move(t));
 }
 
+enum {
+    HEADER_CHUNK = 0x00000001,
+    DATA_PARCEL_CHUNK = 0x00000002,
+    REPLY_PARCEL_CHUNK = 0x00000003,
+    INVALID_CHUNK = 0x00fffffe,
+    END_CHUNK = 0x00ffffff,
+};
+
+struct ChunkDescriptor {
+    uint32_t chunkType = 0;
+    uint32_t padding = 0;
+    uint32_t dataSize = 0;
+    uint32_t reserved = 0; // Future checksum
+};
+
+static android::status_t readChunkDescriptor(borrowed_fd fd, ChunkDescriptor* chunkOut) {
+    if (!android::base::ReadFully(fd, chunkOut, sizeof(ChunkDescriptor))) {
+        LOG(INFO) << "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;
+    }
+    return android::NO_ERROR;
+}
+
 std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd& fd) {
     RecordedTransaction t;
-    if (!android::base::ReadFully(fd, &t.mHeader, sizeof(mHeader))) {
-        LOG(INFO) << "Failed to read transactionHeader from fd " << fd.get();
-        return std::nullopt;
-    }
-    if (t.getVersion() != 0) {
-        LOG(INFO) << "File corrupted: transaction version is not 0.";
-        return std::nullopt;
-    }
+    ChunkDescriptor chunk;
 
-    std::vector<uint8_t> bytes;
-    bytes.resize(t.getDataSize());
-    if (!android::base::ReadFully(fd, bytes.data(), t.getDataSize())) {
-        LOG(INFO) << "Failed to read sent parcel data from fd " << fd.get();
-        return std::nullopt;
-    }
-    if (t.mSent.setData(bytes.data(), t.getDataSize()) != android::NO_ERROR) {
-        LOG(INFO) << "Failed to set sent parcel data.";
-        return std::nullopt;
-    }
-
-    uint8_t padding[7];
-    if (!android::base::ReadFully(fd, padding, PADDING8(t.getDataSize()))) {
-        LOG(INFO) << "Failed to read sent parcel padding from fd " << fd.get();
-        return std::nullopt;
-    }
-    if (std::any_of(padding, padding + 7, [](uint8_t i) { return i != 0; })) {
-        LOG(INFO) << "File corrupted: padding isn't 0.";
-        return std::nullopt;
-    }
-
-    bytes.resize(t.getReplySize());
-    if (!android::base::ReadFully(fd, bytes.data(), t.getReplySize())) {
-        LOG(INFO) << "Failed to read reply parcel data from fd " << fd.get();
-        return std::nullopt;
-    }
-    if (t.mReply.setData(bytes.data(), t.getReplySize()) != android::NO_ERROR) {
-        LOG(INFO) << "Failed to set reply parcel data.";
-        return std::nullopt;
-    }
-
-    if (!android::base::ReadFully(fd, padding, PADDING8(t.getReplySize()))) {
-        LOG(INFO) << "Failed to read parcel padding from fd " << fd.get();
-        return std::nullopt;
-    }
-    if (std::any_of(padding, padding + 7, [](uint8_t i) { return i != 0; })) {
-        LOG(INFO) << "File corrupted: padding isn't 0.";
-        return std::nullopt;
-    }
+    do {
+        if (NO_ERROR != readChunkDescriptor(fd, &chunk)) {
+            LOG(INFO) << "Failed to read chunk descriptor.";
+            return std::nullopt;
+        }
+        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) << ".";
+                    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;
+                }
+                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();
+                    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();
+                    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;
+        }
+    } while (chunk.chunkType != END_CHUNK);
 
     return std::optional<RecordedTransaction>(std::move(t));
 }
 
+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;
+    }
+    // - 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;
+    }
+
+    if (!android::base::WriteFully(fd, data, byteCount)) {
+        LOG(INFO) << "Failed to write chunk data to fd " << fd.get();
+        return UNKNOWN_ERROR;
+    }
+
+    const uint8_t zeros[7] = {0};
+    if (!android::base::WriteFully(fd, zeros, additionalPaddingCount)) {
+        LOG(INFO) << "Failed to write chunk padding to fd " << fd.get();
+        return UNKNOWN_ERROR;
+    }
+    return NO_ERROR;
+}
+
 android::status_t RecordedTransaction::dumpToFile(const unique_fd& fd) const {
-    if (!android::base::WriteFully(fd, &mHeader, sizeof(mHeader))) {
+    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();
         return UNKNOWN_ERROR;
     }
-    if (!android::base::WriteFully(fd, mSent.data(), getDataSize())) {
-        LOG(INFO) << "Failed to write sent parcel data to fd " << fd.get();
+    if (NO_ERROR != writeChunk(fd, DATA_PARCEL_CHUNK, mSent.dataSize(), mSent.data())) {
+        LOG(INFO) << "Failed to write sent Parcel to fd " << fd.get();
         return UNKNOWN_ERROR;
     }
-    const uint8_t zeros[7] = {0};
-    if (!android::base::WriteFully(fd, zeros, PADDING8(getDataSize()))) {
-        LOG(INFO) << "Failed to write sent parcel padding to fd " << fd.get();
+    if (NO_ERROR != writeChunk(fd, REPLY_PARCEL_CHUNK, mReply.dataSize(), mReply.data())) {
+        LOG(INFO) << "Failed to write reply Parcel to fd " << fd.get();
         return UNKNOWN_ERROR;
     }
-    if (!android::base::WriteFully(fd, mReply.data(), getReplySize())) {
-        LOG(INFO) << "Failed to write reply parcel data to fd " << fd.get();
-        return UNKNOWN_ERROR;
-    }
-    if (!android::base::WriteFully(fd, zeros, PADDING8(getReplySize()))) {
-        LOG(INFO) << "Failed to write reply parcel padding to fd " << fd.get();
+    if (NO_ERROR != writeChunk(fd, END_CHUNK, 0, NULL)) {
+        LOG(INFO) << "Failed to write end chunk to fd " << fd.get();
         return UNKNOWN_ERROR;
     }
     return NO_ERROR;
@@ -160,14 +288,6 @@
     return mHeader.flags;
 }
 
-uint64_t RecordedTransaction::getDataSize() const {
-    return mHeader.dataSize;
-}
-
-uint64_t RecordedTransaction::getReplySize() const {
-    return mHeader.replySize;
-}
-
 int32_t RecordedTransaction::getReturnedStatus() const {
     return mHeader.statusReturned;
 }
diff --git a/libs/binder/include/binder/BinderRecordReplay.h b/libs/binder/include/binder/BinderRecordReplay.h
index 25ed5e5..609e5be 100644
--- a/libs/binder/include/binder/BinderRecordReplay.h
+++ b/libs/binder/include/binder/BinderRecordReplay.h
@@ -42,8 +42,6 @@
 
     uint32_t getCode() const;
     uint32_t getFlags() const;
-    uint64_t getDataSize() const;
-    uint64_t getReplySize() const;
     int32_t getReturnedStatus() const;
     uint32_t getVersion() const;
     const Parcel& getDataParcel() const;
@@ -52,27 +50,24 @@
 private:
     RecordedTransaction() = default;
 
+    android::status_t writeChunk(const android::base::borrowed_fd, uint32_t chunkType,
+                                 size_t byteCount, const uint8_t* data) const;
+
 #pragma clang diagnostic push
 #pragma clang diagnostic error "-Wpadded"
     struct TransactionHeader {
         uint32_t code = 0;
         uint32_t flags = 0;
-        uint64_t dataSize = 0;
-        uint64_t replySize = 0;
         int32_t statusReturned = 0;
         uint32_t version = 0; // !0 iff Rpc
     };
 #pragma clang diagnostic pop
-    static_assert(sizeof(TransactionHeader) == 32);
+    static_assert(sizeof(TransactionHeader) == 16);
     static_assert(sizeof(TransactionHeader) % 8 == 0);
 
     TransactionHeader mHeader;
     Parcel mSent;
     Parcel mReply;
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wunused-private-field"
-    uint8_t mReserved[40];
-#pragma clang diagnostic pop
 };
 
 } // namespace binder::debug
diff --git a/libs/binder/libbinder_rpc_unstable.cpp b/libs/binder/libbinder_rpc_unstable.cpp
index 78dae4b..e7943dd 100644
--- a/libs/binder/libbinder_rpc_unstable.cpp
+++ b/libs/binder/libbinder_rpc_unstable.cpp
@@ -112,6 +112,13 @@
         LOG(ERROR) << "Failed to get fd for the socket:" << name;
         return nullptr;
     }
+    // Control socket fds are inherited from init, so they don't have O_CLOEXEC set.
+    // But we don't want any child processes to inherit the socket we are running
+    // the server on, so attempt to set the flag now.
+    if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) {
+        LOG(WARNING) << "Failed to set CLOEXEC on control socket with name " << name
+                     << " error: " << errno;
+    }
     if (status_t status = server->setupRawSocketServer(std::move(fd)); status != OK) {
         LOG(ERROR) << "Failed to set up Unix Domain RPC server with name " << name
                    << " error: " << statusToString(status).c_str();
diff --git a/libs/binder/tests/binderRecordedTransactionTest.cpp b/libs/binder/tests/binderRecordedTransactionTest.cpp
index 23864e6..67e482d 100644
--- a/libs/binder/tests/binderRecordedTransactionTest.cpp
+++ b/libs/binder/tests/binderRecordedTransactionTest.cpp
@@ -42,8 +42,8 @@
 
     EXPECT_EQ(retrievedTransaction->getCode(), 1);
     EXPECT_EQ(retrievedTransaction->getFlags(), 42);
-    EXPECT_EQ(retrievedTransaction->getDataSize(), 12);
-    EXPECT_EQ(retrievedTransaction->getReplySize(), 4);
+    EXPECT_EQ(retrievedTransaction->getDataParcel().dataSize(), 12);
+    EXPECT_EQ(retrievedTransaction->getReplyParcel().dataSize(), 4);
     EXPECT_EQ(retrievedTransaction->getReturnedStatus(), 0);
     EXPECT_EQ(retrievedTransaction->getVersion(), 0);
 
diff --git a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
index d32cd80..fa0cc81 100644
--- a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
@@ -213,7 +213,8 @@
             size_t len = provider.ConsumeIntegralInRange<size_t>(0, provider.remaining_bytes());
             std::vector<uint8_t> parcelData = provider.ConsumeBytes<uint8_t>(len);
             const uint8_t* buffer = parcelData.data();
-            binder_status_t status = AParcel_unmarshal(AParcel_create(), buffer, len);
+            NdkParcelAdapter adapter;
+            binder_status_t status = AParcel_unmarshal(adapter.aParcel(), buffer, len);
             FUZZ_LOG() << "status: " << status;
         },
 
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 97e45c6..797d6ae 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -483,20 +483,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;
@@ -513,10 +511,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;
@@ -526,7 +524,7 @@
     if (buffer == nullptr) {
         mBufferItemConsumer->releaseBuffer(bufferItem, Fence::NO_FENCE);
         BQA_LOGE("Buffer was empty");
-        return;
+        return BAD_VALUE;
     }
 
     if (rejectBuffer(bufferItem)) {
@@ -535,8 +533,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++;
@@ -624,6 +621,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) {
@@ -647,32 +645,6 @@
     mBufferItemConsumer->releaseBuffer(bufferItem, bufferItem.mFence);
 }
 
-void BLASTBufferQueue::flushAndWaitForFreeBuffer(std::unique_lock<std::mutex>& lock) {
-    BBQ_TRACE();
-    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;
@@ -685,7 +657,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.
@@ -696,14 +667,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();
+                }
             }
         }
 
@@ -719,7 +696,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
@@ -829,15 +811,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 47dcc42..001d8e5 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -130,12 +130,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);
@@ -144,7 +143,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/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index 0719fe5..5e2ffa6 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -55,7 +55,7 @@
  * The returned event should not contain any of the real, existing data. It should only
  * contain the predicted samples.
  */
-std::vector<std::unique_ptr<MotionEvent>> MotionPredictor::predict() {
+std::vector<std::unique_ptr<MotionEvent>> MotionPredictor::predict(nsecs_t timestamp) {
     if (mEvents.size() < 2) {
         return {};
     }
@@ -67,7 +67,7 @@
 
     std::unique_ptr<MotionEvent> prediction = std::make_unique<MotionEvent>();
     std::vector<PointerCoords> futureCoords;
-    const int64_t futureTime = getExpectedPresentationTimeNanos() + mPredictionTimestampOffsetNanos;
+    const nsecs_t futureTime = timestamp + mPredictionTimestampOffsetNanos;
     const nsecs_t currentTime = event.getEventTime();
     const MotionEvent& previous = mEvents.rbegin()[1];
     const nsecs_t oldTime = previous.getEventTime();
@@ -143,14 +143,4 @@
     return true;
 }
 
-int64_t MotionPredictor::getExpectedPresentationTimeNanos() {
-    std::scoped_lock lock(mLock);
-    return mExpectedPresentationTimeNanos;
-}
-
-void MotionPredictor::setExpectedPresentationTimeNanos(int64_t expectedPresentationTimeNanos) {
-    std::scoped_lock lock(mLock);
-    mExpectedPresentationTimeNanos = expectedPresentationTimeNanos;
-}
-
 } // namespace android
diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp
index 0f39055..d2b59a1 100644
--- a/libs/input/tests/MotionPredictor_test.cpp
+++ b/libs/input/tests/MotionPredictor_test.cpp
@@ -63,8 +63,7 @@
     predictor.record(getMotionEvent(MOVE, 1, 3, 10));
     predictor.record(getMotionEvent(MOVE, 2, 5, 20));
     predictor.record(getMotionEvent(MOVE, 3, 7, 30));
-    predictor.setExpectedPresentationTimeNanos(40);
-    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict();
+    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
     ASSERT_EQ(1u, predicted.size());
     ASSERT_EQ(predicted[0]->getX(0), 4);
     ASSERT_EQ(predicted[0]->getY(0), 9);
@@ -81,8 +80,7 @@
     predictor.record(getMotionEvent(MOVE, 0, 1, 10));
     predictor.record(getMotionEvent(MOVE, 0, 1, 20));
     predictor.record(getMotionEvent(MOVE, 0, 1, 30));
-    predictor.setExpectedPresentationTimeNanos(40);
-    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict();
+    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
     ASSERT_EQ(1u, predicted.size());
     ASSERT_EQ(predicted[0]->getX(0), 0);
     ASSERT_EQ(predicted[0]->getY(0), 1);
@@ -98,21 +96,19 @@
 TEST(MotionPredictorTest, Offset) {
     MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/1,
                               []() { return true /*enable prediction*/; });
-    predictor.setExpectedPresentationTimeNanos(40);
     predictor.record(getMotionEvent(DOWN, 0, 1, 30));
     predictor.record(getMotionEvent(MOVE, 0, 1, 35));
-    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict();
+    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
     ASSERT_EQ(1u, predicted.size());
     ASSERT_GE(predicted[0]->getEventTime(), 41);
 }
 
-TEST(MotionPredictionTest, FlagDisablesPrediction) {
+TEST(MotionPredictorTest, FlagDisablesPrediction) {
     MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
                               []() { return false /*disable prediction*/; });
-    predictor.setExpectedPresentationTimeNanos(40);
     predictor.record(getMotionEvent(DOWN, 0, 1, 30));
     predictor.record(getMotionEvent(MOVE, 0, 1, 35));
-    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict();
+    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
     ASSERT_EQ(0u, predicted.size());
     ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_STYLUS));
     ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN));
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
index 39c79c9..5993546 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
@@ -36,11 +36,11 @@
     JpegDecoder();
     ~JpegDecoder();
     /*
-     * Decompresses JPEG image to raw image (YUV420planer or grey-scale) format. After calling
-     * this method, call getDecompressedImage() to get the image.
+     * Decompresses JPEG image to raw image (YUV420planer, grey-scale or RGBA) format. After
+     * calling this method, call getDecompressedImage() to get the image.
      * Returns false if decompressing the image fails.
      */
-    bool decompressImage(const void* image, int length);
+    bool decompressImage(const void* image, int length, bool decodeToRGBA = false);
     /*
      * Returns the decompressed raw image buffer pointer. This method must be called only after
      * calling decompressImage().
@@ -98,10 +98,11 @@
     bool extractEXIF(const void* image, int length);
 
 private:
-    bool decode(const void* image, int length);
+    bool decode(const void* image, int length, bool decodeToRGBA);
     // Returns false if errors occur.
     bool decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, bool isSingleChannel);
     bool decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest);
+    bool decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest);
     bool decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest);
     // Process 16 lines of Y and 16 lines of U/V each time.
     // We must pass at least 16 scanlines according to libjpeg documentation.
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
index 905bf16..05b1421 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
@@ -339,11 +339,11 @@
     /*
      * This method will tone map a HDR image to an SDR image.
      *
-     * @param uncompressed_p010_image (input) uncompressed P010 image
+     * @param src (input) uncompressed P010 image
      * @param dest (output) tone mapping result as a YUV_420 image
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
-    status_t toneMap(jr_uncompressed_ptr uncompressed_p010_image,
+    status_t toneMap(jr_uncompressed_ptr src,
                      jr_uncompressed_ptr dest);
 };
 
diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoder.cpp
index c2a8f45..07b527c 100644
--- a/libs/jpegrecoverymap/jpegdecoder.cpp
+++ b/libs/jpegrecoverymap/jpegdecoder.cpp
@@ -93,7 +93,7 @@
 JpegDecoder::~JpegDecoder() {
 }
 
-bool JpegDecoder::decompressImage(const void* image, int length) {
+bool JpegDecoder::decompressImage(const void* image, int length, bool decodeToRGBA) {
     if (image == nullptr || length <= 0) {
         ALOGE("Image size can not be handled: %d", length);
         return false;
@@ -101,7 +101,7 @@
 
     mResultBuffer.clear();
     mXMPBuffer.clear();
-    if (!decode(image, length)) {
+    if (!decode(image, length, decodeToRGBA)) {
         return false;
     }
 
@@ -140,7 +140,7 @@
     return mHeight;
 }
 
-bool JpegDecoder::decode(const void* image, int length) {
+bool JpegDecoder::decode(const void* image, int length, bool decodeToRGBA) {
     jpeg_decompress_struct cinfo;
     jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
     jpegrerror_mgr myerr;
@@ -210,15 +210,26 @@
     mWidth = cinfo.image_width;
     mHeight = cinfo.image_height;
 
-    if (cinfo.jpeg_color_space == JCS_YCbCr) {
-        mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0);
-    } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
-        mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0);
+    if (decodeToRGBA) {
+        if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
+            // We don't intend to support decoding grayscale to RGBA
+            return false;
+        }
+        // 4 bytes per pixel
+        mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 4);
+        cinfo.out_color_space = JCS_EXT_RGBA;
+    } else {
+        if (cinfo.jpeg_color_space == JCS_YCbCr) {
+            // 1 byte per pixel for Y, 0.5 byte per pixel for U+V
+            mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0);
+        } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
+            mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0);
+        }
+        cinfo.out_color_space = cinfo.jpeg_color_space;
+        cinfo.raw_data_out = TRUE;
     }
 
-    cinfo.raw_data_out = TRUE;
     cinfo.dct_method = JDCT_IFAST;
-    cinfo.out_color_space = cinfo.jpeg_color_space;
 
     jpeg_start_decompress(&cinfo);
 
@@ -292,7 +303,10 @@
     if (isSingleChannel) {
         return decompressSingleChannel(cinfo, dest);
     }
-    return decompressYUV(cinfo, dest);
+    if (cinfo->out_color_space == JCS_EXT_RGBA)
+        return decompressRGBA(cinfo, dest);
+    else
+        return decompressYUV(cinfo, dest);
 }
 
 bool JpegDecoder::getCompressedImageParameters(const void* image, int length,
@@ -331,6 +345,20 @@
     return true;
 }
 
+bool JpegDecoder::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+    JSAMPLE* decodeDst = (JSAMPLE*) dest;
+    uint32_t lines = 0;
+    // TODO: use batches for more effectiveness
+    while (lines < cinfo->image_height) {
+        uint32_t ret = jpeg_read_scanlines(cinfo, &decodeDst, 1);
+        if (ret == 0) {
+            break;
+        }
+        decodeDst += cinfo->image_width * 4;
+        lines++;
+    }
+    return lines == cinfo->image_height;
+}
 
 bool JpegDecoder::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
 
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index 1b69743..ee68043 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -25,7 +25,6 @@
 #include <image_io/jpeg/jpeg_scanner.h>
 #include <image_io/jpeg/jpeg_info_builder.h>
 #include <image_io/base/data_segment_data_source.h>
-#include <utils/Log.h>
 
 #include <memory>
 #include <sstream>
@@ -239,6 +238,9 @@
   }
 
   jpegr_uncompressed_struct uncompressed_yuv_420_image;
+  unique_ptr<uint8_t[]> uncompressed_yuv_420_image_data = make_unique<uint8_t[]>(
+      uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2);
+  uncompressed_yuv_420_image.data = uncompressed_yuv_420_image_data.get();
   JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image));
 
   jpegr_uncompressed_struct map;
@@ -265,7 +267,7 @@
   jpeg.length = jpeg_encoder.getCompressedImageSize();
 
   jpegr_exif_struct new_exif;
-  if (exif->data == nullptr) {
+  if (exif == nullptr || exif->data == nullptr) {
       new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
   } else {
       new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH;
@@ -542,23 +544,37 @@
   if (compressed_jpegr_image == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
   // TODO: fill EXIF data
   (void) exif;
 
+  if (request_sdr) {
+    JpegDecoder jpeg_decoder;
+    if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length,
+                                      true)) {
+        return ERROR_JPEGR_DECODE_ERROR;
+    }
+    jpegr_uncompressed_struct uncompressed_rgba_image;
+    uncompressed_rgba_image.data = jpeg_decoder.getDecompressedImagePtr();
+    uncompressed_rgba_image.width = jpeg_decoder.getDecompressedImageWidth();
+    uncompressed_rgba_image.height = jpeg_decoder.getDecompressedImageHeight();
+    memcpy(dest->data, uncompressed_rgba_image.data,
+           uncompressed_rgba_image.width * uncompressed_rgba_image.height * 4);
+    dest->width = uncompressed_rgba_image.width;
+    dest->height = uncompressed_rgba_image.height;
+    return NO_ERROR;
+  }
+
   jpegr_compressed_struct compressed_map;
   jpegr_metadata metadata;
   JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
 
-
   JpegDecoder jpeg_decoder;
   if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
 
   JpegDecoder recovery_map_decoder;
-  if (!recovery_map_decoder.decompressImage(compressed_map.data,
-                                    compressed_map.length)) {
+  if (!recovery_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
 
@@ -567,26 +583,17 @@
   map.width = recovery_map_decoder.getDecompressedImageWidth();
   map.height = recovery_map_decoder.getDecompressedImageHeight();
 
-
   jpegr_uncompressed_struct uncompressed_yuv_420_image;
   uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
   uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
   uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
 
   if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()),
-                                       jpeg_decoder.getXMPSize(), &metadata)) {
+                          jpeg_decoder.getXMPSize(), &metadata)) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
 
-  if (request_sdr) {
-    memcpy(dest->data, uncompressed_yuv_420_image.data,
-            uncompressed_yuv_420_image.width*uncompressed_yuv_420_image.height *3 / 2);
-    dest->width = uncompressed_yuv_420_image.width;
-    dest->height = uncompressed_yuv_420_image.height;
-  } else {
-    JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
-  }
-
+  JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
   return NO_ERROR;
 }
 
@@ -926,18 +933,39 @@
   return NO_ERROR;
 }
 
-status_t RecoveryMap::toneMap(jr_uncompressed_ptr uncompressed_p010_image,
+status_t RecoveryMap::toneMap(jr_uncompressed_ptr src,
                               jr_uncompressed_ptr dest) {
-  if (uncompressed_p010_image == nullptr || dest == nullptr) {
+  if (src == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  dest->width = uncompressed_p010_image->width;
-  dest->height = uncompressed_p010_image->height;
-  unique_ptr<uint8_t[]> dest_data = make_unique<uint8_t[]>(dest->width * dest->height * 3 / 2);
-  dest->data = dest_data.get();
+  dest->width = src->width;
+  dest->height = src->height;
 
-  // TODO: Tone map algorighm here.
+  size_t pixel_count = src->width * src->height;
+  for (size_t y = 0; y < src->height; ++y) {
+    for (size_t x = 0; x < src->width; ++x) {
+      size_t pixel_y_idx = x + y * src->width;
+      size_t pixel_uv_idx = x / 2 + (y / 2) * (src->width / 2);
+
+      uint16_t y_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_y_idx]
+                        >> 6;
+      uint16_t u_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_count + pixel_uv_idx * 2]
+                        >> 6;
+      uint16_t v_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_count + pixel_uv_idx * 2 + 1]
+                        >> 6;
+
+      uint8_t* y = &reinterpret_cast<uint8_t*>(dest->data)[pixel_y_idx];
+      uint8_t* u = &reinterpret_cast<uint8_t*>(dest->data)[pixel_count + pixel_uv_idx];
+      uint8_t* v = &reinterpret_cast<uint8_t*>(dest->data)[pixel_count * 5 / 4 + pixel_uv_idx];
+
+      *y = static_cast<uint8_t>((y_uint >> 2) & 0xff);
+      *u = static_cast<uint8_t>((u_uint >> 2) & 0xff);
+      *v = static_cast<uint8_t>((v_uint >> 2) & 0xff);
+    }
+  }
+
+  dest->colorGamut = src->colorGamut;
 
   return NO_ERROR;
 }
diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
index c3c6fd4..8ff12fb 100644
--- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
@@ -114,58 +114,57 @@
 }
 
 /* Test Encode API-0 and decode */
-// TODO: enable when tonemapper is ready.
-//TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) {
-//  int ret;
-//
-//  // Load input files.
-//  if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
-//    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
-//  }
-//  mRawP010Image.width = TEST_IMAGE_WIDTH;
-//  mRawP010Image.height = TEST_IMAGE_HEIGHT;
-//  mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
-//
-//  RecoveryMap recoveryMap;
-//
-//  jpegr_compressed_struct jpegR;
-//  jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
-//  jpegR.data = malloc(jpegR.maxLength);
-//  ret = recoveryMap.encodeJPEGR(
-//      &mRawP010Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, 90, nullptr);
-//  if (ret != OK) {
-//    FAIL() << "Error code is " << ret;
-//  }
-//  if (SAVE_ENCODING_RESULT) {
-//    // Output image data to file
-//    std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr";
-//    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
-//    if (!imageFile.is_open()) {
-//      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
-//    }
-//    imageFile.write((const char*)jpegR.data, jpegR.length);
-//  }
-//
-//  jpegr_uncompressed_struct decodedJpegR;
-//  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
-//  decodedJpegR.data = malloc(decodedJpegRSize);
-//  ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
-//  if (ret != OK) {
-//    FAIL() << "Error code is " << ret;
-//  }
-//  if (SAVE_DECODING_RESULT) {
-//    // Output image data to file
-//    std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10";
-//    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
-//    if (!imageFile.is_open()) {
-//      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
-//    }
-//    imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
-//  }
-//
-//  free(jpegR.data);
-//  free(decodedJpegR.data);
-//}
+TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) {
+  int ret;
+
+  // Load input files.
+  if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
+    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+  }
+  mRawP010Image.width = TEST_IMAGE_WIDTH;
+  mRawP010Image.height = TEST_IMAGE_HEIGHT;
+  mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+
+  RecoveryMap recoveryMap;
+
+  jpegr_compressed_struct jpegR;
+  jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
+  jpegR.data = malloc(jpegR.maxLength);
+  ret = recoveryMap.encodeJPEGR(
+      &mRawP010Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY, nullptr);
+  if (ret != OK) {
+    FAIL() << "Error code is " << ret;
+  }
+  if (SAVE_ENCODING_RESULT) {
+    // Output image data to file
+    std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr";
+    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+    if (!imageFile.is_open()) {
+      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+    }
+    imageFile.write((const char*)jpegR.data, jpegR.length);
+  }
+
+  jpegr_uncompressed_struct decodedJpegR;
+  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
+  decodedJpegR.data = malloc(decodedJpegRSize);
+  ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
+  if (ret != OK) {
+    FAIL() << "Error code is " << ret;
+  }
+  if (SAVE_DECODING_RESULT) {
+    // Output image data to file
+    std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10";
+    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+    if (!imageFile.is_open()) {
+      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+    }
+    imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
+  }
+
+  free(jpegR.data);
+  free(decodedJpegR.data);
+}
 
 /* Test Encode API-1 and decode */
 TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrThenDecode) {
diff --git a/libs/nativewindow/include/android/hardware_buffer_aidl.h b/libs/nativewindow/include/android/hardware_buffer_aidl.h
index 906d9c6..9fea21e 100644
--- a/libs/nativewindow/include/android/hardware_buffer_aidl.h
+++ b/libs/nativewindow/include/android/hardware_buffer_aidl.h
@@ -83,7 +83,7 @@
 class HardwareBuffer {
 public:
     HardwareBuffer() noexcept {}
-    explicit HardwareBuffer(HardwareBuffer&& other) noexcept : mBuffer(other.release()) {}
+    HardwareBuffer(HardwareBuffer&& other) noexcept : mBuffer(other.release()) {}
 
     ~HardwareBuffer() {
         reset();
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index d2c940f..d55ab28 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -197,6 +197,9 @@
         // The keyboard layout association has changed.
         CHANGE_KEYBOARD_LAYOUT_ASSOCIATION = 1 << 11,
 
+        // The stylus button reporting configurations has changed.
+        CHANGE_STYLUS_BUTTON_REPORTING = 1 << 12,
+
         // All devices must be reopened.
         CHANGE_MUST_REOPEN = 1 << 31,
     };
@@ -309,6 +312,10 @@
     // The set of currently disabled input devices.
     std::set<int32_t> disabledDevices;
 
+    // True if stylus button reporting through motion events should be enabled, in which case
+    // stylus button state changes are reported through motion events.
+    bool stylusButtonMotionEventsEnabled;
+
     InputReaderConfiguration()
           : virtualKeyQuietTime(0),
             pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f,
@@ -329,7 +336,8 @@
             pointerGestureMovementSpeedRatio(0.8f),
             pointerGestureZoomSpeedRatio(0.3f),
             showTouches(false),
-            pointerCaptureRequest() {}
+            pointerCaptureRequest(),
+            stylusButtonMotionEventsEnabled(true) {}
 
     static std::string changesToString(uint32_t changes);
 
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 9a7af40..4d51aee 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -88,6 +88,14 @@
     return rotatedDisplaySize;
 }
 
+static int32_t filterButtonState(InputReaderConfiguration& config, int32_t buttonState) {
+    if (!config.stylusButtonMotionEventsEnabled) {
+        buttonState &=
+                ~(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY | AMOTION_EVENT_BUTTON_STYLUS_SECONDARY);
+    }
+    return buttonState;
+}
+
 // --- RawPointerData ---
 
 void RawPointerData::getCentroidOfTouchingPointers(float* outX, float* outY) const {
@@ -1400,8 +1408,9 @@
     next.readTime = readTime;
 
     // Sync button state.
-    next.buttonState =
-            mTouchButtonAccumulator.getButtonState() | mCursorButtonAccumulator.getButtonState();
+    next.buttonState = filterButtonState(mConfig,
+                                         mTouchButtonAccumulator.getButtonState() |
+                                                 mCursorButtonAccumulator.getButtonState());
 
     // Sync scroll
     next.rawVScroll = mCursorScrollAccumulator.getRelativeVWheel();
@@ -1640,7 +1649,9 @@
 void TouchInputMapper::applyExternalStylusButtonState(nsecs_t when) {
     if (mDeviceMode == DeviceMode::DIRECT && hasExternalStylus()) {
         // If any of the external buttons are already pressed by the touch device, ignore them.
-        const int32_t pressedButtons = ~mCurrentRawState.buttonState & mExternalStylusState.buttons;
+        const int32_t pressedButtons =
+                filterButtonState(mConfig,
+                                  ~mCurrentRawState.buttonState & mExternalStylusState.buttons);
         const int32_t releasedButtons =
                 mExternalStylusButtonsApplied & ~mExternalStylusState.buttons;
 
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index f755356..bb8a30e 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -201,6 +201,10 @@
     mConfig.wheelVelocityControlParameters = params;
 }
 
+void FakeInputReaderPolicy::setStylusButtonMotionEventsEnabled(bool enabled) {
+    mConfig.stylusButtonMotionEventsEnabled = enabled;
+}
+
 void FakeInputReaderPolicy::getReaderConfiguration(InputReaderConfiguration* outConfig) {
     *outConfig = mConfig;
 }
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index 862ff0b..9ec3217 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -75,6 +75,7 @@
     float getPointerGestureMovementSpeedRatio();
     float getPointerGestureZoomSpeedRatio();
     void setVelocityControlParams(const VelocityControlParameters& params);
+    void setStylusButtonMotionEventsEnabled(bool enabled);
 
 private:
     void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index a464639..95d35f4 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -2020,6 +2020,56 @@
                   WithDeviceId(touchscreenId))));
 }
 
+TYPED_TEST(StylusButtonIntegrationTest, StylusButtonMotionEventsDisabled) {
+    TestFixture::mFakePolicy->setStylusButtonMotionEventsEnabled(false);
+    TestFixture::mReader->requestRefreshConfiguration(
+            InputReaderConfiguration::CHANGE_STYLUS_BUTTON_REPORTING);
+
+    const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint();
+    const auto touchscreenId = TestFixture::mTouchscreenInfo.getId();
+    const auto stylusId = TestFixture::mStylusInfo.getId();
+
+    // Start a stylus gesture. By the time this event is processed, the configuration change that
+    // was requested is guaranteed to be completed.
+    TestFixture::mTouchscreen->sendSlot(FIRST_SLOT);
+    TestFixture::mTouchscreen->sendTrackingId(FIRST_TRACKING_ID);
+    TestFixture::mTouchscreen->sendToolType(MT_TOOL_PEN);
+    TestFixture::mTouchscreen->sendDown(centerPoint);
+    TestFixture::mTouchscreen->sendSync();
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithDeviceId(touchscreenId))));
+
+    // Press and release a stylus button. Each change only generates a MOVE motion event.
+    // Key events are unaffected.
+    TestFixture::mStylus->pressKey(BTN_STYLUS);
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled(
+            AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD),
+                  WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithDeviceId(touchscreenId))));
+
+    TestFixture::mStylus->releaseKey(BTN_STYLUS);
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled(
+            AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD),
+                  WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithDeviceId(touchscreenId))));
+
+    // Finish the stylus gesture.
+    TestFixture::mTouchscreen->sendTrackingId(INVALID_TRACKING_ID);
+    TestFixture::mTouchscreen->sendSync();
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithDeviceId(touchscreenId))));
+}
+
 // --- ExternalStylusIntegrationTest ---
 
 // Verify the behavior of an external stylus. An external stylus can report pressure or button
@@ -6577,6 +6627,46 @@
                   WithCoords(toDisplayX(100), toDisplayY(200)), WithButtonState(0))));
 }
 
+TEST_F(SingleTouchInputMapperTest, StylusButtonMotionEventsDisabled) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(ui::ROTATION_0);
+    prepareButtons();
+    prepareAxes(POSITION);
+
+    mFakePolicy->setStylusButtonMotionEventsEnabled(false);
+
+    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+
+    // Press a stylus button.
+    processKey(mapper, BTN_STYLUS, 1);
+    processSync(mapper);
+
+    // Start a touch gesture and ensure that the stylus button is not reported.
+    processDown(mapper, 100, 200);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithButtonState(0))));
+
+    // Release and press the stylus button again.
+    processKey(mapper, BTN_STYLUS, 0);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithButtonState(0))));
+    processKey(mapper, BTN_STYLUS, 1);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithButtonState(0))));
+
+    // Release the touch gesture.
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0))));
+
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
 TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsSetToTouchNavigation_setsCorrectType) {
     mFakePolicy->addDeviceTypeAssociation(DEVICE_LOCATION, "touchNavigation");
     prepareDisplay(ui::ROTATION_0);
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 3dd86aa..b930477 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -440,9 +440,6 @@
     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
@@ -2445,8 +2442,7 @@
     if (!id) {
         return ui::ROTATION_0;
     }
-    if (!mIgnoreHwcPhysicalDisplayOrientation &&
-        getHwComposer().getComposer()->isSupported(
+    if (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 23ac19e..7bb0514 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -311,11 +311,6 @@
     // 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();