Merge "Convert input event type to enum class" into udc-dev
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc
index 92b1677..f1d8c72 100644
--- a/cmds/atrace/atrace.rc
+++ b/cmds/atrace/atrace.rc
@@ -540,6 +540,7 @@
 
 # Run atrace with the categories written in a file
 service boottrace /system/bin/atrace --async_start -f /data/misc/boottrace/categories
+    user root
     disabled
     oneshot
 
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 043a7f1..8ca927e 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -2206,6 +2206,16 @@
             continue;
         }
 
+        // Skip cached processes.
+        if (IsCached(pid)) {
+            // For consistency, the header and footer to this message match those
+            // dumped by debuggerd in the success case.
+            dprintf(fd, "\n---- pid %d at [unknown] ----\n", pid);
+            dprintf(fd, "Dump skipped for cached process.\n");
+            dprintf(fd, "---- end %d ----", pid);
+            continue;
+        }
+
         const std::string link_name = android::base::StringPrintf("/proc/%d/exe", pid);
         std::string exe;
         if (!android::base::Readlink(link_name, &exe)) {
diff --git a/cmds/dumpstate/dumpstate.rc b/cmds/dumpstate/dumpstate.rc
index a80da4e..d0030dd 100644
--- a/cmds/dumpstate/dumpstate.rc
+++ b/cmds/dumpstate/dumpstate.rc
@@ -8,6 +8,7 @@
     socket dumpstate stream 0660 shell log
     disabled
     oneshot
+    user root
 
 # dumpstatez generates a zipped bugreport but also uses a socket to print the file location once
 # it is finished.
@@ -16,9 +17,11 @@
     class main
     disabled
     oneshot
+    user root
 
 # bugreportd starts dumpstate binder service and makes it wait for a listener to connect.
 service bugreportd /system/bin/dumpstate -w
     class main
     disabled
     oneshot
+    user root
diff --git a/cmds/installd/installd.rc b/cmds/installd/installd.rc
index 5b08c77..525f0c8 100644
--- a/cmds/installd/installd.rc
+++ b/cmds/installd/installd.rc
@@ -1,6 +1,7 @@
 
 service installd /system/bin/installd
     class main
+    user root
     capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER FSETID KILL SETGID SETUID SYS_ADMIN
 
 on early-boot
diff --git a/include/input/RingBuffer.h b/include/input/RingBuffer.h
index 67984b7..37fe5af 100644
--- a/include/input/RingBuffer.h
+++ b/include/input/RingBuffer.h
@@ -103,6 +103,11 @@
     iterator end() { return {*this, mSize}; }
     const_iterator end() const { return {*this, mSize}; }
 
+    reference front() { return mBuffer[mBegin]; }
+    const_reference front() const { return mBuffer[mBegin]; }
+    reference back() { return mBuffer[bufferIndex(mSize - 1)]; }
+    const_reference back() const { return mBuffer[bufferIndex(mSize - 1)]; }
+
     reference operator[](size_type i) { return mBuffer[bufferIndex(i)]; }
     const_reference operator[](size_type i) const { return mBuffer[bufferIndex(i)]; }
 
diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp
index 851b407..9282856 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -553,7 +553,7 @@
             socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
     if (!socket_fd.ok()) {
         int savedErrno = errno;
-        ALOGE("Could not create socket: %s", strerror(savedErrno));
+        ALOGE("Could not create socket at %s: %s", addr.toString().c_str(), strerror(savedErrno));
         return -savedErrno;
     }
     if (0 != TEMP_FAILURE_RETRY(bind(socket_fd.get(), addr.addr(), addr.addrSize()))) {
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 504b3ce..8d13007 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -1126,6 +1126,11 @@
 
     android::base::unique_fd serverFd(
             TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
+
+    if (errno == EAFNOSUPPORT) {
+        return false;
+    }
+
     LOG_ALWAYS_FATAL_IF(serverFd == -1, "Could not create socket: %s", strerror(errno));
 
     sockaddr_vm serverAddr{
diff --git a/libs/bufferqueueconverter/Android.bp b/libs/bufferqueueconverter/Android.bp
index 5f145a1..d4605ea 100644
--- a/libs/bufferqueueconverter/Android.bp
+++ b/libs/bufferqueueconverter/Android.bp
@@ -13,7 +13,7 @@
     export_include_dirs: ["include"],
 }
 
-cc_library_shared {
+cc_library {
     name: "libbufferqueueconverter",
     vendor_available: true,
     vndk: {
diff --git a/libs/dumputils/dump_utils.cpp b/libs/dumputils/dump_utils.cpp
index 067ce17..97cb810 100644
--- a/libs/dumputils/dump_utils.cpp
+++ b/libs/dumputils/dump_utils.cpp
@@ -16,6 +16,7 @@
 #include <set>
 
 #include <android-base/file.h>
+#include <android-base/parseint.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
@@ -210,3 +211,18 @@
     return cmdline == "zygote" || cmdline == "zygote64" || cmdline == "usap32" ||
             cmdline == "usap64" || cmdline == "webview_zygote";
 }
+
+bool IsCached(int pid) {
+    std::string oom_score_adj;
+    if (!android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/oom_score_adj",
+                                                                     pid),
+                                         &oom_score_adj)) {
+        return false;
+    }
+    int32_t oom_score_adj_value;
+    if (!android::base::ParseInt(android::base::Trim(oom_score_adj), &oom_score_adj_value)) {
+        return false;
+    }
+    // An OOM score greater than 900 indicates a cached process.
+    return oom_score_adj_value >= 900;
+}
diff --git a/libs/dumputils/include/dumputils/dump_utils.h b/libs/dumputils/include/dumputils/dump_utils.h
index 7c5329d..f973d9f 100644
--- a/libs/dumputils/include/dumputils/dump_utils.h
+++ b/libs/dumputils/include/dumputils/dump_utils.h
@@ -25,4 +25,6 @@
 
 bool IsZygote(int pid);
 
+bool IsCached(int pid);
+
 #endif  // DUMPUTILS_H_
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 821dd37..5c324b2 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -582,7 +582,8 @@
     // Only update mSize for destination bounds if the incoming buffer matches the requested size.
     // Otherwise, it could cause stretching since the destination bounds will update before the
     // buffer with the new size is acquired.
-    if (mRequestedSize == getBufferSize(bufferItem)) {
+    if (mRequestedSize == getBufferSize(bufferItem) ||
+        bufferItem.mScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) {
         mSize = mRequestedSize;
     }
     Rect crop = computeCrop(bufferItem);
@@ -800,34 +801,24 @@
     mDequeueTimestamps.erase(bufferId);
 };
 
-void BLASTBufferQueue::syncNextTransaction(
+bool BLASTBufferQueue::syncNextTransaction(
         std::function<void(SurfaceComposerClient::Transaction*)> callback,
         bool acquireSingleBuffer) {
-    std::function<void(SurfaceComposerClient::Transaction*)> prevCallback = nullptr;
-    SurfaceComposerClient::Transaction* prevTransaction = nullptr;
+    LOG_ALWAYS_FATAL_IF(!callback,
+                        "BLASTBufferQueue: callback passed in to syncNextTransaction must not be "
+                        "NULL");
 
-    {
-        std::lock_guard _lock{mMutex};
-        BBQ_TRACE();
-        // We're about to overwrite the previous call so we should invoke that callback
-        // immediately.
-        if (mTransactionReadyCallback) {
-            prevCallback = mTransactionReadyCallback;
-            prevTransaction = mSyncTransaction;
-        }
-
-        mTransactionReadyCallback = callback;
-        if (callback) {
-            mSyncTransaction = new SurfaceComposerClient::Transaction();
-        } else {
-            mSyncTransaction = nullptr;
-        }
-        mAcquireSingleBuffer = mTransactionReadyCallback ? acquireSingleBuffer : true;
+    std::lock_guard _lock{mMutex};
+    BBQ_TRACE();
+    if (mTransactionReadyCallback) {
+        ALOGW("Attempting to overwrite transaction callback in syncNextTransaction");
+        return false;
     }
 
-    if (prevCallback) {
-        prevCallback(prevTransaction);
-    }
+    mTransactionReadyCallback = callback;
+    mSyncTransaction = new SurfaceComposerClient::Transaction();
+    mAcquireSingleBuffer = acquireSingleBuffer;
+    return true;
 }
 
 void BLASTBufferQueue::stopContinuousSyncTransaction() {
@@ -835,20 +826,35 @@
     SurfaceComposerClient::Transaction* prevTransaction = nullptr;
     {
         std::lock_guard _lock{mMutex};
-        bool invokeCallback = mTransactionReadyCallback && !mAcquireSingleBuffer;
-        if (invokeCallback) {
-            prevCallback = mTransactionReadyCallback;
-            prevTransaction = mSyncTransaction;
+        if (mAcquireSingleBuffer || !mTransactionReadyCallback) {
+            ALOGW("Attempting to stop continuous sync when none are active");
+            return;
         }
+
+        prevCallback = mTransactionReadyCallback;
+        prevTransaction = mSyncTransaction;
+
         mTransactionReadyCallback = nullptr;
         mSyncTransaction = nullptr;
         mAcquireSingleBuffer = true;
     }
+
     if (prevCallback) {
         prevCallback(prevTransaction);
     }
 }
 
+void BLASTBufferQueue::clearSyncTransaction() {
+    std::lock_guard _lock{mMutex};
+    if (!mAcquireSingleBuffer) {
+        ALOGW("Attempting to clear sync transaction when none are active");
+        return;
+    }
+
+    mTransactionReadyCallback = nullptr;
+    mSyncTransaction = nullptr;
+}
+
 bool BLASTBufferQueue::rejectBuffer(const BufferItem& item) {
     if (item.mScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) {
         // Only reject buffers if scaling mode is freeze.
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index b18bf5b..a5cf8d6 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -793,11 +793,15 @@
         return result;
     }
 
-    std::vector<CancelBufferInput> cancelBufferInputs(numBufferRequested);
+    std::vector<CancelBufferInput> cancelBufferInputs;
+    cancelBufferInputs.reserve(numBufferRequested);
     std::vector<status_t> cancelBufferOutputs;
     for (size_t i = 0; i < numBufferRequested; i++) {
-        cancelBufferInputs[i].slot = dequeueOutput[i].slot;
-        cancelBufferInputs[i].fence = dequeueOutput[i].fence;
+        if (dequeueOutput[i].result >= 0) {
+            CancelBufferInput& input = cancelBufferInputs.emplace_back();
+            input.slot = dequeueOutput[i].slot;
+            input.fence = dequeueOutput[i].fence;
+        }
     }
 
     for (const auto& output : dequeueOutput) {
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 69e9f8a..a49a859 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -97,9 +97,10 @@
     void releaseBufferCallbackLocked(const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
                                      std::optional<uint32_t> currentMaxAcquiredBufferCount,
                                      bool fakeRelease) REQUIRES(mMutex);
-    void syncNextTransaction(std::function<void(SurfaceComposerClient::Transaction*)> callback,
+    bool syncNextTransaction(std::function<void(SurfaceComposerClient::Transaction*)> callback,
                              bool acquireSingleBuffer = true);
     void stopContinuousSyncTransaction();
+    void clearSyncTransaction();
 
     void mergeWithNextTransaction(SurfaceComposerClient::Transaction* t, uint64_t frameNumber);
     void applyPendingTransactions(uint64_t frameNumber);
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index cf2593d..7067c11 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -32,6 +32,7 @@
 #include <private/gui/ComposerService.h>
 #include <private/gui/ComposerServiceAIDL.h>
 #include <ui/DisplayMode.h>
+#include <ui/DisplayState.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/GraphicTypes.h>
 #include <ui/Transform.h>
@@ -116,15 +117,17 @@
         mBlastBufferQueueAdapter->syncNextTransaction(callback, acquireSingleBuffer);
     }
 
-    void syncNextTransaction(std::function<void(Transaction*)> callback,
+    bool syncNextTransaction(std::function<void(Transaction*)> callback,
                              bool acquireSingleBuffer = true) {
-        mBlastBufferQueueAdapter->syncNextTransaction(callback, acquireSingleBuffer);
+        return mBlastBufferQueueAdapter->syncNextTransaction(callback, acquireSingleBuffer);
     }
 
     void stopContinuousSyncTransaction() {
         mBlastBufferQueueAdapter->stopContinuousSyncTransaction();
     }
 
+    void clearSyncTransaction() { mBlastBufferQueueAdapter->clearSyncTransaction(); }
+
     int getWidth() { return mBlastBufferQueueAdapter->mSize.width; }
 
     int getHeight() { return mBlastBufferQueueAdapter->mSize.height; }
@@ -198,11 +201,13 @@
         t.apply();
         t.clear();
 
-        ui::DisplayMode mode;
-        ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(mDisplayToken, &mode));
-        const ui::Size& resolution = mode.resolution;
+        ui::DisplayState displayState;
+        ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(mDisplayToken, &displayState));
+        const ui::Size& resolution = displayState.layerStackSpaceRect;
         mDisplayWidth = resolution.getWidth();
         mDisplayHeight = resolution.getHeight();
+        ALOGV("Display: %dx%d orientation:%d", mDisplayWidth, mDisplayHeight,
+              displayState.orientation);
 
         mSurfaceControl = mClient->createSurface(String8("TestSurface"), mDisplayWidth,
                                                  mDisplayHeight, PIXEL_FORMAT_RGBA_8888,
@@ -1108,7 +1113,11 @@
     ASSERT_NE(nullptr, adapter.getTransactionReadyCallback());
 
     auto callback2 = [](Transaction*) {};
-    adapter.syncNextTransaction(callback2);
+    ASSERT_FALSE(adapter.syncNextTransaction(callback2));
+
+    sp<IGraphicBufferProducer> igbProducer;
+    setUpProducer(adapter, igbProducer);
+    queueBuffer(igbProducer, 0, 255, 0, 0);
 
     std::unique_lock<std::mutex> lock(mutex);
     if (!receivedCallback) {
@@ -1120,6 +1129,37 @@
     ASSERT_TRUE(receivedCallback);
 }
 
+TEST_F(BLASTBufferQueueTest, ClearSyncTransaction) {
+    std::mutex mutex;
+    std::condition_variable callbackReceivedCv;
+    bool receivedCallback = false;
+
+    BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+    ASSERT_EQ(nullptr, adapter.getTransactionReadyCallback());
+    auto callback = [&](Transaction*) {
+        std::unique_lock<std::mutex> lock(mutex);
+        receivedCallback = true;
+        callbackReceivedCv.notify_one();
+    };
+    adapter.syncNextTransaction(callback);
+    ASSERT_NE(nullptr, adapter.getTransactionReadyCallback());
+
+    adapter.clearSyncTransaction();
+
+    sp<IGraphicBufferProducer> igbProducer;
+    setUpProducer(adapter, igbProducer);
+    queueBuffer(igbProducer, 0, 255, 0, 0);
+
+    std::unique_lock<std::mutex> lock(mutex);
+    if (!receivedCallback) {
+        ASSERT_EQ(callbackReceivedCv.wait_for(lock, std::chrono::seconds(3)),
+                  std::cv_status::timeout)
+                << "did not receive callback";
+    }
+
+    ASSERT_FALSE(receivedCallback);
+}
+
 TEST_F(BLASTBufferQueueTest, SyncNextTransactionDropBuffer) {
     uint8_t r = 255;
     uint8_t g = 0;
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index a5734b7..4ec7a06 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -628,7 +628,7 @@
 
     // Expect no crash for overflow.
     injectTap(12, 24);
-    fgSurface->expectTap(6, 12);
+    bgSurface->expectTap(12, 24);
 }
 
 // Ensure we ignore transparent region when getting screen bounds when positioning input frame.
@@ -1235,32 +1235,6 @@
     surface->expectKey(AKEYCODE_V);
 }
 
-/**
- * When multiple DisplayDevices are mapped to the same layerStack, use the configuration for the
- * display that can receive input.
- */
-TEST_F(MultiDisplayTests, many_to_one_display_mapping) {
-    ui::LayerStack layerStack = ui::LayerStack::fromValue(42);
-    createDisplay(1000, 1000, false /*isSecure*/, layerStack, false /*receivesInput*/,
-                  100 /*offsetX*/, 100 /*offsetY*/);
-    createDisplay(1000, 1000, false /*isSecure*/, layerStack, true /*receivesInput*/,
-                  200 /*offsetX*/, 200 /*offsetY*/);
-    createDisplay(1000, 1000, false /*isSecure*/, layerStack, false /*receivesInput*/,
-                  300 /*offsetX*/, 300 /*offsetY*/);
-    std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
-    surface->doTransaction([&](auto &t, auto &sc) { t.setLayerStack(sc, layerStack); });
-    surface->showAt(10, 10);
-
-    // Input injection happens in logical display coordinates.
-    injectTapOnDisplay(11, 11, layerStack.id);
-    // Expect that the display transform for the display that receives input was used.
-    surface->expectTapInDisplayCoordinates(211, 211);
-
-    surface->requestFocus(layerStack.id);
-    surface->assertFocusChange(true);
-    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
-}
-
 TEST_F(MultiDisplayTests, drop_input_for_secure_layer_on_nonsecure_display) {
     ui::LayerStack layerStack = ui::LayerStack::fromValue(42);
     createDisplay(1000, 1000, false /*isSecure*/, layerStack);
diff --git a/libs/input/tests/RingBuffer_test.cpp b/libs/input/tests/RingBuffer_test.cpp
index 8a6ef4c..a2ef658 100644
--- a/libs/input/tests/RingBuffer_test.cpp
+++ b/libs/input/tests/RingBuffer_test.cpp
@@ -118,6 +118,21 @@
     EXPECT_EQ(0u, d.capacity());
 }
 
+TEST(RingBufferTest, FrontBackAccess) {
+    RingBuffer<int> buffer(/*capacity=*/2);
+    buffer.pushBack(1);
+    EXPECT_EQ(1, buffer.front());
+    EXPECT_EQ(1, buffer.back());
+
+    buffer.pushFront(0);
+    EXPECT_EQ(0, buffer.front());
+    EXPECT_EQ(1, buffer.back());
+
+    buffer.pushFront(-1);
+    EXPECT_EQ(-1, buffer.front());
+    EXPECT_EQ(0, buffer.back());
+}
+
 TEST(RingBufferTest, Subscripting) {
     RingBuffer<int> buffer(/*capacity=*/2);
     buffer.pushBack(1);
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp
index a1b0e19..a376ced 100644
--- a/libs/jpegrecoverymap/Android.bp
+++ b/libs/jpegrecoverymap/Android.bp
@@ -31,7 +31,7 @@
     srcs: [
         "icc.cpp",
         "jpegr.cpp",
-        "recoverymapmath.cpp",
+        "gainmapmath.cpp",
         "jpegrutils.cpp",
         "multipictureformat.cpp",
     ],
diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/gainmapmath.cpp
similarity index 91%
rename from libs/jpegrecoverymap/recoverymapmath.cpp
rename to libs/jpegrecoverymap/gainmapmath.cpp
index 2cffde3..f15a078 100644
--- a/libs/jpegrecoverymap/recoverymapmath.cpp
+++ b/libs/jpegrecoverymap/gainmapmath.cpp
@@ -16,7 +16,7 @@
 
 #include <cmath>
 #include <vector>
-#include <jpegrecoverymap/recoverymapmath.h>
+#include <jpegrecoverymap/gainmapmath.h>
 
 namespace android::jpegrecoverymap {
 
@@ -441,8 +441,14 @@
 
 
 ////////////////////////////////////////////////////////////////////////////////
-// Recovery map calculations
-uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata) {
+// Gain map calculations
+uint8_t encodeGain(float y_sdr, float y_hdr, jr_metadata_ptr metadata) {
+  return encodeGain(y_sdr, y_hdr, metadata,
+                    log2(metadata->minContentBoost), log2(metadata->maxContentBoost));
+}
+
+uint8_t encodeGain(float y_sdr, float y_hdr, jr_metadata_ptr metadata,
+                   float log2MinContentBoost, float log2MaxContentBoost) {
   float gain = 1.0f;
   if (y_sdr > 0.0f) {
     gain = y_hdr / y_sdr;
@@ -451,28 +457,28 @@
   if (gain < metadata->minContentBoost) gain = metadata->minContentBoost;
   if (gain > metadata->maxContentBoost) gain = metadata->maxContentBoost;
 
-  return static_cast<uint8_t>((log2(gain) - log2(metadata->minContentBoost))
-                            / (log2(metadata->maxContentBoost) - log2(metadata->minContentBoost))
+  return static_cast<uint8_t>((log2(gain) - log2MinContentBoost)
+                            / (log2MaxContentBoost - log2MinContentBoost)
                             * 255.0f);
 }
 
-Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata) {
-  float logBoost = log2(metadata->minContentBoost) * (1.0f - recovery)
-                 + log2(metadata->maxContentBoost) * recovery;
-  float recoveryFactor = exp2(logBoost);
-  return e * recoveryFactor;
+Color applyGain(Color e, float gain, jr_metadata_ptr metadata) {
+  float logBoost = log2(metadata->minContentBoost) * (1.0f - gain)
+                 + log2(metadata->maxContentBoost) * gain;
+  float gainFactor = exp2(logBoost);
+  return e * gainFactor;
 }
 
-Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata, float displayBoost) {
-  float logBoost = log2(metadata->minContentBoost) * (1.0f - recovery)
-                 + log2(metadata->maxContentBoost) * recovery;
-  float recoveryFactor = exp2(logBoost * displayBoost / metadata->maxContentBoost);
-  return e * recoveryFactor;
+Color applyGain(Color e, float gain, jr_metadata_ptr metadata, float displayBoost) {
+  float logBoost = log2(metadata->minContentBoost) * (1.0f - gain)
+                 + log2(metadata->maxContentBoost) * gain;
+  float gainFactor = exp2(logBoost * displayBoost / metadata->maxContentBoost);
+  return e * gainFactor;
 }
 
-Color applyRecoveryLUT(Color e, float recovery, RecoveryLUT& recoveryLUT) {
-  float recoveryFactor = recoveryLUT.getRecoveryFactor(recovery);
-  return e * recoveryFactor;
+Color applyGainLUT(Color e, float gain, GainLUT& gainLUT) {
+  float gainFactor = gainLUT.getGainFactor(gain);
+  return e * gainFactor;
 }
 
 Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
@@ -493,17 +499,28 @@
 }
 
 Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
-  size_t pixel_count = image->width * image->height;
+  size_t luma_stride = image->luma_stride;
+  size_t chroma_stride = image->chroma_stride;
+  uint16_t* luma_data = reinterpret_cast<uint16_t*>(image->data);
+  uint16_t* chroma_data = reinterpret_cast<uint16_t*>(image->chroma_data);
 
-  size_t pixel_y_idx = x + y * image->width;
-  size_t pixel_uv_idx = x / 2 + (y / 2) * (image->width / 2);
+  if (luma_stride == 0) {
+    luma_stride = image->width;
+  }
+  if (chroma_stride == 0) {
+    chroma_stride = luma_stride;
+  }
+  if (chroma_data == nullptr) {
+    chroma_data = &reinterpret_cast<uint16_t*>(image->data)[image->luma_stride * image->height];
+  }
 
-  uint16_t y_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_y_idx]
-                  >> 6;
-  uint16_t u_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_count + pixel_uv_idx * 2]
-                  >> 6;
-  uint16_t v_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_count + pixel_uv_idx * 2 + 1]
-                  >> 6;
+  size_t pixel_y_idx = y * luma_stride + x;
+  size_t pixel_u_idx = (y >> 1) * chroma_stride + (x & ~0x1);
+  size_t pixel_v_idx = pixel_u_idx + 1;
+
+  uint16_t y_uint = luma_data[pixel_y_idx] >> 6;
+  uint16_t u_uint = chroma_data[pixel_u_idx] >> 6;
+  uint16_t v_uint = chroma_data[pixel_v_idx] >> 6;
 
   // Conversions include taking narrow-range into account.
   return {{{ (static_cast<float>(y_uint) - 64.0f) / 876.0f,
diff --git a/libs/jpegrecoverymap/icc.cpp b/libs/jpegrecoverymap/icc.cpp
index 5412cb1..6e78f67 100644
--- a/libs/jpegrecoverymap/icc.cpp
+++ b/libs/jpegrecoverymap/icc.cpp
@@ -15,7 +15,7 @@
  */
 
 #include <jpegrecoverymap/icc.h>
-#include <jpegrecoverymap/recoverymapmath.h>
+#include <jpegrecoverymap/gainmapmath.h>
 #include <vector>
 #include <utils/Log.h>
 
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/gainmapmath.h
similarity index 83%
rename from libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
rename to libs/jpegrecoverymap/include/jpegrecoverymap/gainmapmath.h
index 67d2a6a..57fddd0 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/gainmapmath.h
@@ -116,47 +116,53 @@
 }
 
 inline uint16_t floatToHalf(float f) {
-  uint32_t x = *((uint32_t*)&f);
-  uint16_t h = ((x >> 16) & 0x8000)
-             | ((((x & 0x7f800000) - 0x38000000) >> 13) & 0x7c00)
-             | ((x >> 13) & 0x03ff);
-  return h;
+  // round-to-nearest-even: add last bit after truncated mantissa
+  const uint32_t b = *((uint32_t*)&f) + 0x00001000;
+
+  const uint32_t e = (b & 0x7F800000) >> 23; // exponent
+  const uint32_t m = b & 0x007FFFFF; // mantissa
+
+  // sign : normalized : denormalized : saturate
+  return (b & 0x80000000) >> 16
+            | (e > 112) * ((((e - 112) << 10) & 0x7C00) | m >> 13)
+            | ((e < 113) & (e > 101)) * ((((0x007FF000 + m) >> (125 - e)) + 1) >> 1)
+            | (e > 143) * 0x7FFF;
 }
 
-constexpr size_t kRecoveryFactorPrecision = 10;
-constexpr size_t kRecoveryFactorNumEntries = 1 << kRecoveryFactorPrecision;
-struct RecoveryLUT {
-  RecoveryLUT(jr_metadata_ptr metadata) {
-    for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kRecoveryFactorNumEntries - 1);
+constexpr size_t kGainFactorPrecision = 10;
+constexpr size_t kGainFactorNumEntries = 1 << kGainFactorPrecision;
+struct GainLUT {
+  GainLUT(jr_metadata_ptr metadata) {
+    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
+      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
       float logBoost = log2(metadata->minContentBoost) * (1.0f - value)
                      + log2(metadata->maxContentBoost) * value;
-      mRecoveryTable[idx] = exp2(logBoost);
+      mGainTable[idx] = exp2(logBoost);
     }
   }
 
-  RecoveryLUT(jr_metadata_ptr metadata, float displayBoost) {
+  GainLUT(jr_metadata_ptr metadata, float displayBoost) {
     float boostFactor = displayBoost > 0 ? displayBoost / metadata->maxContentBoost : 1.0f;
-    for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kRecoveryFactorNumEntries - 1);
+    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
+      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
       float logBoost = log2(metadata->minContentBoost) * (1.0f - value)
                      + log2(metadata->maxContentBoost) * value;
-      mRecoveryTable[idx] = exp2(logBoost * boostFactor);
+      mGainTable[idx] = exp2(logBoost * boostFactor);
     }
   }
 
-  ~RecoveryLUT() {
+  ~GainLUT() {
   }
 
-  float getRecoveryFactor(float recovery) {
-    uint32_t idx = static_cast<uint32_t>(recovery * (kRecoveryFactorNumEntries - 1));
+  float getGainFactor(float gain) {
+    uint32_t idx = static_cast<uint32_t>(gain * (kGainFactorNumEntries - 1));
     //TODO() : Remove once conversion modules have appropriate clamping in place
-    idx = CLIP3(idx, 0, kRecoveryFactorNumEntries - 1);
-    return mRecoveryTable[idx];
+    idx = CLIP3(idx, 0, kGainFactorNumEntries - 1);
+    return mGainTable[idx];
   }
 
 private:
-  float mRecoveryTable[kRecoveryFactorNumEntries];
+  float mGainTable[kGainFactorNumEntries];
 };
 
 struct ShepardsIDW {
@@ -189,11 +195,11 @@
   // p60 p61 p62 p63 p64 p65 p66 p67
   // p70 p71 p72 p73 p74 p75 p76 p77
 
-  // Recovery Map (for 4 scale factor) :-
+  // Gain Map (for 4 scale factor) :-
   // m00 p01
   // m10 m11
 
-  // Recovery sample of curr 4x4, right 4x4, bottom 4x4, bottom right 4x4 are used during
+  // Gain sample of curr 4x4, right 4x4, bottom 4x4, bottom right 4x4 are used during
   // reconstruction. hence table weight size is 4.
   float* mWeights;
   // TODO: check if its ok to mWeights at places
@@ -348,27 +354,29 @@
 inline Color identityConversion(Color e) { return e; }
 
 /*
- * Get the conversion to apply to the HDR image for recovery map generation
+ * Get the conversion to apply to the HDR image for gain map generation
  */
 ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gamut hdr_gamut);
 
 
 ////////////////////////////////////////////////////////////////////////////////
-// Recovery map calculations
+// Gain map calculations
 
 /*
- * Calculate the 8-bit unsigned integer recovery value for the given SDR and HDR
+ * Calculate the 8-bit unsigned integer gain value for the given SDR and HDR
  * luminances in linear space, and the hdr ratio to encode against.
  */
-uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata);
+uint8_t encodeGain(float y_sdr, float y_hdr, jr_metadata_ptr metadata);
+uint8_t encodeGain(float y_sdr, float y_hdr, jr_metadata_ptr metadata,
+                   float log2MinContentBoost, float log2MaxContentBoost);
 
 /*
- * Calculates the linear luminance in nits after applying the given recovery
+ * Calculates the linear luminance in nits after applying the given gain
  * value, with the given hdr ratio, to the given sdr input in the range [0, 1].
  */
-Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata);
-Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata, float displayBoost);
-Color applyRecoveryLUT(Color e, float recovery, RecoveryLUT& recoveryLUT);
+Color applyGain(Color e, float gain, jr_metadata_ptr metadata);
+Color applyGain(Color e, float gain, jr_metadata_ptr metadata, float displayBoost);
+Color applyGainLUT(Color e, float gain, GainLUT& gainLUT);
 
 /*
  * Helper for sampling from YUV 420 images.
@@ -397,7 +405,7 @@
 Color sampleP010(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
 
 /*
- * Sample the recovery value for the map from a given x,y coordinate on a scale
+ * Sample the gain value for the map from a given x,y coordinate on a scale
  * that is map scale factor larger than the map size.
  */
 float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y);
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h
index 1ab1dd7..ce7b33b 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h
@@ -19,6 +19,10 @@
 
 #include "jpegrerrorcode.h"
 
+#ifndef FLT_MAX
+#define FLT_MAX 0x1.fffffep127f
+#endif
+
 namespace android::jpegrecoverymap {
 
 // Color gamuts for image data
@@ -54,21 +58,35 @@
 };
 
 /*
- * Holds information for uncompressed image or recovery map.
+ * Holds information for uncompressed image or gain map.
  */
 struct jpegr_uncompressed_struct {
     // Pointer to the data location.
     void* data;
-    // Width of the recovery map or image in pixels.
+    // Width of the gain map or the luma plane of the image in pixels.
     int width;
-    // Height of the recovery map or image in pixels.
+    // Height of the gain map or the luma plane of the image in pixels.
     int height;
     // Color gamut.
     jpegr_color_gamut colorGamut;
+
+    // Values below are optional
+    // Pointer to chroma data, if it's NULL, chroma plane is considered to be immediately
+    // following after the luma plane.
+    // Note: currently this feature is only supported for P010 image (HDR input).
+    void* chroma_data = nullptr;
+    // Strides of Y plane in number of pixels, using 0 to present uninitialized, must be
+    // larger than or equal to luma width.
+    // Note: currently this feature is only supported for P010 image (HDR input).
+    int luma_stride = 0;
+    // Strides of UV plane in number of pixels, using 0 to present uninitialized, must be
+    // larger than or equal to chroma width.
+    // Note: currently this feature is only supported for P010 image (HDR input).
+    int chroma_stride = 0;
 };
 
 /*
- * Holds information for compressed image or recovery map.
+ * Holds information for compressed image or gain map.
  */
 struct jpegr_compressed_struct {
     // Pointer to the data location.
@@ -92,7 +110,7 @@
 };
 
 /*
- * Holds information for recovery map related metadata.
+ * Holds information for gain map related metadata.
  */
 struct jpegr_metadata_struct {
   // JPEG/R version
@@ -117,8 +135,8 @@
      * Encode API-0
      * Compress JPEGR image from 10-bit HDR YUV.
      *
-     * Tonemap the HDR input to a SDR image, generate recovery map from the HDR and SDR images,
-     * compress SDR YUV to 8-bit JPEG and append the recovery map to the end of the compressed
+     * Tonemap the HDR input to a SDR image, generate gain map from the HDR and SDR images,
+     * compress SDR YUV to 8-bit JPEG and append the gain map to the end of the compressed
      * JPEG.
      * @param uncompressed_p010_image uncompressed HDR image in P010 color format
      * @param hdr_tf transfer function of the HDR image
@@ -138,8 +156,8 @@
      * Encode API-1
      * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
      *
-     * Generate recovery map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append
-     * the recovery map to the end of the compressed JPEG. HDR and SDR inputs must be the same
+     * Generate gain map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append
+     * the gain map to the end of the compressed JPEG. HDR and SDR inputs must be the same
      * resolution.
      * @param uncompressed_p010_image uncompressed HDR image in P010 color format
      * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
@@ -163,7 +181,7 @@
      *
      * This method requires HAL Hardware JPEG encoder.
      *
-     * Generate recovery map from the HDR and SDR inputs, append the recovery map to the end of the
+     * Generate gain map from the HDR and SDR inputs, append the gain map to the end of the
      * compressed JPEG. HDR and SDR inputs must be the same resolution and color space.
      * @param uncompressed_p010_image uncompressed HDR image in P010 color format
      * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
@@ -186,8 +204,8 @@
      *
      * This method requires HAL Hardware JPEG encoder.
      *
-     * Decode the compressed 8-bit JPEG image to YUV SDR, generate recovery map from the HDR input
-     * and the decoded SDR result, append the recovery map to the end of the compressed JPEG. HDR
+     * Decode the compressed 8-bit JPEG image to YUV SDR, generate gain map from the HDR input
+     * and the decoded SDR result, append the gain map to the end of the compressed JPEG. HDR
      * and SDR inputs must be the same resolution.
      * @param uncompressed_p010_image uncompressed HDR image in P010 color format
      * @param compressed_jpeg_image compressed 8-bit JPEG image
@@ -206,7 +224,8 @@
      *
      * @param compressed_jpegr_image compressed JPEGR image.
      * @param dest destination of the uncompressed JPEGR image.
-     * @param max_display_boost (optional) the maximum available boost supported by a display
+     * @param max_display_boost (optional) the maximum available boost supported by a display,
+     *                          the value must be greater than or equal to 1.0.
      * @param exif destination of the decoded EXIF metadata. The default value is NULL where the
                    decoder will do nothing about it. If configured not NULL the decoder will write
                    EXIF data into this structure. The format is defined in {@code jpegr_exif_struct}
@@ -223,9 +242,9 @@
                             ----------------------------------------------------------------------
                             |   JPEGR_OUTPUT_HDR_HLG   |            RGBA_1010102 HLG             |
                             ----------------------------------------------------------------------
-     * @param recovery_map destination of the decoded recovery map. The default value is NULL where
+     * @param gain_map destination of the decoded gain map. The default value is NULL where
                            the decoder will do nothing about it. If configured not NULL the decoder
-                           will write the decoded recovery_map data into this structure. The format
+                           will write the decoded gain_map data into this structure. The format
                            is defined in {@code jpegr_uncompressed_struct}.
      * @param metadata destination of the decoded metadata. The default value is NULL where the
                        decoder will do nothing about it. If configured not NULL the decoder will
@@ -235,10 +254,10 @@
      */
     status_t decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
                          jr_uncompressed_ptr dest,
-                         float max_display_boost = -1.0f,
+                         float max_display_boost = FLT_MAX,
                          jr_exif_ptr exif = nullptr,
                          jpegr_output_format output_format = JPEGR_OUTPUT_HDR_LINEAR,
-                         jr_uncompressed_ptr recovery_map = nullptr,
+                         jr_uncompressed_ptr gain_map = nullptr,
                          jr_metadata_ptr metadata = nullptr);
 
     /*
@@ -255,30 +274,30 @@
 protected:
     /*
      * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and
-     * 10-bit yuv images as input, and calculate the uncompressed recovery map. The input images
+     * 10-bit yuv images as input, and calculate the uncompressed gain map. The input images
      * must be the same resolution.
      *
      * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
      * @param uncompressed_p010_image uncompressed HDR image in P010 color format
      * @param hdr_tf transfer function of the HDR image
-     * @param dest recovery map; caller responsible for memory of data
+     * @param dest gain map; caller responsible for memory of data
      * @param metadata max_content_boost is filled in
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
-    status_t generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
-                                 jr_uncompressed_ptr uncompressed_p010_image,
-                                 jpegr_transfer_function hdr_tf,
-                                 jr_metadata_ptr metadata,
-                                 jr_uncompressed_ptr dest);
+    status_t generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+                             jr_uncompressed_ptr uncompressed_p010_image,
+                             jpegr_transfer_function hdr_tf,
+                             jr_metadata_ptr metadata,
+                             jr_uncompressed_ptr dest);
 
     /*
      * This method is called in the decoding pipeline. It will take the uncompressed (decoded)
-     * 8-bit yuv image, the uncompressed (decoded) recovery map, and extracted JPEG/R metadata as
+     * 8-bit yuv image, the uncompressed (decoded) gain map, and extracted JPEG/R metadata as
      * input, and calculate the 10-bit recovered image. The recovered output image is the same
      * color gamut as the SDR image, with HLG transfer function, and is in RGBA1010102 data format.
      *
      * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
-     * @param uncompressed_recovery_map uncompressed recovery map
+     * @param uncompressed_gain_map uncompressed gain map
      * @param metadata JPEG/R metadata extracted from XMP.
      * @param output_format flag for setting output color format. if set to
      *                      {@code JPEGR_OUTPUT_SDR}, decoder will only decode the primary image
@@ -287,67 +306,67 @@
      * @param dest reconstructed HDR image
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
-    status_t applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
-                              jr_uncompressed_ptr uncompressed_recovery_map,
-                              jr_metadata_ptr metadata,
-                              jpegr_output_format output_format,
-                              float max_display_boost,
-                              jr_uncompressed_ptr dest);
+    status_t applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+                          jr_uncompressed_ptr uncompressed_gain_map,
+                          jr_metadata_ptr metadata,
+                          jpegr_output_format output_format,
+                          float max_display_boost,
+                          jr_uncompressed_ptr dest);
 
 private:
     /*
-     * This method is called in the encoding pipeline. It will encode the recovery map.
+     * This method is called in the encoding pipeline. It will encode the gain map.
      *
-     * @param uncompressed_recovery_map uncompressed recovery map
+     * @param uncompressed_gain_map uncompressed gain map
      * @param dest encoded recover map
      * @return NO_ERROR if encoding succeeds, error code if error occurs.
      */
-    status_t compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
-                               jr_compressed_ptr dest);
+    status_t compressGainMap(jr_uncompressed_ptr uncompressed_gain_map,
+                             jr_compressed_ptr dest);
 
     /*
-     * This methoud is called to separate primary image and recovery map image from JPEGR
+     * This methoud is called to separate primary image and gain map image from JPEGR
      *
      * @param compressed_jpegr_image compressed JPEGR image
      * @param primary_image destination of primary image
-     * @param recovery_map destination of compressed recovery map
+     * @param gain_map destination of compressed gain map
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
     */
-    status_t extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
-                                               jr_compressed_ptr primary_image,
-                                               jr_compressed_ptr recovery_map);
+    status_t extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr_image,
+                                           jr_compressed_ptr primary_image,
+                                           jr_compressed_ptr gain_map);
     /*
      * This method is called in the decoding pipeline. It will read XMP metadata to find the start
-     * position of the compressed recovery map, and will extract the compressed recovery map.
+     * position of the compressed gain map, and will extract the compressed gain map.
      *
      * @param compressed_jpegr_image compressed JPEGR image
-     * @param dest destination of compressed recovery map
+     * @param dest destination of compressed gain map
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
-    status_t extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
-                                jr_compressed_ptr dest);
+    status_t extractGainMap(jr_compressed_ptr compressed_jpegr_image,
+                            jr_compressed_ptr dest);
 
     /*
      * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image,
-     * the compressed recovery map and optionally the exif package as inputs, and generate the XMP
+     * the compressed gain map and optionally the exif package as inputs, and generate the XMP
      * metadata, and finally append everything in the order of:
-     *     SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, recovery map
+     *     SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, gain map
      * Note that EXIF package is only available for encoding API-0 and API-1. For encoding API-2 and
      * API-3 this parameter is null, but the primary image in JPEG/R may still have EXIF as long as
      * the input JPEG has EXIF.
      *
      * @param compressed_jpeg_image compressed 8-bit JPEG image
-     * @param compress_recovery_map compressed recover map
+     * @param compress_gain_map compressed recover map
      * @param (nullable) exif EXIF package
      * @param metadata JPEG/R metadata to encode in XMP of the jpeg
      * @param dest compressed JPEGR image
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
-    status_t appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
-                               jr_compressed_ptr compressed_recovery_map,
-                               jr_exif_ptr exif,
-                               jr_metadata_ptr metadata,
-                               jr_compressed_ptr dest);
+    status_t appendGainMap(jr_compressed_ptr compressed_jpeg_image,
+                           jr_compressed_ptr compressed_gain_map,
+                           jr_exif_ptr exif,
+                           jr_metadata_ptr metadata,
+                           jr_compressed_ptr dest);
 
     /*
      * This method will tone map a HDR image to an SDR image.
@@ -358,6 +377,16 @@
      */
     status_t toneMap(jr_uncompressed_ptr src,
                      jr_uncompressed_ptr dest);
+
+    /*
+     * This method will check the validity of the input images.
+     *
+     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+     * @return NO_ERROR if the input images are valid, error code is not valid.
+     */
+    status_t areInputImagesValid(jr_uncompressed_ptr uncompressed_p010_image,
+                                 jr_uncompressed_ptr uncompressed_yuv_420_image);
 };
 
 } // namespace android::jpegrecoverymap
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
index f730343..159aaa8 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
@@ -46,6 +46,8 @@
     ERROR_JPEGR_CALCULATION_ERROR       = JPEGR_RUNTIME_ERROR_BASE - 3,
     ERROR_JPEGR_METADATA_ERROR          = JPEGR_RUNTIME_ERROR_BASE - 4,
     ERROR_JPEGR_TONEMAP_ERROR           = JPEGR_RUNTIME_ERROR_BASE - 5,
+
+    ERROR_JPEGR_UNSUPPORTED_FEATURE     = -20000,
 };
 
 }  // namespace android::jpegrecoverymap
diff --git a/libs/jpegrecoverymap/jpegr.cpp b/libs/jpegrecoverymap/jpegr.cpp
index e395d51..2590f63 100644
--- a/libs/jpegrecoverymap/jpegr.cpp
+++ b/libs/jpegrecoverymap/jpegr.cpp
@@ -17,7 +17,7 @@
 #include <jpegrecoverymap/jpegr.h>
 #include <jpegrecoverymap/jpegencoderhelper.h>
 #include <jpegrecoverymap/jpegdecoderhelper.h>
-#include <jpegrecoverymap/recoverymapmath.h>
+#include <jpegrecoverymap/gainmapmath.h>
 #include <jpegrecoverymap/jpegrutils.h>
 #include <jpegrecoverymap/multipictureformat.h>
 #include <jpegrecoverymap/icc.h>
@@ -50,7 +50,7 @@
 #define USE_PQ_OETF_LUT 1
 #define USE_HLG_INVOETF_LUT 1
 #define USE_PQ_INVOETF_LUT 1
-#define USE_APPLY_RECOVERY_LUT 1
+#define USE_APPLY_GAIN_LUT 1
 
 #define JPEGR_CHECK(x)          \
   {                             \
@@ -69,7 +69,7 @@
 // JPEG encoding / decoding will require 8 x 8 DCT transform.
 // Width must be 8 dividable, and height must be 2 dividable.
 static const size_t kJpegBlock = 8;
-// JPEG compress quality (0 ~ 100) for recovery map
+// JPEG compress quality (0 ~ 100) for gain map
 static const int kMapCompressQuality = 85;
 
 #define CONFIG_MULTITHREAD 1
@@ -86,6 +86,54 @@
   return cpuCoreCount;
 }
 
+status_t JpegR::areInputImagesValid(jr_uncompressed_ptr uncompressed_p010_image,
+                                    jr_uncompressed_ptr uncompressed_yuv_420_image) {
+  if (uncompressed_p010_image == nullptr) {
+    return ERROR_JPEGR_INVALID_NULL_PTR;
+  }
+
+  if (uncompressed_p010_image->width % kJpegBlock != 0
+          || uncompressed_p010_image->height % 2 != 0) {
+    ALOGE("Image size can not be handled: %dx%d.",
+            uncompressed_p010_image->width, uncompressed_p010_image->height);
+    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  }
+
+  if (uncompressed_p010_image->luma_stride != 0
+          && uncompressed_p010_image->luma_stride < uncompressed_p010_image->width) {
+    ALOGE("Image stride can not be smaller than width, stride=%d, width=%d",
+                uncompressed_p010_image->luma_stride, uncompressed_p010_image->width);
+    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  }
+
+  if (uncompressed_yuv_420_image == nullptr) {
+    return NO_ERROR;
+  }
+
+  if (uncompressed_yuv_420_image->luma_stride != 0) {
+    ALOGE("Stride is not supported for YUV420 image");
+    return ERROR_JPEGR_UNSUPPORTED_FEATURE;
+  }
+
+  if (uncompressed_yuv_420_image->chroma_data != nullptr) {
+    ALOGE("Pointer to chroma plane is not supported for YUV420 image, chroma data must"
+          "be immediately after the luma data.");
+    return ERROR_JPEGR_UNSUPPORTED_FEATURE;
+  }
+
+  if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
+      || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
+    ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d",
+              uncompressed_p010_image->width,
+              uncompressed_p010_image->height,
+              uncompressed_yuv_420_image->width,
+              uncompressed_yuv_420_image->height);
+    return ERROR_JPEGR_RESOLUTION_MISMATCH;
+  }
+
+  return NO_ERROR;
+}
+
 /* Encode API-0 */
 status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                             jpegr_transfer_function hdr_tf,
@@ -100,11 +148,9 @@
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
 
-  if (uncompressed_p010_image->width % kJpegBlock != 0
-          || uncompressed_p010_image->height % 2 != 0) {
-    ALOGE("Image size can not be handled: %dx%d",
-            uncompressed_p010_image->width, uncompressed_p010_image->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  if (status_t ret = areInputImagesValid(
+          uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr) != NO_ERROR) {
+    return ret;
   }
 
   jpegr_metadata_struct metadata;
@@ -117,7 +163,7 @@
   JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image));
 
   jpegr_uncompressed_struct map;
-  JPEGR_CHECK(generateRecoveryMap(
+  JPEGR_CHECK(generateGainMap(
       &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(map.data));
@@ -126,7 +172,7 @@
   compressed_map.maxLength = map.width * map.height;
   unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
   compressed_map.data = compressed_map_data.get();
-  JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
+  JPEGR_CHECK(compressGainMap(&map, &compressed_map));
 
   sp<DataStruct> icc = IccHelper::writeIccProfile(JPEGR_TF_SRGB,
                                                   uncompressed_yuv_420_image.colorGamut);
@@ -142,7 +188,7 @@
   jpeg.data = jpeg_encoder.getCompressedImagePtr();
   jpeg.length = jpeg_encoder.getCompressedImageSize();
 
-  JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, exif, &metadata, dest));
+  JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, &metadata, dest));
 
   return NO_ERROR;
 }
@@ -164,23 +210,16 @@
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
 
-  if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
-   || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
-    return ERROR_JPEGR_RESOLUTION_MISMATCH;
-  }
-
-  if (uncompressed_p010_image->width % kJpegBlock != 0
-          || uncompressed_p010_image->height % 2 != 0) {
-    ALOGE("Image size can not be handled: %dx%d",
-            uncompressed_p010_image->width, uncompressed_p010_image->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  if (status_t ret = areInputImagesValid(
+          uncompressed_p010_image, uncompressed_yuv_420_image) != NO_ERROR) {
+    return ret;
   }
 
   jpegr_metadata_struct metadata;
   metadata.version = kJpegrVersion;
 
   jpegr_uncompressed_struct map;
-  JPEGR_CHECK(generateRecoveryMap(
+  JPEGR_CHECK(generateGainMap(
       uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(map.data));
@@ -189,7 +228,7 @@
   compressed_map.maxLength = map.width * map.height;
   unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
   compressed_map.data = compressed_map_data.get();
-  JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
+  JPEGR_CHECK(compressGainMap(&map, &compressed_map));
 
   sp<DataStruct> icc = IccHelper::writeIccProfile(JPEGR_TF_SRGB,
                                                   uncompressed_yuv_420_image->colorGamut);
@@ -205,7 +244,7 @@
   jpeg.data = jpeg_encoder.getCompressedImagePtr();
   jpeg.length = jpeg_encoder.getCompressedImageSize();
 
-  JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, exif, &metadata, dest));
+  JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, &metadata, dest));
 
   return NO_ERROR;
 }
@@ -223,23 +262,16 @@
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
-   || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
-    return ERROR_JPEGR_RESOLUTION_MISMATCH;
-  }
-
-  if (uncompressed_p010_image->width % kJpegBlock != 0
-          || uncompressed_p010_image->height % 2 != 0) {
-    ALOGE("Image size can not be handled: %dx%d",
-            uncompressed_p010_image->width, uncompressed_p010_image->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  if (status_t ret = areInputImagesValid(
+          uncompressed_p010_image, uncompressed_yuv_420_image) != NO_ERROR) {
+    return ret;
   }
 
   jpegr_metadata_struct metadata;
   metadata.version = kJpegrVersion;
 
   jpegr_uncompressed_struct map;
-  JPEGR_CHECK(generateRecoveryMap(
+  JPEGR_CHECK(generateGainMap(
       uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(map.data));
@@ -248,9 +280,9 @@
   compressed_map.maxLength = map.width * map.height;
   unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
   compressed_map.data = compressed_map_data.get();
-  JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
+  JPEGR_CHECK(compressGainMap(&map, &compressed_map));
 
-  JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
+  JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
 
   return NO_ERROR;
 }
@@ -266,11 +298,9 @@
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  if (uncompressed_p010_image->width % kJpegBlock != 0
-          || uncompressed_p010_image->height % 2 != 0) {
-    ALOGE("Image size can not be handled: %dx%d",
-            uncompressed_p010_image->width, uncompressed_p010_image->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  if (status_t ret = areInputImagesValid(
+          uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr) != NO_ERROR) {
+    return ret;
   }
 
   JpegDecoderHelper jpeg_decoder;
@@ -292,7 +322,7 @@
   metadata.version = kJpegrVersion;
 
   jpegr_uncompressed_struct map;
-  JPEGR_CHECK(generateRecoveryMap(
+  JPEGR_CHECK(generateGainMap(
       &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(map.data));
@@ -301,9 +331,9 @@
   compressed_map.maxLength = map.width * map.height;
   unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
   compressed_map.data = compressed_map_data.get();
-  JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
+  JPEGR_CHECK(compressGainMap(&map, &compressed_map));
 
-  JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
+  JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
 
   return NO_ERROR;
 }
@@ -313,9 +343,9 @@
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  jpegr_compressed_struct primary_image, recovery_map;
-  JPEGR_CHECK(extractPrimaryImageAndRecoveryMap(compressed_jpegr_image,
-                                                &primary_image, &recovery_map));
+  jpegr_compressed_struct primary_image, gain_map;
+  JPEGR_CHECK(extractPrimaryImageAndGainMap(compressed_jpegr_image,
+                                            &primary_image, &gain_map));
 
   JpegDecoderHelper jpeg_decoder;
   if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
@@ -333,12 +363,16 @@
                             float max_display_boost,
                             jr_exif_ptr exif,
                             jpegr_output_format output_format,
-                            jr_uncompressed_ptr recovery_map,
+                            jr_uncompressed_ptr gain_map,
                             jr_metadata_ptr metadata) {
   if (compressed_jpegr_image == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
+  if (max_display_boost < 1.0f) {
+      return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  }
+
   if (output_format == JPEGR_OUTPUT_SDR) {
     JpegDecoderHelper jpeg_decoder;
     if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length,
@@ -354,7 +388,7 @@
     dest->width = uncompressed_rgba_image.width;
     dest->height = uncompressed_rgba_image.height;
 
-    if (recovery_map == nullptr && exif == nullptr) {
+    if (gain_map == nullptr && exif == nullptr) {
       return NO_ERROR;
     }
 
@@ -368,30 +402,30 @@
       memcpy(exif->data, jpeg_decoder.getEXIFPtr(), jpeg_decoder.getEXIFSize());
       exif->length = jpeg_decoder.getEXIFSize();
     }
-    if (recovery_map == nullptr) {
+    if (gain_map == nullptr) {
       return NO_ERROR;
     }
   }
 
   jpegr_compressed_struct compressed_map;
-  JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
+  JPEGR_CHECK(extractGainMap(compressed_jpegr_image, &compressed_map));
 
-  JpegDecoderHelper recovery_map_decoder;
-  if (!recovery_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) {
+  JpegDecoderHelper gain_map_decoder;
+  if (!gain_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
 
-  if (recovery_map != nullptr) {
-    recovery_map->width = recovery_map_decoder.getDecompressedImageWidth();
-    recovery_map->height = recovery_map_decoder.getDecompressedImageHeight();
-    int size = recovery_map->width * recovery_map->height;
-    recovery_map->data = malloc(size);
-    memcpy(recovery_map->data, recovery_map_decoder.getDecompressedImagePtr(), size);
+  if (gain_map != nullptr) {
+    gain_map->width = gain_map_decoder.getDecompressedImageWidth();
+    gain_map->height = gain_map_decoder.getDecompressedImageHeight();
+    int size = gain_map->width * gain_map->height;
+    gain_map->data = malloc(size);
+    memcpy(gain_map->data, gain_map_decoder.getDecompressedImagePtr(), size);
   }
 
   jpegr_metadata_struct jr_metadata;
-  if (!getMetadataFromXMP(static_cast<uint8_t*>(recovery_map_decoder.getXMPPtr()),
-                          recovery_map_decoder.getXMPSize(), &jr_metadata)) {
+  if (!getMetadataFromXMP(static_cast<uint8_t*>(gain_map_decoder.getXMPPtr()),
+                          gain_map_decoder.getXMPSize(), &jr_metadata)) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
 
@@ -422,30 +456,30 @@
   }
 
   jpegr_uncompressed_struct map;
-  map.data = recovery_map_decoder.getDecompressedImagePtr();
-  map.width = recovery_map_decoder.getDecompressedImageWidth();
-  map.height = recovery_map_decoder.getDecompressedImageHeight();
+  map.data = gain_map_decoder.getDecompressedImagePtr();
+  map.width = gain_map_decoder.getDecompressedImageWidth();
+  map.height = gain_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();
 
-  JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &jr_metadata, output_format,
-                               max_display_boost, dest));
+  JPEGR_CHECK(applyGainMap(&uncompressed_yuv_420_image, &map, &jr_metadata, output_format,
+                           max_display_boost, dest));
   return NO_ERROR;
 }
 
-status_t JpegR::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
-                                    jr_compressed_ptr dest) {
-  if (uncompressed_recovery_map == nullptr || dest == nullptr) {
+status_t JpegR::compressGainMap(jr_uncompressed_ptr uncompressed_gain_map,
+                                jr_compressed_ptr dest) {
+  if (uncompressed_gain_map == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
   JpegEncoderHelper jpeg_encoder;
-  if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data,
-                                  uncompressed_recovery_map->width,
-                                  uncompressed_recovery_map->height,
+  if (!jpeg_encoder.compressImage(uncompressed_gain_map->data,
+                                  uncompressed_gain_map->width,
+                                  uncompressed_gain_map->height,
                                   kMapCompressQuality,
                                   nullptr,
                                   0,
@@ -520,11 +554,11 @@
   mQueuedAllJobs = false;
 }
 
-status_t JpegR::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
-                                    jr_uncompressed_ptr uncompressed_p010_image,
-                                    jpegr_transfer_function hdr_tf,
-                                    jr_metadata_ptr metadata,
-                                    jr_uncompressed_ptr dest) {
+status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+                                jr_uncompressed_ptr uncompressed_p010_image,
+                                jpegr_transfer_function hdr_tf,
+                                jr_metadata_ptr metadata,
+                                jr_uncompressed_ptr dest) {
   if (uncompressed_yuv_420_image == nullptr
    || uncompressed_p010_image == nullptr
    || metadata == nullptr
@@ -586,6 +620,8 @@
 
   metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
   metadata->minContentBoost = 1.0f;
+  float log2MinBoost = log2(metadata->minContentBoost);
+  float log2MaxBoost = log2(metadata->maxContentBoost);
 
   ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
       uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
@@ -613,7 +649,8 @@
 
   std::function<void()> generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image,
                                        metadata, dest, hdrInvOetf, hdrGamutConversionFn,
-                                       luminanceFn, hdr_white_nits, &jobQueue]() -> void {
+                                       luminanceFn, hdr_white_nits, log2MinBoost, log2MaxBoost,
+                                       &jobQueue]() -> void {
     size_t rowStart, rowEnd;
     size_t dest_map_width = uncompressed_yuv_420_image->width / kMapDimensionScaleFactor;
     size_t dest_map_stride = dest->width;
@@ -638,7 +675,7 @@
 
           size_t pixel_idx = x + y * dest_map_stride;
           reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
-              encodeRecovery(sdr_y_nits, hdr_y_nits, metadata);
+              encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
         }
       }
     }
@@ -664,14 +701,14 @@
   return NO_ERROR;
 }
 
-status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
-                                 jr_uncompressed_ptr uncompressed_recovery_map,
-                                 jr_metadata_ptr metadata,
-                                 jpegr_output_format output_format,
-                                 float max_display_boost,
-                                 jr_uncompressed_ptr dest) {
+status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+                             jr_uncompressed_ptr uncompressed_gain_map,
+                             jr_metadata_ptr metadata,
+                             jpegr_output_format output_format,
+                             float max_display_boost,
+                             jr_uncompressed_ptr dest) {
   if (uncompressed_yuv_420_image == nullptr
-   || uncompressed_recovery_map == nullptr
+   || uncompressed_gain_map == nullptr
    || metadata == nullptr
    || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
@@ -680,15 +717,13 @@
   dest->width = uncompressed_yuv_420_image->width;
   dest->height = uncompressed_yuv_420_image->height;
   ShepardsIDW idwTable(kMapDimensionScaleFactor);
-  float display_boost = max_display_boost > 0 ?
-          std::min(max_display_boost, metadata->maxContentBoost)
-          : metadata->maxContentBoost;
-  RecoveryLUT recoveryLUT(metadata, display_boost);
+  float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
+  GainLUT gainLUT(metadata, display_boost);
 
   JobQueue jobQueue;
-  std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_recovery_map,
+  std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_gain_map,
                                        metadata, dest, &jobQueue, &idwTable, output_format,
-                                       &recoveryLUT, display_boost]() -> void {
+                                       &gainLUT, display_boost]() -> void {
     size_t width = uncompressed_yuv_420_image->width;
     size_t height = uncompressed_yuv_420_image->height;
 
@@ -703,22 +738,22 @@
 #else
           Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
 #endif
-          float recovery;
+          float gain;
           // TODO: determine map scaling factor based on actual map dims
           size_t map_scale_factor = kMapDimensionScaleFactor;
           // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
           // Currently map_scale_factor is of type size_t, but it could be changed to a float
           // later.
           if (map_scale_factor != floorf(map_scale_factor)) {
-            recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y);
+            gain = sampleMap(uncompressed_gain_map, map_scale_factor, x, y);
           } else {
-            recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y, idwTable);
+            gain = sampleMap(uncompressed_gain_map, map_scale_factor, x, y, idwTable);
           }
 
-#if USE_APPLY_RECOVERY_LUT
-          Color rgb_hdr = applyRecoveryLUT(rgb_sdr, recovery, recoveryLUT);
+#if USE_APPLY_GAIN_LUT
+          Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
 #else
-          Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata, display_boost);
+          Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
 #endif
           rgb_hdr = rgb_hdr / display_boost;
           size_t pixel_idx = x + y * width;
@@ -780,9 +815,9 @@
   return NO_ERROR;
 }
 
-status_t JpegR::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
-                                                  jr_compressed_ptr primary_image,
-                                                  jr_compressed_ptr recovery_map) {
+status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr_image,
+                                              jr_compressed_ptr primary_image,
+                                              jr_compressed_ptr gain_map) {
   if (compressed_jpegr_image == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
@@ -820,23 +855,23 @@
     primary_image->length = image_ranges[0].GetLength();
   }
 
-  if (recovery_map != nullptr) {
-    recovery_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
+  if (gain_map != nullptr) {
+    gain_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
                                               image_ranges[1].GetBegin();
-    recovery_map->length = image_ranges[1].GetLength();
+    gain_map->length = image_ranges[1].GetLength();
   }
 
   return NO_ERROR;
 }
 
 
-status_t JpegR::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
-                                   jr_compressed_ptr dest) {
+status_t JpegR::extractGainMap(jr_compressed_ptr compressed_jpegr_image,
+                               jr_compressed_ptr dest) {
   if (compressed_jpegr_image == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest);
+  return extractPrimaryImageAndGainMap(compressed_jpegr_image, nullptr, dest);
 }
 
 // JPEG/R structure:
@@ -865,20 +900,20 @@
 // name space ("http://ns.adobe.com/xap/1.0/\0")
 // XMP
 //
-// (Required) secondary image (the recovery map, without the first two bytes (SOI))
+// (Required) secondary image (the gain map, without the first two bytes (SOI))
 //
 // Metadata versions we are using:
 // ECMA TR-98 for JFIF marker
 // Exif 2.2 spec for EXIF marker
 // Adobe XMP spec part 3 for XMP marker
 // ICC v4.3 spec for ICC
-status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
-                                  jr_compressed_ptr compressed_recovery_map,
-                                  jr_exif_ptr exif,
-                                  jr_metadata_ptr metadata,
-                                  jr_compressed_ptr dest) {
+status_t JpegR::appendGainMap(jr_compressed_ptr compressed_jpeg_image,
+                              jr_compressed_ptr compressed_gain_map,
+                              jr_exif_ptr exif,
+                              jr_metadata_ptr metadata,
+                              jr_compressed_ptr dest) {
   if (compressed_jpeg_image == nullptr
-   || compressed_recovery_map == nullptr
+   || compressed_gain_map == nullptr
    || metadata == nullptr
    || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
@@ -895,7 +930,7 @@
                                  + xmp_secondary.size(); /* length of xmp packet */
   const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
                                  + xmp_secondary_length
-                                 + compressed_recovery_map->length;
+                                 + compressed_gain_map->length;
   // primary image
   const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size);
   // same as primary
@@ -959,7 +994,7 @@
       (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
   // Finish primary image
 
-  // Begin secondary image (recovery map)
+  // Begin secondary image (gain map)
   // Write SOI
   JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
   JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
@@ -979,7 +1014,7 @@
 
   // Write secondary image
   JPEGR_CHECK(Write(dest,
-        (uint8_t*)compressed_recovery_map->data + 2, compressed_recovery_map->length - 2, pos));
+        (uint8_t*)compressed_gain_map->data + 2, compressed_gain_map->length - 2, pos));
 
   // Set back length
   dest->length = pos;
@@ -993,25 +1028,43 @@
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
+  size_t src_luma_stride = src->luma_stride;
+  size_t src_chroma_stride = src->chroma_stride;
+  uint16_t* src_luma_data = reinterpret_cast<uint16_t*>(src->data);
+  uint16_t* src_chroma_data = reinterpret_cast<uint16_t*>(src->chroma_data);
+
+  if (src_chroma_data == nullptr) {
+    src_chroma_data = &reinterpret_cast<uint16_t*>(src->data)[src->luma_stride * src->height];
+  }
+  if (src_luma_stride == 0) {
+    src_luma_stride = src->width;
+  }
+  if (src_chroma_stride == 0) {
+    src_chroma_stride = src_luma_stride;
+  }
+
   dest->width = src->width;
   dest->height = src->height;
 
-  size_t pixel_count = src->width * src->height;
+  size_t dest_luma_pixel_count = dest->width * dest->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);
+      size_t src_y_idx = y * src_luma_stride + x;
+      size_t src_u_idx = (y >> 1) * src_chroma_stride + (x & ~0x1);
+      size_t src_v_idx = src_u_idx + 1;
 
-      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;
+      uint16_t y_uint = src_luma_data[src_y_idx] >> 6;
+      uint16_t u_uint = src_chroma_data[src_u_idx] >> 6;
+      uint16_t v_uint = src_chroma_data[src_v_idx] >> 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];
+      size_t dest_y_idx = x + y * dest->width;
+      size_t dest_uv_idx = x / 2 + (y / 2) * (dest->width / 2);
+
+      uint8_t* y = &reinterpret_cast<uint8_t*>(dest->data)[dest_y_idx];
+      uint8_t* u = &reinterpret_cast<uint8_t*>(dest->data)[dest_luma_pixel_count + dest_uv_idx];
+      uint8_t* v = &reinterpret_cast<uint8_t*>(
+              dest->data)[dest_luma_pixel_count * 5 / 4 + dest_uv_idx];
 
       *y = static_cast<uint8_t>((y_uint >> 2) & 0xff);
       *u = static_cast<uint8_t>((u_uint >> 2) & 0xff);
diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp
index d5da7fb..59b1237 100644
--- a/libs/jpegrecoverymap/tests/Android.bp
+++ b/libs/jpegrecoverymap/tests/Android.bp
@@ -26,7 +26,7 @@
     test_suites: ["device-tests"],
     srcs: [
         "jpegr_test.cpp",
-        "recoverymapmath_test.cpp",
+        "gainmapmath_test.cpp",
     ],
     shared_libs: [
         "libimage_io",
diff --git a/libs/jpegrecoverymap/tests/data/raw_p010_image_with_stride.p010 b/libs/jpegrecoverymap/tests/data/raw_p010_image_with_stride.p010
new file mode 100644
index 0000000..e7a5dc8
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/raw_p010_image_with_stride.p010
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/gainmapmath_test.cpp
similarity index 71%
rename from libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
rename to libs/jpegrecoverymap/tests/gainmapmath_test.cpp
index 5ef79e9..21de2e6 100644
--- a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
+++ b/libs/jpegrecoverymap/tests/gainmapmath_test.cpp
@@ -17,14 +17,14 @@
 #include <cmath>
 #include <gtest/gtest.h>
 #include <gmock/gmock.h>
-#include <jpegrecoverymap/recoverymapmath.h>
+#include <jpegrecoverymap/gainmapmath.h>
 
 namespace android::jpegrecoverymap {
 
-class RecoveryMapMathTest : public testing::Test {
+class GainMapMathTest : public testing::Test {
 public:
-  RecoveryMapMathTest();
-  ~RecoveryMapMathTest();
+  GainMapMathTest();
+  ~GainMapMathTest();
 
   float ComparisonEpsilon() { return 1e-4f; }
   float LuminanceEpsilon() { return 1e-2f; }
@@ -88,10 +88,10 @@
     return luminance_scaled * scale_factor;
   }
 
-  Color Recover(Color yuv_gamma, float recovery, jr_metadata_ptr metadata) {
+  Color Recover(Color yuv_gamma, float gain, jr_metadata_ptr metadata) {
     Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
     Color rgb = srgbInvOetf(rgb_gamma);
-    return applyRecovery(rgb, recovery, metadata);
+    return applyGain(rgb, gain, metadata);
   }
 
   jpegr_uncompressed_struct Yuv420Image() {
@@ -193,11 +193,11 @@
   virtual void TearDown();
 };
 
-RecoveryMapMathTest::RecoveryMapMathTest() {}
-RecoveryMapMathTest::~RecoveryMapMathTest() {}
+GainMapMathTest::GainMapMathTest() {}
+GainMapMathTest::~GainMapMathTest() {}
 
-void RecoveryMapMathTest::SetUp() {}
-void RecoveryMapMathTest::TearDown() {}
+void GainMapMathTest::SetUp() {}
+void GainMapMathTest::TearDown() {}
 
 #define EXPECT_RGB_EQ(e1, e2)       \
   EXPECT_FLOAT_EQ((e1).r, (e2).r);  \
@@ -231,7 +231,7 @@
 
 // TODO: a bunch of these tests can be parameterized.
 
-TEST_F(RecoveryMapMathTest, ColorConstruct) {
+TEST_F(GainMapMathTest, ColorConstruct) {
   Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
 
   EXPECT_FLOAT_EQ(e1.r, 0.1f);
@@ -243,7 +243,7 @@
   EXPECT_FLOAT_EQ(e1.v, 0.3f);
 }
 
-TEST_F(RecoveryMapMathTest, ColorAddColor) {
+TEST_F(GainMapMathTest, ColorAddColor) {
   Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
 
   Color e2 = e1 + e1;
@@ -257,7 +257,7 @@
   EXPECT_FLOAT_EQ(e2.b, e1.b * 3.0f);
 }
 
-TEST_F(RecoveryMapMathTest, ColorAddFloat) {
+TEST_F(GainMapMathTest, ColorAddFloat) {
   Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
 
   Color e2 = e1 + 0.1f;
@@ -271,7 +271,7 @@
   EXPECT_FLOAT_EQ(e2.b, e1.b + 0.2f);
 }
 
-TEST_F(RecoveryMapMathTest, ColorSubtractColor) {
+TEST_F(GainMapMathTest, ColorSubtractColor) {
   Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
 
   Color e2 = e1 - e1;
@@ -285,7 +285,7 @@
   EXPECT_FLOAT_EQ(e2.b, -e1.b);
 }
 
-TEST_F(RecoveryMapMathTest, ColorSubtractFloat) {
+TEST_F(GainMapMathTest, ColorSubtractFloat) {
   Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
 
   Color e2 = e1 - 0.1f;
@@ -299,7 +299,7 @@
   EXPECT_FLOAT_EQ(e2.b, e1.b - 0.2f);
 }
 
-TEST_F(RecoveryMapMathTest, ColorMultiplyFloat) {
+TEST_F(GainMapMathTest, ColorMultiplyFloat) {
   Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
 
   Color e2 = e1 * 2.0f;
@@ -313,7 +313,7 @@
   EXPECT_FLOAT_EQ(e2.b, e1.b * 4.0f);
 }
 
-TEST_F(RecoveryMapMathTest, ColorDivideFloat) {
+TEST_F(GainMapMathTest, ColorDivideFloat) {
   Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
 
   Color e2 = e1 / 2.0f;
@@ -327,7 +327,7 @@
   EXPECT_FLOAT_EQ(e2.b, e1.b / 4.0f);
 }
 
-TEST_F(RecoveryMapMathTest, SrgbLuminance) {
+TEST_F(GainMapMathTest, SrgbLuminance) {
   EXPECT_FLOAT_EQ(srgbLuminance(RgbBlack()), 0.0f);
   EXPECT_FLOAT_EQ(srgbLuminance(RgbWhite()), 1.0f);
   EXPECT_FLOAT_EQ(srgbLuminance(RgbRed()), 0.2126f);
@@ -335,7 +335,7 @@
   EXPECT_FLOAT_EQ(srgbLuminance(RgbBlue()), 0.0722f);
 }
 
-TEST_F(RecoveryMapMathTest, SrgbYuvToRgb) {
+TEST_F(GainMapMathTest, SrgbYuvToRgb) {
   Color rgb_black = srgbYuvToRgb(YuvBlack());
   EXPECT_RGB_NEAR(rgb_black, RgbBlack());
 
@@ -352,7 +352,7 @@
   EXPECT_RGB_NEAR(rgb_b, RgbBlue());
 }
 
-TEST_F(RecoveryMapMathTest, SrgbRgbToYuv) {
+TEST_F(GainMapMathTest, SrgbRgbToYuv) {
   Color yuv_black = srgbRgbToYuv(RgbBlack());
   EXPECT_YUV_NEAR(yuv_black, YuvBlack());
 
@@ -369,7 +369,7 @@
   EXPECT_YUV_NEAR(yuv_b, SrgbYuvBlue());
 }
 
-TEST_F(RecoveryMapMathTest, SrgbRgbYuvRoundtrip) {
+TEST_F(GainMapMathTest, SrgbRgbYuvRoundtrip) {
   Color rgb_black = srgbYuvToRgb(srgbRgbToYuv(RgbBlack()));
   EXPECT_RGB_NEAR(rgb_black, RgbBlack());
 
@@ -386,7 +386,7 @@
   EXPECT_RGB_NEAR(rgb_b, RgbBlue());
 }
 
-TEST_F(RecoveryMapMathTest, SrgbTransferFunction) {
+TEST_F(GainMapMathTest, SrgbTransferFunction) {
   EXPECT_FLOAT_EQ(srgbInvOetf(0.0f), 0.0f);
   EXPECT_NEAR(srgbInvOetf(0.02f), 0.00154f, ComparisonEpsilon());
   EXPECT_NEAR(srgbInvOetf(0.04045f), 0.00313f, ComparisonEpsilon());
@@ -394,7 +394,7 @@
   EXPECT_FLOAT_EQ(srgbInvOetf(1.0f), 1.0f);
 }
 
-TEST_F(RecoveryMapMathTest, P3Luminance) {
+TEST_F(GainMapMathTest, P3Luminance) {
   EXPECT_FLOAT_EQ(p3Luminance(RgbBlack()), 0.0f);
   EXPECT_FLOAT_EQ(p3Luminance(RgbWhite()), 1.0f);
   EXPECT_FLOAT_EQ(p3Luminance(RgbRed()), 0.20949f);
@@ -402,7 +402,7 @@
   EXPECT_FLOAT_EQ(p3Luminance(RgbBlue()), 0.06891f);
 }
 
-TEST_F(RecoveryMapMathTest, Bt2100Luminance) {
+TEST_F(GainMapMathTest, Bt2100Luminance) {
   EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlack()), 0.0f);
   EXPECT_FLOAT_EQ(bt2100Luminance(RgbWhite()), 1.0f);
   EXPECT_FLOAT_EQ(bt2100Luminance(RgbRed()), 0.2627f);
@@ -410,7 +410,7 @@
   EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlue()), 0.0593f);
 }
 
-TEST_F(RecoveryMapMathTest, Bt2100YuvToRgb) {
+TEST_F(GainMapMathTest, Bt2100YuvToRgb) {
   Color rgb_black = bt2100YuvToRgb(YuvBlack());
   EXPECT_RGB_NEAR(rgb_black, RgbBlack());
 
@@ -427,7 +427,7 @@
   EXPECT_RGB_NEAR(rgb_b, RgbBlue());
 }
 
-TEST_F(RecoveryMapMathTest, Bt2100RgbToYuv) {
+TEST_F(GainMapMathTest, Bt2100RgbToYuv) {
   Color yuv_black = bt2100RgbToYuv(RgbBlack());
   EXPECT_YUV_NEAR(yuv_black, YuvBlack());
 
@@ -444,7 +444,7 @@
   EXPECT_YUV_NEAR(yuv_b, Bt2100YuvBlue());
 }
 
-TEST_F(RecoveryMapMathTest, Bt2100RgbYuvRoundtrip) {
+TEST_F(GainMapMathTest, Bt2100RgbYuvRoundtrip) {
   Color rgb_black = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlack()));
   EXPECT_RGB_NEAR(rgb_black, RgbBlack());
 
@@ -461,7 +461,7 @@
   EXPECT_RGB_NEAR(rgb_b, RgbBlue());
 }
 
-TEST_F(RecoveryMapMathTest, HlgOetf) {
+TEST_F(GainMapMathTest, HlgOetf) {
   EXPECT_FLOAT_EQ(hlgOetf(0.0f), 0.0f);
   EXPECT_NEAR(hlgOetf(0.04167f), 0.35357f, ComparisonEpsilon());
   EXPECT_NEAR(hlgOetf(0.08333f), 0.5f, ComparisonEpsilon());
@@ -473,7 +473,7 @@
   EXPECT_RGB_NEAR(hlgOetf(e), e_gamma);
 }
 
-TEST_F(RecoveryMapMathTest, HlgInvOetf) {
+TEST_F(GainMapMathTest, HlgInvOetf) {
   EXPECT_FLOAT_EQ(hlgInvOetf(0.0f), 0.0f);
   EXPECT_NEAR(hlgInvOetf(0.25f), 0.02083f, ComparisonEpsilon());
   EXPECT_NEAR(hlgInvOetf(0.5f), 0.08333f, ComparisonEpsilon());
@@ -485,7 +485,7 @@
   EXPECT_RGB_NEAR(hlgInvOetf(e_gamma), e);
 }
 
-TEST_F(RecoveryMapMathTest, HlgTransferFunctionRoundtrip) {
+TEST_F(GainMapMathTest, HlgTransferFunctionRoundtrip) {
   EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(0.0f)), 0.0f);
   EXPECT_NEAR(hlgInvOetf(hlgOetf(0.04167f)), 0.04167f, ComparisonEpsilon());
   EXPECT_NEAR(hlgInvOetf(hlgOetf(0.08333f)), 0.08333f, ComparisonEpsilon());
@@ -493,7 +493,7 @@
   EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(1.0f)), 1.0f);
 }
 
-TEST_F(RecoveryMapMathTest, PqOetf) {
+TEST_F(GainMapMathTest, PqOetf) {
   EXPECT_FLOAT_EQ(pqOetf(0.0f), 0.0f);
   EXPECT_NEAR(pqOetf(0.01f), 0.50808f, ComparisonEpsilon());
   EXPECT_NEAR(pqOetf(0.5f), 0.92655f, ComparisonEpsilon());
@@ -505,7 +505,7 @@
   EXPECT_RGB_NEAR(pqOetf(e), e_gamma);
 }
 
-TEST_F(RecoveryMapMathTest, PqInvOetf) {
+TEST_F(GainMapMathTest, PqInvOetf) {
   EXPECT_FLOAT_EQ(pqInvOetf(0.0f), 0.0f);
   EXPECT_NEAR(pqInvOetf(0.01f), 2.31017e-7f, ComparisonEpsilon());
   EXPECT_NEAR(pqInvOetf(0.5f), 0.00922f, ComparisonEpsilon());
@@ -517,99 +517,99 @@
   EXPECT_RGB_NEAR(pqInvOetf(e_gamma), e);
 }
 
-TEST_F(RecoveryMapMathTest, PqInvOetfLUT) {
+TEST_F(GainMapMathTest, PqInvOetfLUT) {
     for (int idx = 0; idx < kPqInvOETFNumEntries; idx++) {
       float value = static_cast<float>(idx) / static_cast<float>(kPqInvOETFNumEntries - 1);
       EXPECT_FLOAT_EQ(pqInvOetf(value), pqInvOetfLUT(value));
     }
 }
 
-TEST_F(RecoveryMapMathTest, HlgInvOetfLUT) {
+TEST_F(GainMapMathTest, HlgInvOetfLUT) {
     for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++) {
       float value = static_cast<float>(idx) / static_cast<float>(kHlgInvOETFNumEntries - 1);
       EXPECT_FLOAT_EQ(hlgInvOetf(value), hlgInvOetfLUT(value));
     }
 }
 
-TEST_F(RecoveryMapMathTest, pqOetfLUT) {
+TEST_F(GainMapMathTest, pqOetfLUT) {
     for (int idx = 0; idx < kPqOETFNumEntries; idx++) {
       float value = static_cast<float>(idx) / static_cast<float>(kPqOETFNumEntries - 1);
       EXPECT_FLOAT_EQ(pqOetf(value), pqOetfLUT(value));
     }
 }
 
-TEST_F(RecoveryMapMathTest, hlgOetfLUT) {
+TEST_F(GainMapMathTest, hlgOetfLUT) {
     for (int idx = 0; idx < kHlgOETFNumEntries; idx++) {
       float value = static_cast<float>(idx) / static_cast<float>(kHlgOETFNumEntries - 1);
       EXPECT_FLOAT_EQ(hlgOetf(value), hlgOetfLUT(value));
     }
 }
 
-TEST_F(RecoveryMapMathTest, srgbInvOetfLUT) {
+TEST_F(GainMapMathTest, srgbInvOetfLUT) {
     for (int idx = 0; idx < kSrgbInvOETFNumEntries; idx++) {
       float value = static_cast<float>(idx) / static_cast<float>(kSrgbInvOETFNumEntries - 1);
       EXPECT_FLOAT_EQ(srgbInvOetf(value), srgbInvOetfLUT(value));
     }
 }
 
-TEST_F(RecoveryMapMathTest, applyRecoveryLUT) {
+TEST_F(GainMapMathTest, applyGainLUT) {
   for (int boost = 1; boost <= 10; boost++) {
     jpegr_metadata_struct metadata = { .maxContentBoost = static_cast<float>(boost),
                                        .minContentBoost = 1.0f / static_cast<float>(boost) };
-    RecoveryLUT recoveryLUT(&metadata);
-    RecoveryLUT recoveryLUTWithBoost(&metadata, metadata.maxContentBoost);
-    for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kRecoveryFactorNumEntries - 1);
-      EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata),
-                      applyRecoveryLUT(RgbBlack(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, &metadata),
-                      applyRecoveryLUT(RgbWhite(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, &metadata),
-                      applyRecoveryLUT(RgbRed(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, &metadata),
-                      applyRecoveryLUT(RgbGreen(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata),
-                      applyRecoveryLUT(RgbBlue(), value, recoveryLUT));
-      EXPECT_RGB_EQ(applyRecoveryLUT(RgbBlack(), value, recoveryLUT),
-                    applyRecoveryLUT(RgbBlack(), value, recoveryLUTWithBoost));
-      EXPECT_RGB_EQ(applyRecoveryLUT(RgbWhite(), value, recoveryLUT),
-                    applyRecoveryLUT(RgbWhite(), value, recoveryLUTWithBoost));
-      EXPECT_RGB_EQ(applyRecoveryLUT(RgbRed(), value, recoveryLUT),
-                    applyRecoveryLUT(RgbRed(), value, recoveryLUTWithBoost));
-      EXPECT_RGB_EQ(applyRecoveryLUT(RgbGreen(), value, recoveryLUT),
-                    applyRecoveryLUT(RgbGreen(), value, recoveryLUTWithBoost));
-      EXPECT_RGB_EQ(applyRecoveryLUT(RgbBlue(), value, recoveryLUT),
-                    applyRecoveryLUT(RgbBlue(), value, recoveryLUTWithBoost));
+    GainLUT gainLUT(&metadata);
+    GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost);
+    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
+      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
+      EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata),
+                      applyGainLUT(RgbBlack(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata),
+                      applyGainLUT(RgbWhite(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata),
+                      applyGainLUT(RgbRed(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata),
+                      applyGainLUT(RgbGreen(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata),
+                      applyGainLUT(RgbBlue(), value, gainLUT));
+      EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT),
+                    applyGainLUT(RgbBlack(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT),
+                    applyGainLUT(RgbWhite(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT),
+                    applyGainLUT(RgbRed(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT),
+                    applyGainLUT(RgbGreen(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT),
+                    applyGainLUT(RgbBlue(), value, gainLUTWithBoost));
     }
   }
 
   for (int boost = 1; boost <= 10; boost++) {
     jpegr_metadata_struct metadata = { .maxContentBoost = static_cast<float>(boost),
                                        .minContentBoost = 1.0f };
-    RecoveryLUT recoveryLUT(&metadata);
-    RecoveryLUT recoveryLUTWithBoost(&metadata, metadata.maxContentBoost);
-    for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kRecoveryFactorNumEntries - 1);
-      EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata),
-                      applyRecoveryLUT(RgbBlack(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, &metadata),
-                      applyRecoveryLUT(RgbWhite(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, &metadata),
-                      applyRecoveryLUT(RgbRed(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, &metadata),
-                      applyRecoveryLUT(RgbGreen(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata),
-                      applyRecoveryLUT(RgbBlue(), value, recoveryLUT));
-      EXPECT_RGB_EQ(applyRecoveryLUT(RgbBlack(), value, recoveryLUT),
-                    applyRecoveryLUT(RgbBlack(), value, recoveryLUTWithBoost));
-      EXPECT_RGB_EQ(applyRecoveryLUT(RgbWhite(), value, recoveryLUT),
-                    applyRecoveryLUT(RgbWhite(), value, recoveryLUTWithBoost));
-      EXPECT_RGB_EQ(applyRecoveryLUT(RgbRed(), value, recoveryLUT),
-                    applyRecoveryLUT(RgbRed(), value, recoveryLUTWithBoost));
-      EXPECT_RGB_EQ(applyRecoveryLUT(RgbGreen(), value, recoveryLUT),
-                    applyRecoveryLUT(RgbGreen(), value, recoveryLUTWithBoost));
-      EXPECT_RGB_EQ(applyRecoveryLUT(RgbBlue(), value, recoveryLUT),
-                    applyRecoveryLUT(RgbBlue(), value, recoveryLUTWithBoost));
+    GainLUT gainLUT(&metadata);
+    GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost);
+    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
+      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
+      EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata),
+                      applyGainLUT(RgbBlack(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata),
+                      applyGainLUT(RgbWhite(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata),
+                      applyGainLUT(RgbRed(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata),
+                      applyGainLUT(RgbGreen(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata),
+                      applyGainLUT(RgbBlue(), value, gainLUT));
+      EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT),
+                    applyGainLUT(RgbBlack(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT),
+                    applyGainLUT(RgbWhite(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT),
+                    applyGainLUT(RgbRed(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT),
+                    applyGainLUT(RgbGreen(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT),
+                    applyGainLUT(RgbBlue(), value, gainLUTWithBoost));
     }
   }
 
@@ -617,35 +617,35 @@
     jpegr_metadata_struct metadata = { .maxContentBoost = static_cast<float>(boost),
                                        .minContentBoost = 1.0f / pow(static_cast<float>(boost),
                                                               1.0f / 3.0f) };
-    RecoveryLUT recoveryLUT(&metadata);
-    RecoveryLUT recoveryLUTWithBoost(&metadata, metadata.maxContentBoost);
-    for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kRecoveryFactorNumEntries - 1);
-      EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata),
-                      applyRecoveryLUT(RgbBlack(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, &metadata),
-                      applyRecoveryLUT(RgbWhite(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, &metadata),
-                      applyRecoveryLUT(RgbRed(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, &metadata),
-                      applyRecoveryLUT(RgbGreen(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata),
-                      applyRecoveryLUT(RgbBlue(), value, recoveryLUT));
-      EXPECT_RGB_EQ(applyRecoveryLUT(RgbBlack(), value, recoveryLUT),
-                    applyRecoveryLUT(RgbBlack(), value, recoveryLUTWithBoost));
-      EXPECT_RGB_EQ(applyRecoveryLUT(RgbWhite(), value, recoveryLUT),
-                    applyRecoveryLUT(RgbWhite(), value, recoveryLUTWithBoost));
-      EXPECT_RGB_EQ(applyRecoveryLUT(RgbRed(), value, recoveryLUT),
-                    applyRecoveryLUT(RgbRed(), value, recoveryLUTWithBoost));
-      EXPECT_RGB_EQ(applyRecoveryLUT(RgbGreen(), value, recoveryLUT),
-                    applyRecoveryLUT(RgbGreen(), value, recoveryLUTWithBoost));
-      EXPECT_RGB_EQ(applyRecoveryLUT(RgbBlue(), value, recoveryLUT),
-                    applyRecoveryLUT(RgbBlue(), value, recoveryLUTWithBoost));
+    GainLUT gainLUT(&metadata);
+    GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost);
+    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
+      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
+      EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata),
+                      applyGainLUT(RgbBlack(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata),
+                      applyGainLUT(RgbWhite(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata),
+                      applyGainLUT(RgbRed(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata),
+                      applyGainLUT(RgbGreen(), value, gainLUT));
+      EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata),
+                      applyGainLUT(RgbBlue(), value, gainLUT));
+      EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT),
+                    applyGainLUT(RgbBlack(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT),
+                    applyGainLUT(RgbWhite(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT),
+                    applyGainLUT(RgbRed(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT),
+                    applyGainLUT(RgbGreen(), value, gainLUTWithBoost));
+      EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT),
+                    applyGainLUT(RgbBlue(), value, gainLUTWithBoost));
     }
   }
 }
 
-TEST_F(RecoveryMapMathTest, PqTransferFunctionRoundtrip) {
+TEST_F(GainMapMathTest, PqTransferFunctionRoundtrip) {
   EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(0.0f)), 0.0f);
   EXPECT_NEAR(pqInvOetf(pqOetf(0.01f)), 0.01f, ComparisonEpsilon());
   EXPECT_NEAR(pqInvOetf(pqOetf(0.5f)), 0.5f, ComparisonEpsilon());
@@ -653,7 +653,7 @@
   EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(1.0f)), 1.0f);
 }
 
-TEST_F(RecoveryMapMathTest, ColorConversionLookup) {
+TEST_F(GainMapMathTest, ColorConversionLookup) {
   EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_UNSPECIFIED),
             nullptr);
   EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_BT709),
@@ -691,139 +691,139 @@
             nullptr);
 }
 
-TEST_F(RecoveryMapMathTest, EncodeRecovery) {
+TEST_F(GainMapMathTest, EncodeGain) {
   jpegr_metadata_struct metadata = { .maxContentBoost = 4.0f,
                                      .minContentBoost = 1.0f / 4.0f };
 
-  EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 127);
-  EXPECT_EQ(encodeRecovery(0.0f, 1.0f, &metadata), 127);
-  EXPECT_EQ(encodeRecovery(1.0f, 0.0f, &metadata), 0);
-  EXPECT_EQ(encodeRecovery(0.5f, 0.0f, &metadata), 0);
+  EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 127);
+  EXPECT_EQ(encodeGain(0.0f, 1.0f, &metadata), 127);
+  EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0);
+  EXPECT_EQ(encodeGain(0.5f, 0.0f, &metadata), 0);
 
-  EXPECT_EQ(encodeRecovery(1.0f, 1.0f, &metadata), 127);
-  EXPECT_EQ(encodeRecovery(1.0f, 4.0f, &metadata), 255);
-  EXPECT_EQ(encodeRecovery(1.0f, 5.0f, &metadata), 255);
-  EXPECT_EQ(encodeRecovery(4.0f, 1.0f, &metadata), 0);
-  EXPECT_EQ(encodeRecovery(4.0f, 0.5f, &metadata), 0);
-  EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 191);
-  EXPECT_EQ(encodeRecovery(2.0f, 1.0f, &metadata), 63);
+  EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 127);
+  EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 255);
+  EXPECT_EQ(encodeGain(1.0f, 5.0f, &metadata), 255);
+  EXPECT_EQ(encodeGain(4.0f, 1.0f, &metadata), 0);
+  EXPECT_EQ(encodeGain(4.0f, 0.5f, &metadata), 0);
+  EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 191);
+  EXPECT_EQ(encodeGain(2.0f, 1.0f, &metadata), 63);
 
   metadata.maxContentBoost = 2.0f;
   metadata.minContentBoost = 1.0f / 2.0f;
 
-  EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 255);
-  EXPECT_EQ(encodeRecovery(2.0f, 1.0f, &metadata), 0);
-  EXPECT_EQ(encodeRecovery(1.0f, 1.41421f, &metadata), 191);
-  EXPECT_EQ(encodeRecovery(1.41421f, 1.0f, &metadata), 63);
+  EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 255);
+  EXPECT_EQ(encodeGain(2.0f, 1.0f, &metadata), 0);
+  EXPECT_EQ(encodeGain(1.0f, 1.41421f, &metadata), 191);
+  EXPECT_EQ(encodeGain(1.41421f, 1.0f, &metadata), 63);
 
   metadata.maxContentBoost = 8.0f;
   metadata.minContentBoost = 1.0f / 8.0f;
 
-  EXPECT_EQ(encodeRecovery(1.0f, 8.0f, &metadata), 255);
-  EXPECT_EQ(encodeRecovery(8.0f, 1.0f, &metadata), 0);
-  EXPECT_EQ(encodeRecovery(1.0f, 2.82843f, &metadata), 191);
-  EXPECT_EQ(encodeRecovery(2.82843f, 1.0f, &metadata), 63);
+  EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255);
+  EXPECT_EQ(encodeGain(8.0f, 1.0f, &metadata), 0);
+  EXPECT_EQ(encodeGain(1.0f, 2.82843f, &metadata), 191);
+  EXPECT_EQ(encodeGain(2.82843f, 1.0f, &metadata), 63);
 
   metadata.maxContentBoost = 8.0f;
   metadata.minContentBoost = 1.0f;
 
-  EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 0);
-  EXPECT_EQ(encodeRecovery(1.0f, 0.0f, &metadata), 0);
+  EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 0);
+  EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0);
 
-  EXPECT_EQ(encodeRecovery(1.0f, 1.0f, &metadata), 0);
-  EXPECT_EQ(encodeRecovery(1.0f, 8.0f, &metadata), 255);
-  EXPECT_EQ(encodeRecovery(1.0f, 4.0f, &metadata), 170);
-  EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 85);
+  EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 0);
+  EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255);
+  EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 170);
+  EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 85);
 
   metadata.maxContentBoost = 8.0f;
   metadata.minContentBoost = 0.5f;
 
-  EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 63);
-  EXPECT_EQ(encodeRecovery(1.0f, 0.0f, &metadata), 0);
+  EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 63);
+  EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0);
 
-  EXPECT_EQ(encodeRecovery(1.0f, 1.0f, &metadata), 63);
-  EXPECT_EQ(encodeRecovery(1.0f, 8.0f, &metadata), 255);
-  EXPECT_EQ(encodeRecovery(1.0f, 4.0f, &metadata), 191);
-  EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 127);
-  EXPECT_EQ(encodeRecovery(1.0f, 0.7071f, &metadata), 31);
-  EXPECT_EQ(encodeRecovery(1.0f, 0.5f, &metadata), 0);
+  EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 63);
+  EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255);
+  EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 191);
+  EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 127);
+  EXPECT_EQ(encodeGain(1.0f, 0.7071f, &metadata), 31);
+  EXPECT_EQ(encodeGain(1.0f, 0.5f, &metadata), 0);
 }
 
-TEST_F(RecoveryMapMathTest, ApplyRecovery) {
+TEST_F(GainMapMathTest, ApplyGain) {
   jpegr_metadata_struct metadata = { .maxContentBoost = 4.0f,
                                      .minContentBoost = 1.0f / 4.0f };
   float displayBoost = metadata.maxContentBoost;
 
-  EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.0f, &metadata), RgbBlack());
-  EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.5f, &metadata), RgbBlack());
-  EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 1.0f, &metadata), RgbBlack());
+  EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.0f, &metadata), RgbBlack());
+  EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.5f, &metadata), RgbBlack());
+  EXPECT_RGB_NEAR(applyGain(RgbBlack(), 1.0f, &metadata), RgbBlack());
 
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 4.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 4.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 4.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite());
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 4.0f);
 
   metadata.maxContentBoost = 2.0f;
   metadata.minContentBoost = 1.0f / 2.0f;
 
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite() / 1.41421f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 1.41421f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 2.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 1.41421f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite());
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 1.41421f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 2.0f);
 
   metadata.maxContentBoost = 8.0f;
   metadata.minContentBoost = 1.0f / 8.0f;
 
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 8.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.82843f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.82843f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 8.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.82843f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite());
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.82843f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
 
   metadata.maxContentBoost = 8.0f;
   metadata.minContentBoost = 1.0f;
 
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite());
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
 
   metadata.maxContentBoost = 8.0f;
   metadata.minContentBoost = 0.5f;
 
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite() * 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 4.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite());
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite() * 2.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 4.0f);
+  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
 
   Color e = {{{ 0.0f, 0.5f, 1.0f }}};
   metadata.maxContentBoost = 4.0f;
   metadata.minContentBoost = 1.0f / 4.0f;
 
-  EXPECT_RGB_NEAR(applyRecovery(e, 0.0f, &metadata), e / 4.0f);
-  EXPECT_RGB_NEAR(applyRecovery(e, 0.25f, &metadata), e / 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(e, 0.5f, &metadata), e);
-  EXPECT_RGB_NEAR(applyRecovery(e, 0.75f, &metadata), e * 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(e, 1.0f, &metadata), e * 4.0f);
+  EXPECT_RGB_NEAR(applyGain(e, 0.0f, &metadata), e / 4.0f);
+  EXPECT_RGB_NEAR(applyGain(e, 0.25f, &metadata), e / 2.0f);
+  EXPECT_RGB_NEAR(applyGain(e, 0.5f, &metadata), e);
+  EXPECT_RGB_NEAR(applyGain(e, 0.75f, &metadata), e * 2.0f);
+  EXPECT_RGB_NEAR(applyGain(e, 1.0f, &metadata), e * 4.0f);
 
-  EXPECT_RGB_EQ(applyRecovery(RgbBlack(), 1.0f, &metadata),
-                applyRecovery(RgbBlack(), 1.0f, &metadata, displayBoost));
-  EXPECT_RGB_EQ(applyRecovery(RgbWhite(), 1.0f, &metadata),
-                applyRecovery(RgbWhite(), 1.0f, &metadata, displayBoost));
-  EXPECT_RGB_EQ(applyRecovery(RgbRed(), 1.0f, &metadata),
-                applyRecovery(RgbRed(), 1.0f, &metadata, displayBoost));
-  EXPECT_RGB_EQ(applyRecovery(RgbGreen(), 1.0f, &metadata),
-                applyRecovery(RgbGreen(), 1.0f, &metadata, displayBoost));
-  EXPECT_RGB_EQ(applyRecovery(RgbBlue(), 1.0f, &metadata),
-                applyRecovery(RgbBlue(), 1.0f, &metadata, displayBoost));
-  EXPECT_RGB_EQ(applyRecovery(e, 1.0f, &metadata),
-                applyRecovery(e, 1.0f, &metadata, displayBoost));
+  EXPECT_RGB_EQ(applyGain(RgbBlack(), 1.0f, &metadata),
+                applyGain(RgbBlack(), 1.0f, &metadata, displayBoost));
+  EXPECT_RGB_EQ(applyGain(RgbWhite(), 1.0f, &metadata),
+                applyGain(RgbWhite(), 1.0f, &metadata, displayBoost));
+  EXPECT_RGB_EQ(applyGain(RgbRed(), 1.0f, &metadata),
+                applyGain(RgbRed(), 1.0f, &metadata, displayBoost));
+  EXPECT_RGB_EQ(applyGain(RgbGreen(), 1.0f, &metadata),
+                applyGain(RgbGreen(), 1.0f, &metadata, displayBoost));
+  EXPECT_RGB_EQ(applyGain(RgbBlue(), 1.0f, &metadata),
+                applyGain(RgbBlue(), 1.0f, &metadata, displayBoost));
+  EXPECT_RGB_EQ(applyGain(e, 1.0f, &metadata),
+                applyGain(e, 1.0f, &metadata, displayBoost));
 }
 
-TEST_F(RecoveryMapMathTest, GetYuv420Pixel) {
+TEST_F(GainMapMathTest, GetYuv420Pixel) {
   jpegr_uncompressed_struct image = Yuv420Image();
   Color (*colors)[4] = Yuv420Colors();
 
@@ -834,7 +834,7 @@
   }
 }
 
-TEST_F(RecoveryMapMathTest, GetP010Pixel) {
+TEST_F(GainMapMathTest, GetP010Pixel) {
   jpegr_uncompressed_struct image = P010Image();
   Color (*colors)[4] = P010Colors();
 
@@ -845,7 +845,7 @@
   }
 }
 
-TEST_F(RecoveryMapMathTest, SampleYuv420) {
+TEST_F(GainMapMathTest, SampleYuv420) {
   jpegr_uncompressed_struct image = Yuv420Image();
   Color (*colors)[4] = Yuv420Colors();
 
@@ -871,7 +871,7 @@
   }
 }
 
-TEST_F(RecoveryMapMathTest, SampleP010) {
+TEST_F(GainMapMathTest, SampleP010) {
   jpegr_uncompressed_struct image = P010Image();
   Color (*colors)[4] = P010Colors();
 
@@ -897,7 +897,7 @@
   }
 }
 
-TEST_F(RecoveryMapMathTest, SampleMap) {
+TEST_F(GainMapMathTest, SampleMap) {
   jpegr_uncompressed_struct image = MapImage();
   float (*values)[4] = MapValues();
 
@@ -937,7 +937,7 @@
   }
 }
 
-TEST_F(RecoveryMapMathTest, ColorToRgba1010102) {
+TEST_F(GainMapMathTest, ColorToRgba1010102) {
   EXPECT_EQ(colorToRgba1010102(RgbBlack()), 0x3 << 30);
   EXPECT_EQ(colorToRgba1010102(RgbWhite()), 0xFFFFFFFF);
   EXPECT_EQ(colorToRgba1010102(RgbRed()), 0x3 << 30 | 0x3ff);
@@ -952,7 +952,28 @@
           | static_cast<uint32_t>(0.3f * static_cast<float>(0x3ff)) << 20);
 }
 
-TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgb) {
+TEST_F(GainMapMathTest, ColorToRgbaF16) {
+  EXPECT_EQ(colorToRgbaF16(RgbBlack()), ((uint64_t) 0x3C00) << 48);
+  EXPECT_EQ(colorToRgbaF16(RgbWhite()), 0x3C003C003C003C00);
+  EXPECT_EQ(colorToRgbaF16(RgbRed()),   (((uint64_t) 0x3C00) << 48) | ((uint64_t) 0x3C00));
+  EXPECT_EQ(colorToRgbaF16(RgbGreen()), (((uint64_t) 0x3C00) << 48) | (((uint64_t) 0x3C00) << 16));
+  EXPECT_EQ(colorToRgbaF16(RgbBlue()),  (((uint64_t) 0x3C00) << 48) | (((uint64_t) 0x3C00) << 32));
+
+  Color e_gamma = {{{ 0.1f, 0.2f, 0.3f }}};
+  EXPECT_EQ(colorToRgbaF16(e_gamma), 0x3C0034CD32662E66);
+}
+
+TEST_F(GainMapMathTest, Float32ToFloat16) {
+  EXPECT_EQ(floatToHalf(0.1f), 0x2E66);
+  EXPECT_EQ(floatToHalf(0.0f), 0x0);
+  EXPECT_EQ(floatToHalf(1.0f), 0x3C00);
+  EXPECT_EQ(floatToHalf(-1.0f), 0xBC00);
+  EXPECT_EQ(floatToHalf(0x1.fffffep127f), 0x7FFF);  // float max
+  EXPECT_EQ(floatToHalf(-0x1.fffffep127f), 0xFFFF);  // float min
+  EXPECT_EQ(floatToHalf(0x1.0p-126f), 0x0);  // float zero
+}
+
+TEST_F(GainMapMathTest, GenerateMapLuminanceSrgb) {
   EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), srgbLuminance),
                   0.0f);
   EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), srgbLuminance),
@@ -965,7 +986,7 @@
               srgbLuminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
 }
 
-TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgbP3) {
+TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbP3) {
   EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), p3Luminance),
                   0.0f);
   EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), p3Luminance),
@@ -978,7 +999,7 @@
               p3Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
 }
 
-TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgbBt2100) {
+TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbBt2100) {
   EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), bt2100Luminance),
                   0.0f);
   EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), bt2100Luminance),
@@ -991,7 +1012,7 @@
               bt2100Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
 }
 
-TEST_F(RecoveryMapMathTest, GenerateMapLuminanceHlg) {
+TEST_F(GainMapMathTest, GenerateMapLuminanceHlg) {
   EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), hlgInvOetf, identityConversion,
                                        bt2100Luminance, kHlgMaxNits),
                   0.0f);
@@ -1009,7 +1030,7 @@
               bt2100Luminance(RgbBlue()) * kHlgMaxNits, LuminanceEpsilon());
 }
 
-TEST_F(RecoveryMapMathTest, GenerateMapLuminancePq) {
+TEST_F(GainMapMathTest, GenerateMapLuminancePq) {
   EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), pqInvOetf, identityConversion,
                                        bt2100Luminance, kPqMaxNits),
                   0.0f);
@@ -1027,7 +1048,7 @@
               bt2100Luminance(RgbBlue()) * kPqMaxNits, LuminanceEpsilon());
 }
 
-TEST_F(RecoveryMapMathTest, ApplyMap) {
+TEST_F(GainMapMathTest, ApplyMap) {
   jpegr_metadata_struct metadata = { .maxContentBoost = 8.0f,
                                      .minContentBoost = 1.0f / 8.0f };
 
diff --git a/libs/jpegrecoverymap/tests/jpegr_test.cpp b/libs/jpegrecoverymap/tests/jpegr_test.cpp
index 7c669ab..620f431 100644
--- a/libs/jpegrecoverymap/tests/jpegr_test.cpp
+++ b/libs/jpegrecoverymap/tests/jpegr_test.cpp
@@ -16,7 +16,7 @@
 
 #include <jpegrecoverymap/jpegr.h>
 #include <jpegrecoverymap/jpegrutils.h>
-#include <jpegrecoverymap/recoverymapmath.h>
+#include <jpegrecoverymap/gainmapmath.h>
 #include <fcntl.h>
 #include <fstream>
 #include <gtest/gtest.h>
@@ -24,10 +24,12 @@
 #include <utils/Log.h>
 
 #define RAW_P010_IMAGE "/sdcard/Documents/raw_p010_image.p010"
+#define RAW_P010_IMAGE_WITH_STRIDE "/sdcard/Documents/raw_p010_image_with_stride.p010"
 #define RAW_YUV420_IMAGE "/sdcard/Documents/raw_yuv420_image.yuv420"
 #define JPEG_IMAGE "/sdcard/Documents/jpeg_image.jpg"
 #define TEST_IMAGE_WIDTH 1280
 #define TEST_IMAGE_HEIGHT 720
+#define TEST_IMAGE_STRIDE 1288
 #define DEFAULT_JPEG_QUALITY 90
 
 #define SAVE_ENCODING_RESULT true
@@ -97,6 +99,7 @@
   virtual void TearDown();
 
   struct jpegr_uncompressed_struct mRawP010Image;
+  struct jpegr_uncompressed_struct mRawP010ImageWithStride;
   struct jpegr_uncompressed_struct mRawYuv420Image;
   struct jpegr_compressed_struct mJpegImage;
 };
@@ -107,24 +110,25 @@
 void JpegRTest::SetUp() {}
 void JpegRTest::TearDown() {
   free(mRawP010Image.data);
+  free(mRawP010ImageWithStride.data);
   free(mRawYuv420Image.data);
   free(mJpegImage.data);
 }
 
 class JpegRBenchmark : public JpegR {
 public:
- void BenchmarkGenerateRecoveryMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image,
-                                   jr_metadata_ptr metadata, jr_uncompressed_ptr map);
- void BenchmarkApplyRecoveryMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
-                                jr_metadata_ptr metadata, jr_uncompressed_ptr dest);
+ void BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image,
+                               jr_metadata_ptr metadata, jr_uncompressed_ptr map);
+ void BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
+                            jr_metadata_ptr metadata, jr_uncompressed_ptr dest);
 private:
  const int kProfileCount = 10;
 };
 
-void JpegRBenchmark::BenchmarkGenerateRecoveryMap(jr_uncompressed_ptr yuv420Image,
-                                                        jr_uncompressed_ptr p010Image,
-                                                        jr_metadata_ptr metadata,
-                                                        jr_uncompressed_ptr map) {
+void JpegRBenchmark::BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image,
+                                              jr_uncompressed_ptr p010Image,
+                                              jr_metadata_ptr metadata,
+                                              jr_uncompressed_ptr map) {
   ASSERT_EQ(yuv420Image->width, p010Image->width);
   ASSERT_EQ(yuv420Image->height, p010Image->height);
 
@@ -132,38 +136,38 @@
 
   timerStart(&genRecMapTime);
   for (auto i = 0; i < kProfileCount; i++) {
-      ASSERT_EQ(OK, generateRecoveryMap(
+      ASSERT_EQ(OK, generateGainMap(
           yuv420Image, p010Image, jpegr_transfer_function::JPEGR_TF_HLG, metadata, map));
       if (i != kProfileCount - 1) delete[] static_cast<uint8_t *>(map->data);
   }
   timerStop(&genRecMapTime);
 
-  ALOGE("Generate Recovery Map:- Res = %i x %i, time = %f ms",
+  ALOGE("Generate Gain Map:- Res = %i x %i, time = %f ms",
         yuv420Image->width, yuv420Image->height,
         elapsedTime(&genRecMapTime) / (kProfileCount * 1000.f));
 
 }
 
-void JpegRBenchmark::BenchmarkApplyRecoveryMap(jr_uncompressed_ptr yuv420Image,
-                                                     jr_uncompressed_ptr map,
-                                                     jr_metadata_ptr metadata,
-                                                     jr_uncompressed_ptr dest) {
+void JpegRBenchmark::BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image,
+                                           jr_uncompressed_ptr map,
+                                           jr_metadata_ptr metadata,
+                                           jr_uncompressed_ptr dest) {
   Timer applyRecMapTime;
 
   timerStart(&applyRecMapTime);
   for (auto i = 0; i < kProfileCount; i++) {
-      ASSERT_EQ(OK, applyRecoveryMap(yuv420Image, map, metadata, JPEGR_OUTPUT_HDR_HLG,
-                                     metadata->maxContentBoost /* displayBoost */, dest));
+      ASSERT_EQ(OK, applyGainMap(yuv420Image, map, metadata, JPEGR_OUTPUT_HDR_HLG,
+                                 metadata->maxContentBoost /* displayBoost */, dest));
   }
   timerStop(&applyRecMapTime);
 
-  ALOGE("Apply Recovery Map:- Res = %i x %i, time = %f ms",
+  ALOGE("Apply Gain Map:- Res = %i x %i, time = %f ms",
         yuv420Image->width, yuv420Image->height,
         elapsedTime(&applyRecMapTime) / (kProfileCount * 1000.f));
 }
 
 TEST_F(JpegRTest, build) {
-  // Force all of the recovery map lib to be linked by calling all public functions.
+  // Force all of the gain map lib to be linked by calling all public functions.
   JpegR jpegRCodec;
   jpegRCodec.encodeJPEGR(nullptr, static_cast<jpegr_transfer_function>(0), nullptr, 0, nullptr);
   jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast<jpegr_transfer_function>(0),
@@ -249,6 +253,61 @@
   free(decodedJpegR.data);
 }
 
+/* Test Encode API-0 (with stride) and decode */
+TEST_F(JpegRTest, encodeFromP010WithStrideThenDecode) {
+  int ret;
+
+  // Load input files.
+  if (!loadFile(RAW_P010_IMAGE_WITH_STRIDE, mRawP010ImageWithStride.data, nullptr)) {
+    FAIL() << "Load file " << RAW_P010_IMAGE_WITH_STRIDE << " failed";
+  }
+  mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+  mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+  mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
+  mRawP010ImageWithStride.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+
+  JpegR jpegRCodec;
+
+  jpegr_compressed_struct jpegR;
+  jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
+  jpegR.data = malloc(jpegR.maxLength);
+  ret = jpegRCodec.encodeJPEGR(
+      &mRawP010ImageWithStride, 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_p010_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 * 8;
+  decodedJpegR.data = malloc(decodedJpegRSize);
+  ret = jpegRCodec.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_p010_input.rgb";
+    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(JpegRTest, encodeFromRawHdrAndSdrThenDecode) {
   int ret;
@@ -456,7 +515,7 @@
   free(decodedJpegR.data);
 }
 
-TEST_F(JpegRTest, ProfileRecoveryMapFuncs) {
+TEST_F(JpegRTest, ProfileGainMapFuncs) {
   const size_t kWidth = TEST_IMAGE_WIDTH;
   const size_t kHeight = TEST_IMAGE_HEIGHT;
 
@@ -486,7 +545,7 @@
                                     .height = 0,
                                     .colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED };
 
-  benchmark.BenchmarkGenerateRecoveryMap(&mRawYuv420Image, &mRawP010Image, &metadata, &map);
+  benchmark.BenchmarkGenerateGainMap(&mRawYuv420Image, &mRawP010Image, &metadata, &map);
 
   const int dstSize = mRawYuv420Image.width * mRawYuv420Image.height * 4;
   auto bufferDst = std::make_unique<uint8_t[]>(dstSize);
@@ -495,7 +554,7 @@
                                      .height = 0,
                                      .colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED };
 
-  benchmark.BenchmarkApplyRecoveryMap(&mRawYuv420Image, &map, &metadata, &dest);
+  benchmark.BenchmarkApplyGainMap(&mRawYuv420Image, &map, &metadata, &dest);
 }
 
 } // namespace android::recoverymap
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 495334e..f0b1072 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -27,6 +27,14 @@
       "name": "libinputservice_test"
     },
     {
+      "name": "libgui_test",
+      "options": [
+        {
+          "native-test-flag": "--gtest_filter=\"InputSurfacesTest*:MultiDisplayTests*\""
+        }
+      ]
+    },
+    {
       "name": "CtsHardwareTestCases",
       "options": [
         {
@@ -123,6 +131,14 @@
       "name": "libinputservice_test"
     },
     {
+      "name": "libgui_test",
+      "options": [
+        {
+          "native-test-flag": "--gtest_filter=\"InputSurfacesTest*:MultiDisplayTests*\""
+        }
+      ]
+    },
+    {
       "name": "CtsHardwareTestCases",
       "options": [
         {
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index eaed987..ccf4118 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -253,7 +253,8 @@
     mDevices.erase(eventHubId);
 }
 
-std::list<NotifyArgs> InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config,
+std::list<NotifyArgs> InputDevice::configure(nsecs_t when,
+                                             const InputReaderConfiguration& readerConfig,
                                              uint32_t changes) {
     std::list<NotifyArgs> out;
     mSources = 0;
@@ -291,7 +292,7 @@
             });
 
             mAssociatedDeviceType =
-                    getValueByKey(config->deviceTypeAssociations, mIdentifier.location);
+                    getValueByKey(readerConfig.deviceTypeAssociations, mIdentifier.location);
         }
 
         if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS)) {
@@ -325,8 +326,8 @@
             // Do not execute this code on the first configure, because 'setEnabled' would call
             // InputMapper::reset, and you can't reset a mapper before it has been configured.
             // The mappers are configured for the first time at the bottom of this function.
-            auto it = config->disabledDevices.find(mId);
-            bool enabled = it == config->disabledDevices.end();
+            auto it = readerConfig.disabledDevices.find(mId);
+            bool enabled = it == readerConfig.disabledDevices.end();
             out += setEnabled(enabled, when);
         }
 
@@ -338,13 +339,14 @@
             // Find the display port that corresponds to the current input port.
             const std::string& inputPort = mIdentifier.location;
             if (!inputPort.empty()) {
-                const std::unordered_map<std::string, uint8_t>& ports = config->portAssociations;
+                const std::unordered_map<std::string, uint8_t>& ports =
+                        readerConfig.portAssociations;
                 const auto& displayPort = ports.find(inputPort);
                 if (displayPort != ports.end()) {
                     mAssociatedDisplayPort = std::make_optional(displayPort->second);
                 } else {
                     const std::unordered_map<std::string, std::string>& displayUniqueIds =
-                            config->uniqueIdAssociations;
+                            readerConfig.uniqueIdAssociations;
                     const auto& displayUniqueId = displayUniqueIds.find(inputPort);
                     if (displayUniqueId != displayUniqueIds.end()) {
                         mAssociatedDisplayUniqueId = displayUniqueId->second;
@@ -356,9 +358,11 @@
             // "disabledDevices" list. If it is associated with a specific display, and it was not
             // explicitly disabled, then enable/disable the device based on whether we can find the
             // corresponding viewport.
-            bool enabled = (config->disabledDevices.find(mId) == config->disabledDevices.end());
+            bool enabled =
+                    (readerConfig.disabledDevices.find(mId) == readerConfig.disabledDevices.end());
             if (mAssociatedDisplayPort) {
-                mAssociatedViewport = config->getDisplayViewportByPort(*mAssociatedDisplayPort);
+                mAssociatedViewport =
+                        readerConfig.getDisplayViewportByPort(*mAssociatedDisplayPort);
                 if (!mAssociatedViewport) {
                     ALOGW("Input device %s should be associated with display on port %" PRIu8 ", "
                           "but the corresponding viewport is not found.",
@@ -367,7 +371,7 @@
                 }
             } else if (mAssociatedDisplayUniqueId != std::nullopt) {
                 mAssociatedViewport =
-                        config->getDisplayViewportByUniqueId(*mAssociatedDisplayUniqueId);
+                        readerConfig.getDisplayViewportByUniqueId(*mAssociatedDisplayUniqueId);
                 if (!mAssociatedViewport) {
                     ALOGW("Input device %s should be associated with display %s but the "
                           "corresponding viewport cannot be found",
@@ -384,15 +388,16 @@
             }
         }
 
-        for_each_mapper([this, when, &config, changes, &out](InputMapper& mapper) {
-            out += mapper.reconfigure(when, config, changes);
+        for_each_mapper([this, when, &readerConfig, changes, &out](InputMapper& mapper) {
+            out += mapper.reconfigure(when, readerConfig, changes);
             mSources |= mapper.getSources();
         });
 
         // If a device is just plugged but it might be disabled, we need to update some info like
         // axis range of touch from each InputMapper first, then disable it.
         if (!changes) {
-            out += setEnabled(config->disabledDevices.find(mId) == config->disabledDevices.end(),
+            out += setEnabled(readerConfig.disabledDevices.find(mId) ==
+                                      readerConfig.disabledDevices.end(),
                               when);
         }
     }
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 81ac03b..80459a2 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -234,7 +234,7 @@
     InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(eventHubId);
     std::shared_ptr<InputDevice> device = createDeviceLocked(eventHubId, identifier);
 
-    notifyAll(device->configure(when, &mConfig, 0));
+    notifyAll(device->configure(when, mConfig, 0));
     notifyAll(device->reset(when));
 
     if (device->isIgnored()) {
@@ -310,7 +310,7 @@
 
     std::list<NotifyArgs> resetEvents;
     if (device->hasEventHubDevices()) {
-        resetEvents += device->configure(when, &mConfig, 0);
+        resetEvents += device->configure(when, mConfig, 0);
     }
     resetEvents += device->reset(when);
     notifyAll(std::move(resetEvents));
@@ -408,7 +408,7 @@
     } else {
         for (auto& devicePair : mDevices) {
             std::shared_ptr<InputDevice>& device = devicePair.second;
-            notifyAll(device->configure(now, &mConfig, changes));
+            notifyAll(device->configure(now, mConfig, changes));
         }
     }
 
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 4ae06fe..ad45cb1 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -83,7 +83,7 @@
     void addEventHubDevice(int32_t eventHubId, bool populateMappers = true);
     void removeEventHubDevice(int32_t eventHubId);
     [[nodiscard]] std::list<NotifyArgs> configure(nsecs_t when,
-                                                  const InputReaderConfiguration* config,
+                                                  const InputReaderConfiguration& readerConfig,
                                                   uint32_t changes);
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when);
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvents, size_t count);
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index d7dc2ae..1cc614e 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -134,7 +134,7 @@
 }
 
 std::list<NotifyArgs> CursorInputMapper::reconfigure(nsecs_t when,
-                                                     const InputReaderConfiguration* config,
+                                                     const InputReaderConfiguration& config,
                                                      uint32_t changes) {
     std::list<NotifyArgs> out = InputMapper::reconfigure(when, config, changes);
 
@@ -173,10 +173,10 @@
     }
 
     const bool configurePointerCapture = mParameters.mode != Parameters::Mode::NAVIGATION &&
-            ((!changes && config->pointerCaptureRequest.enable) ||
+            ((!changes && config.pointerCaptureRequest.enable) ||
              (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE));
     if (configurePointerCapture) {
-        if (config->pointerCaptureRequest.enable) {
+        if (config.pointerCaptureRequest.enable) {
             if (mParameters.mode == Parameters::Mode::POINTER) {
                 mParameters.mode = Parameters::Mode::POINTER_RELATIVE;
                 mSource = AINPUT_SOURCE_MOUSE_RELATIVE;
@@ -207,9 +207,9 @@
             mWheelXVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
             mWheelYVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
         } else {
-            mPointerVelocityControl.setParameters(config->pointerVelocityControlParameters);
-            mWheelXVelocityControl.setParameters(config->wheelVelocityControlParameters);
-            mWheelYVelocityControl.setParameters(config->wheelVelocityControlParameters);
+            mPointerVelocityControl.setParameters(config.pointerVelocityControlParameters);
+            mWheelXVelocityControl.setParameters(config.wheelVelocityControlParameters);
+            mWheelYVelocityControl.setParameters(config.wheelVelocityControlParameters);
         }
     }
 
@@ -241,7 +241,7 @@
         // rotations and report values directly from the input device.
         if (!isOrientedDevice && mDisplayId &&
             mParameters.mode != Parameters::Mode::POINTER_RELATIVE) {
-            if (auto viewport = config->getDisplayViewportById(*mDisplayId); viewport) {
+            if (auto viewport = config.getDisplayViewportById(*mDisplayId); viewport) {
                 mOrientation = getInverseRotation(viewport->orientation);
             }
         }
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index 987b9de..5f7a3ad 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -60,7 +60,7 @@
     virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
     virtual void dump(std::string& dump) override;
     [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
-                                                    const InputReaderConfiguration* config,
+                                                    const InputReaderConfiguration& config,
                                                     uint32_t changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
index c5a3075..bbb641e 100644
--- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
@@ -47,7 +47,7 @@
 }
 
 std::list<NotifyArgs> ExternalStylusInputMapper::reconfigure(nsecs_t when,
-                                                             const InputReaderConfiguration* config,
+                                                             const InputReaderConfiguration& config,
                                                              uint32_t changes) {
     getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPressureAxis);
     mTouchButtonAccumulator.configure();
diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
index 0df8cf7..3eac10d 100644
--- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
+++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
@@ -33,7 +33,7 @@
     void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
     void dump(std::string& dump) override;
     [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
-                                                    const InputReaderConfiguration* config,
+                                                    const InputReaderConfiguration& config,
                                                     uint32_t changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp
index 9d1e9ce..5dd7bc4 100644
--- a/services/inputflinger/reader/mapper/InputMapper.cpp
+++ b/services/inputflinger/reader/mapper/InputMapper.cpp
@@ -35,7 +35,7 @@
 
 void InputMapper::dump(std::string& dump) {}
 
-std::list<NotifyArgs> InputMapper::reconfigure(nsecs_t when, const InputReaderConfiguration* config,
+std::list<NotifyArgs> InputMapper::reconfigure(nsecs_t when, const InputReaderConfiguration& config,
                                                uint32_t changes) {
     return {};
 }
diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h
index bb15e4d..ab573f0 100644
--- a/services/inputflinger/reader/mapper/InputMapper.h
+++ b/services/inputflinger/reader/mapper/InputMapper.h
@@ -54,7 +54,7 @@
     virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo);
     virtual void dump(std::string& dump);
     [[nodiscard]] virtual std::list<NotifyArgs> reconfigure(nsecs_t when,
-                                                            const InputReaderConfiguration* config,
+                                                            const InputReaderConfiguration& config,
                                                             uint32_t changes);
     [[nodiscard]] virtual std::list<NotifyArgs> reset(nsecs_t when);
     [[nodiscard]] virtual std::list<NotifyArgs> process(const RawEvent* rawEvent) = 0;
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
index f60035b..3e840ee 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
@@ -104,7 +104,7 @@
 }
 
 std::list<NotifyArgs> JoystickInputMapper::reconfigure(nsecs_t when,
-                                                       const InputReaderConfiguration* config,
+                                                       const InputReaderConfiguration& config,
                                                        uint32_t changes) {
     std::list<NotifyArgs> out = InputMapper::reconfigure(when, config, changes);
 
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.h b/services/inputflinger/reader/mapper/JoystickInputMapper.h
index 9adb07f..6f1c6b7 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.h
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h
@@ -29,7 +29,7 @@
     virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
     virtual void dump(std::string& dump) override;
     [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
-                                                    const InputReaderConfiguration* config,
+                                                    const InputReaderConfiguration& config,
                                                     uint32_t changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index fc00c48..f8dd3a8 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -118,21 +118,21 @@
 }
 
 std::optional<DisplayViewport> KeyboardInputMapper::findViewport(
-        const InputReaderConfiguration* config) {
+        const InputReaderConfiguration& readerConfig) {
     if (getDeviceContext().getAssociatedViewport()) {
         return getDeviceContext().getAssociatedViewport();
     }
 
     // No associated display defined, try to find default display if orientationAware.
     if (mParameters.orientationAware) {
-        return config->getDisplayViewportByType(ViewportType::INTERNAL);
+        return readerConfig.getDisplayViewportByType(ViewportType::INTERNAL);
     }
 
     return std::nullopt;
 }
 
 std::list<NotifyArgs> KeyboardInputMapper::reconfigure(nsecs_t when,
-                                                       const InputReaderConfiguration* config,
+                                                       const InputReaderConfiguration& config,
                                                        uint32_t changes) {
     std::list<NotifyArgs> out = InputMapper::reconfigure(when, config, changes);
 
@@ -147,7 +147,7 @@
 
     if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUT_ASSOCIATION)) {
         mKeyboardLayoutInfo =
-                getValueByKey(config->keyboardLayoutAssociations, getDeviceContext().getLocation());
+                getValueByKey(config.keyboardLayoutAssociations, getDeviceContext().getLocation());
     }
 
     return out;
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index 52576c3..0cb130d 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -30,7 +30,7 @@
     void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
     void dump(std::string& dump) override;
     [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
-                                                    const InputReaderConfiguration* config,
+                                                    const InputReaderConfiguration& config,
                                                     uint32_t changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
@@ -96,7 +96,7 @@
     void resetLedState();
     void initializeLedState(LedState& ledState, int32_t led);
     void updateLedStateForModifier(LedState& ledState, int32_t led, int32_t modifier, bool reset);
-    std::optional<DisplayViewport> findViewport(const InputReaderConfiguration* config);
+    std::optional<DisplayViewport> findViewport(const InputReaderConfiguration& readerConfig);
     [[nodiscard]] std::list<NotifyArgs> cancelAllDownKeys(nsecs_t when);
 };
 
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index 5b7b295..b181aa0 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -64,7 +64,7 @@
 }
 
 std::list<NotifyArgs> RotaryEncoderInputMapper::reconfigure(nsecs_t when,
-                                                            const InputReaderConfiguration* config,
+                                                            const InputReaderConfiguration& config,
                                                             uint32_t changes) {
     std::list<NotifyArgs> out = InputMapper::reconfigure(when, config, changes);
     if (!changes) {
@@ -72,7 +72,7 @@
     }
     if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
         std::optional<DisplayViewport> internalViewport =
-                config->getDisplayViewportByType(ViewportType::INTERNAL);
+                config.getDisplayViewportByType(ViewportType::INTERNAL);
         if (internalViewport) {
             mOrientation = internalViewport->orientation;
         } else {
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
index 639a987..37c9442 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
@@ -32,7 +32,7 @@
     virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
     virtual void dump(std::string& dump) override;
     [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
-                                                    const InputReaderConfiguration* config,
+                                                    const InputReaderConfiguration& config,
                                                     uint32_t changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
index 720fc69..f8a520d 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
@@ -117,7 +117,7 @@
 }
 
 std::list<NotifyArgs> SensorInputMapper::reconfigure(nsecs_t when,
-                                                     const InputReaderConfiguration* config,
+                                                     const InputReaderConfiguration& config,
                                                      uint32_t changes) {
     std::list<NotifyArgs> out = InputMapper::reconfigure(when, config, changes);
 
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h
index 93cc244..fa36ab3 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.h
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.h
@@ -34,7 +34,7 @@
     void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
     void dump(std::string& dump) override;
     [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
-                                                    const InputReaderConfiguration* config,
+                                                    const InputReaderConfiguration& config,
                                                     uint32_t changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index eb99438..9a426bf 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -288,11 +288,11 @@
 }
 
 std::list<NotifyArgs> TouchInputMapper::reconfigure(nsecs_t when,
-                                                    const InputReaderConfiguration* config,
+                                                    const InputReaderConfiguration& config,
                                                     uint32_t changes) {
     std::list<NotifyArgs> out = InputMapper::reconfigure(when, config, changes);
 
-    mConfig = *config;
+    mConfig = config;
 
     // Full configuration should happen the first time configure is called and
     // when the device type is changed. Changing a device type can affect
@@ -1855,6 +1855,27 @@
         }
     }
 
+    if (!mCurrentRawState.rawPointerData.hoveringIdBits.isEmpty() &&
+        mCurrentRawState.rawPointerData.touchingIdBits.isEmpty() &&
+        mDeviceMode != DeviceMode::UNSCALED) {
+        // We have hovering pointers, and there are no touching pointers.
+        bool hoveringPointersInFrame = false;
+        auto hoveringIds = mCurrentRawState.rawPointerData.hoveringIdBits;
+        while (!hoveringIds.isEmpty()) {
+            uint32_t id = hoveringIds.clearFirstMarkedBit();
+            const auto& pointer = mCurrentRawState.rawPointerData.pointerForId(id);
+            if (isPointInsidePhysicalFrame(pointer.x, pointer.y)) {
+                hoveringPointersInFrame = true;
+                break;
+            }
+        }
+        if (!hoveringPointersInFrame) {
+            // All hovering pointers are outside the physical frame.
+            outConsumed = true;
+            return out;
+        }
+    }
+
     if (mLastRawState.rawPointerData.touchingIdBits.isEmpty() &&
         !mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) {
         // Pointer just went down.  Check for virtual key press or off-screen touches.
@@ -1865,7 +1886,7 @@
         if (!isPointInsidePhysicalFrame(pointer.x, pointer.y) &&
             mDeviceMode != DeviceMode::UNSCALED) {
             // If exactly one pointer went down, check for virtual key hit.
-            // Otherwise we will drop the entire stroke.
+            // Otherwise, we will drop the entire stroke.
             if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) {
                 const VirtualKey* virtualKey = findVirtualKeyHit(pointer.x, pointer.y);
                 if (virtualKey) {
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index d98ae60..0e8ff4b 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -153,7 +153,7 @@
     void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
     void dump(std::string& dump) override;
     [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
-                                                    const InputReaderConfiguration* config,
+                                                    const InputReaderConfiguration& config,
                                                     uint32_t changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 33f368e..6b1d409 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -57,7 +57,7 @@
         {std::numeric_limits<double>::infinity(), 15.04, -857.758},
 };
 
-const std::vector<double> sensitivityFactors = {1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 18, 20};
+const std::vector<double> sensitivityFactors = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 18};
 
 std::vector<double> createAccelerationCurveForSensitivity(int32_t sensitivity,
                                                           size_t propertySize) {
@@ -220,7 +220,7 @@
 }
 
 std::list<NotifyArgs> TouchpadInputMapper::reconfigure(nsecs_t when,
-                                                       const InputReaderConfiguration* config,
+                                                       const InputReaderConfiguration& config,
                                                        uint32_t changes) {
     if (!changes) {
         // First time configuration
@@ -231,7 +231,7 @@
         std::optional<int32_t> displayId = mPointerController->getDisplayId();
         ui::Rotation orientation = ui::ROTATION_0;
         if (displayId.has_value()) {
-            if (auto viewport = config->getDisplayViewportById(*displayId); viewport) {
+            if (auto viewport = config.getDisplayViewportById(*displayId); viewport) {
                 orientation = getInverseRotation(viewport->orientation);
             }
         }
@@ -242,14 +242,14 @@
                 .setBoolValues({true});
         GesturesProp accelCurveProp = mPropertyProvider.getProperty("Pointer Accel Curve");
         accelCurveProp.setRealValues(
-                createAccelerationCurveForSensitivity(config->touchpadPointerSpeed,
+                createAccelerationCurveForSensitivity(config.touchpadPointerSpeed,
                                                       accelCurveProp.getCount()));
         mPropertyProvider.getProperty("Invert Scrolling")
-                .setBoolValues({config->touchpadNaturalScrollingEnabled});
+                .setBoolValues({config.touchpadNaturalScrollingEnabled});
         mPropertyProvider.getProperty("Tap Enable")
-                .setBoolValues({config->touchpadTapToClickEnabled});
+                .setBoolValues({config.touchpadTapToClickEnabled});
         mPropertyProvider.getProperty("Button Right Click Zone Enable")
-                .setBoolValues({config->touchpadRightClickZoneEnabled});
+                .setBoolValues({config.touchpadRightClickZoneEnabled});
     }
     return {};
 }
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index 6f152fa..27cdde1 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -45,7 +45,7 @@
     void dump(std::string& dump) override;
 
     [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
-                                                    const InputReaderConfiguration* config,
+                                                    const InputReaderConfiguration& config,
                                                     uint32_t changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
index c5fd5f5..e829692 100644
--- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
@@ -41,6 +41,12 @@
     bool isHovering() const;
     bool hasStylus() const;
     bool hasButtonTouch() const;
+
+    /*
+     * Returns the number of touches reported by the device through its BTN_TOOL_FINGER and
+     * BTN_TOOL_*TAP "buttons". Note that this count includes touches reported with their
+     * ABS_MT_TOOL_TYPE set to MT_TOOL_PALM.
+     */
     int getTouchCount() const;
 
 private:
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
index e89262a..8841b6e 100644
--- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
@@ -81,11 +81,16 @@
     }
 
     schs.fingers.clear();
+    size_t numPalms = 0;
     for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) {
         MultiTouchMotionAccumulator::Slot slot = mMotionAccumulator.getSlot(i);
+        if (!slot.isInUse()) {
+            continue;
+        }
         // Some touchpads continue to report contacts even after they've identified them as palms.
         // We want to exclude these contacts from the HardwareStates.
-        if (!slot.isInUse() || slot.getToolType() == ToolType::PALM) {
+        if (slot.getToolType() == ToolType::PALM) {
+            numPalms++;
             continue;
         }
 
@@ -103,7 +108,7 @@
     }
     schs.state.fingers = schs.fingers.data();
     schs.state.finger_cnt = schs.fingers.size();
-    schs.state.touch_cnt = mTouchButtonAccumulator.getTouchCount();
+    schs.state.touch_cnt = mTouchButtonAccumulator.getTouchCount() - numPalms;
     return schs;
 }
 
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index 30c1719..3486d0f 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -154,8 +154,8 @@
     mPointerController = std::move(controller);
 }
 
-const InputReaderConfiguration* FakeInputReaderPolicy::getReaderConfiguration() const {
-    return &mConfig;
+const InputReaderConfiguration& FakeInputReaderPolicy::getReaderConfiguration() const {
+    return mConfig;
 }
 
 const std::vector<InputDeviceInfo>& FakeInputReaderPolicy::getInputDevices() const {
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index 28ac505..85ff01a 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -63,7 +63,7 @@
     void addDisabledDevice(int32_t deviceId);
     void removeDisabledDevice(int32_t deviceId);
     void setPointerController(std::shared_ptr<FakePointerController> controller);
-    const InputReaderConfiguration* getReaderConfiguration() const;
+    const InputReaderConfiguration& getReaderConfiguration() const;
     const std::vector<InputDeviceInfo>& getInputDevices() const;
     TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor,
                                                            ui::Rotation surfaceRotation);
diff --git a/services/inputflinger/tests/HardwareStateConverter_test.cpp b/services/inputflinger/tests/HardwareStateConverter_test.cpp
index 3e97241..19d46c8 100644
--- a/services/inputflinger/tests/HardwareStateConverter_test.cpp
+++ b/services/inputflinger/tests/HardwareStateConverter_test.cpp
@@ -13,11 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <gestures/HardwareStateConverter.h>
+
+#include <memory>
 
 #include <EventHub.h>
-#include <gestures/HardwareStateConverter.h>
 #include <gtest/gtest.h>
 #include <linux/input-event-codes.h>
+#include <utils/StrongPointer.h>
 
 #include "FakeEventHub.h"
 #include "FakeInputReaderPolicy.h"
@@ -28,38 +31,37 @@
 namespace android {
 
 class HardwareStateConverterTest : public testing::Test {
+public:
+    HardwareStateConverterTest()
+          : mFakeEventHub(std::make_shared<FakeEventHub>()),
+            mFakePolicy(sp<FakeInputReaderPolicy>::make()),
+            mReader(mFakeEventHub, mFakePolicy, mFakeListener),
+            mDevice(newDevice()),
+            mDeviceContext(*mDevice, EVENTHUB_ID) {
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, 7, 0, 0, 0);
+        mConverter = std::make_unique<HardwareStateConverter>(mDeviceContext);
+    }
+
 protected:
     static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
     static constexpr int32_t EVENTHUB_ID = 1;
 
-    void SetUp() {
-        mFakeEventHub = std::make_unique<FakeEventHub>();
-        mFakePolicy = sp<FakeInputReaderPolicy>::make();
-        mFakeListener = std::make_unique<TestInputListener>();
-        mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
-                                                            *mFakeListener);
-        mDevice = newDevice();
-
-        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, 7, 0, 0, 0);
-    }
-
     std::shared_ptr<InputDevice> newDevice() {
         InputDeviceIdentifier identifier;
         identifier.name = "device";
         identifier.location = "USB1";
         identifier.bus = 0;
         std::shared_ptr<InputDevice> device =
-                std::make_shared<InputDevice>(mReader->getContext(), DEVICE_ID, /* generation= */ 2,
+                std::make_shared<InputDevice>(mReader.getContext(), DEVICE_ID, /*generation=*/2,
                                               identifier);
-        mReader->pushNextDevice(device);
+        mReader.pushNextDevice(device);
         mFakeEventHub->addDevice(EVENTHUB_ID, identifier.name, InputDeviceClass::TOUCHPAD,
                                  identifier.bus);
-        mReader->loopOnce();
+        mReader.loopOnce();
         return device;
     }
 
-    void processAxis(HardwareStateConverter& conv, nsecs_t when, int32_t type, int32_t code,
-                     int32_t value) {
+    void processAxis(nsecs_t when, int32_t type, int32_t code, int32_t value) {
         RawEvent event;
         event.when = when;
         event.readTime = READ_TIME;
@@ -67,12 +69,11 @@
         event.type = type;
         event.code = code;
         event.value = value;
-        std::optional<SelfContainedHardwareState> schs = conv.processRawEvent(&event);
+        std::optional<SelfContainedHardwareState> schs = mConverter->processRawEvent(&event);
         EXPECT_FALSE(schs.has_value());
     }
 
-    std::optional<SelfContainedHardwareState> processSync(HardwareStateConverter& conv,
-                                                          nsecs_t when) {
+    std::optional<SelfContainedHardwareState> processSync(nsecs_t when) {
         RawEvent event;
         event.when = when;
         event.readTime = READ_TIME;
@@ -80,37 +81,37 @@
         event.type = EV_SYN;
         event.code = SYN_REPORT;
         event.value = 0;
-        return conv.processRawEvent(&event);
+        return mConverter->processRawEvent(&event);
     }
 
     std::shared_ptr<FakeEventHub> mFakeEventHub;
     sp<FakeInputReaderPolicy> mFakePolicy;
-    std::unique_ptr<TestInputListener> mFakeListener;
-    std::unique_ptr<InstrumentedInputReader> mReader;
+    TestInputListener mFakeListener;
+    InstrumentedInputReader mReader;
     std::shared_ptr<InputDevice> mDevice;
+    InputDeviceContext mDeviceContext;
+    std::unique_ptr<HardwareStateConverter> mConverter;
 };
 
 TEST_F(HardwareStateConverterTest, OneFinger) {
     const nsecs_t time = 1500000000;
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    HardwareStateConverter conv(deviceContext);
 
-    processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0);
-    processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100);
-    processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MAJOR, 5);
-    processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MINOR, 4);
-    processAxis(conv, time, EV_ABS, ABS_MT_PRESSURE, 42);
-    processAxis(conv, time, EV_ABS, ABS_MT_ORIENTATION, 2);
+    processAxis(time, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(time, EV_ABS, ABS_MT_TRACKING_ID, 123);
+    processAxis(time, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(time, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(time, EV_ABS, ABS_MT_TOUCH_MAJOR, 5);
+    processAxis(time, EV_ABS, ABS_MT_TOUCH_MINOR, 4);
+    processAxis(time, EV_ABS, ABS_MT_PRESSURE, 42);
+    processAxis(time, EV_ABS, ABS_MT_ORIENTATION, 2);
 
-    processAxis(conv, time, EV_ABS, ABS_X, 50);
-    processAxis(conv, time, EV_ABS, ABS_Y, 100);
-    processAxis(conv, time, EV_ABS, ABS_PRESSURE, 42);
+    processAxis(time, EV_ABS, ABS_X, 50);
+    processAxis(time, EV_ABS, ABS_Y, 100);
+    processAxis(time, EV_ABS, ABS_PRESSURE, 42);
 
-    processAxis(conv, time, EV_KEY, BTN_TOUCH, 1);
-    processAxis(conv, time, EV_KEY, BTN_TOOL_FINGER, 1);
-    std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+    processAxis(time, EV_KEY, BTN_TOUCH, 1);
+    processAxis(time, EV_KEY, BTN_TOOL_FINGER, 1);
+    std::optional<SelfContainedHardwareState> schs = processSync(time);
 
     ASSERT_TRUE(schs.has_value());
     const HardwareState& state = schs->state;
@@ -138,35 +139,31 @@
 }
 
 TEST_F(HardwareStateConverterTest, TwoFingers) {
-    const nsecs_t time = ARBITRARY_TIME;
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    HardwareStateConverter conv(deviceContext);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 123);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOUCH_MAJOR, 5);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOUCH_MINOR, 4);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_PRESSURE, 42);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_ORIENTATION, 2);
 
-    processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0);
-    processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100);
-    processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MAJOR, 5);
-    processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MINOR, 4);
-    processAxis(conv, time, EV_ABS, ABS_MT_PRESSURE, 42);
-    processAxis(conv, time, EV_ABS, ABS_MT_ORIENTATION, 2);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 1);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 456);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, -20);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 40);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOUCH_MAJOR, 8);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOUCH_MINOR, 7);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_PRESSURE, 21);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_ORIENTATION, 1);
 
-    processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 1);
-    processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 456);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, -20);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 40);
-    processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MAJOR, 8);
-    processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MINOR, 7);
-    processAxis(conv, time, EV_ABS, ABS_MT_PRESSURE, 21);
-    processAxis(conv, time, EV_ABS, ABS_MT_ORIENTATION, 1);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_X, 50);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_Y, 100);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_PRESSURE, 42);
 
-    processAxis(conv, time, EV_ABS, ABS_X, 50);
-    processAxis(conv, time, EV_ABS, ABS_Y, 100);
-    processAxis(conv, time, EV_ABS, ABS_PRESSURE, 42);
-
-    processAxis(conv, time, EV_KEY, BTN_TOUCH, 1);
-    processAxis(conv, time, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
-    std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+    processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOUCH, 1);
+    processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+    std::optional<SelfContainedHardwareState> schs = processSync(ARBITRARY_TIME);
 
     ASSERT_TRUE(schs.has_value());
     ASSERT_EQ(2, schs->state.finger_cnt);
@@ -192,59 +189,58 @@
 }
 
 TEST_F(HardwareStateConverterTest, OnePalm) {
-    const nsecs_t time = ARBITRARY_TIME;
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    HardwareStateConverter conv(deviceContext);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 123);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 100);
 
-    processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0);
-    processAxis(conv, time, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM);
-    processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100);
-
-    processAxis(conv, time, EV_KEY, BTN_TOUCH, 1);
-    std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+    processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOUCH, 1);
+    processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOOL_FINGER, 1);
+    std::optional<SelfContainedHardwareState> schs = processSync(ARBITRARY_TIME);
     ASSERT_TRUE(schs.has_value());
+    EXPECT_EQ(0, schs->state.touch_cnt);
     EXPECT_EQ(0, schs->state.finger_cnt);
 }
 
 TEST_F(HardwareStateConverterTest, OneFingerTurningIntoAPalm) {
-    const nsecs_t time = ARBITRARY_TIME;
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    HardwareStateConverter conv(deviceContext);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 123);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 100);
 
-    processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0);
-    processAxis(conv, time, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
-    processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOUCH, 1);
+    processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOOL_FINGER, 1);
 
-    processAxis(conv, time, EV_KEY, BTN_TOUCH, 1);
-
-    std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+    std::optional<SelfContainedHardwareState> schs = processSync(ARBITRARY_TIME);
     ASSERT_TRUE(schs.has_value());
+    EXPECT_EQ(1, schs->state.touch_cnt);
     EXPECT_EQ(1, schs->state.finger_cnt);
 
-    processAxis(conv, time, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 51);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 99);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 51);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 99);
 
-    schs = processSync(conv, time);
+    schs = processSync(ARBITRARY_TIME);
     ASSERT_TRUE(schs.has_value());
+    EXPECT_EQ(0, schs->state.touch_cnt);
     ASSERT_EQ(0, schs->state.finger_cnt);
 
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 53);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 97);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 53);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 97);
 
-    schs = processSync(conv, time);
+    schs = processSync(ARBITRARY_TIME);
     ASSERT_TRUE(schs.has_value());
+    EXPECT_EQ(0, schs->state.touch_cnt);
     EXPECT_EQ(0, schs->state.finger_cnt);
 
-    processAxis(conv, time, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 55);
-    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 95);
-    schs = processSync(conv, time);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 55);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 95);
+    schs = processSync(ARBITRARY_TIME);
     ASSERT_TRUE(schs.has_value());
+    EXPECT_EQ(1, schs->state.touch_cnt);
     ASSERT_EQ(1, schs->state.finger_cnt);
     const FingerState& newFinger = schs->state.fingers[0];
     EXPECT_EQ(123, newFinger.tracking_id);
@@ -253,25 +249,16 @@
 }
 
 TEST_F(HardwareStateConverterTest, ButtonPressed) {
-    const nsecs_t time = ARBITRARY_TIME;
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    HardwareStateConverter conv(deviceContext);
-
-    processAxis(conv, time, EV_KEY, BTN_LEFT, 1);
-    std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+    processAxis(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 1);
+    std::optional<SelfContainedHardwareState> schs = processSync(ARBITRARY_TIME);
 
     ASSERT_TRUE(schs.has_value());
     EXPECT_EQ(GESTURES_BUTTON_LEFT, schs->state.buttons_down);
 }
 
 TEST_F(HardwareStateConverterTest, MscTimestamp) {
-    const nsecs_t time = ARBITRARY_TIME;
-    mFakeEventHub->setMscEvent(EVENTHUB_ID, MSC_TIMESTAMP);
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    HardwareStateConverter conv(deviceContext);
-
-    processAxis(conv, time, EV_MSC, MSC_TIMESTAMP, 1200000);
-    std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+    processAxis(ARBITRARY_TIME, EV_MSC, MSC_TIMESTAMP, 1200000);
+    std::optional<SelfContainedHardwareState> schs = processSync(ARBITRARY_TIME);
 
     ASSERT_TRUE(schs.has_value());
     EXPECT_NEAR(1.2, schs->state.msc_timestamp, EPSILON);
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 2223b35..dcf8557 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -263,7 +263,7 @@
         }
     }
 
-    std::list<NotifyArgs> reconfigure(nsecs_t, const InputReaderConfiguration* config,
+    std::list<NotifyArgs> reconfigure(nsecs_t, const InputReaderConfiguration& config,
                                       uint32_t changes) override {
         std::scoped_lock<std::mutex> lock(mLock);
         mConfigureWasCalled = true;
@@ -271,7 +271,7 @@
         // Find the associated viewport if exist.
         const std::optional<uint8_t> displayPort = getDeviceContext().getAssociatedDisplayPort();
         if (displayPort && (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
-            mViewport = config->getDisplayViewportByPort(*displayPort);
+            mViewport = config.getDisplayViewportByPort(*displayPort);
         }
 
         mStateChangedCondition.notify_all();
@@ -2319,7 +2319,7 @@
 TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) {
     // Configuration.
     InputReaderConfiguration config;
-    std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, &config, 0);
+    std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, 0);
 
     // Reset.
     unused += mDevice->reset(ARBITRARY_TIME);
@@ -2378,7 +2378,7 @@
     mapper2.setMetaState(AMETA_SHIFT_ON);
 
     InputReaderConfiguration config;
-    std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, &config, 0);
+    std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, 0);
 
     std::optional<std::string> propertyValue = mDevice->getConfiguration().getString("key");
     ASSERT_TRUE(propertyValue.has_value())
@@ -3669,7 +3669,7 @@
     addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
                                                AINPUT_KEYBOARD_TYPE_ALPHABETIC);
     InputReaderConfiguration config;
-    std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, &config, 0);
+    std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, 0);
 
     ASSERT_EQ("en", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->languageTag);
     ASSERT_EQ("extended", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->layoutType);
@@ -6735,6 +6735,54 @@
     ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mDevice->getSources());
 }
 
+TEST_F(SingleTouchInputMapperTest, HoverEventsOutsidePhysicalFrameAreIgnored) {
+    // Initialize the device without setting device source to touch navigation.
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(ui::ROTATION_0);
+    prepareButtons();
+    prepareAxes(POSITION);
+    mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
+
+    // Set a physical frame in the display viewport.
+    auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
+    viewport->physicalLeft = 0;
+    viewport->physicalTop = 0;
+    viewport->physicalRight = DISPLAY_WIDTH / 2;
+    viewport->physicalBottom = DISPLAY_HEIGHT / 2;
+    mFakePolicy->updateViewport(*viewport);
+    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+
+    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+
+    // Hovering inside the physical frame produces events.
+    processKey(mapper, BTN_TOOL_PEN, 1);
+    processMove(mapper, RAW_X_MIN + 1, RAW_Y_MIN + 1);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)));
+
+    // Leaving the physical frame ends the hovering gesture.
+    processMove(mapper, RAW_X_MAX - 1, RAW_Y_MAX - 1);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)));
+
+    // Moving outside the physical frame does not produce events.
+    processMove(mapper, RAW_X_MAX - 2, RAW_Y_MAX - 2);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+    // Re-entering the physical frame produces events.
+    processMove(mapper, RAW_X_MIN, RAW_Y_MIN);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)));
+}
+
 // --- TouchDisplayProjectionTest ---
 
 class TouchDisplayProjectionTest : public SingleTouchInputMapperTest {
diff --git a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
index 0d5f30c..28873a3 100644
--- a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
@@ -52,13 +52,13 @@
                 [&]() -> void { mapper.getSources(); },
                 [&]() -> void {
                     std::list<NotifyArgs> unused =
-                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), &policyConfig,
+                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
                                                fdp->ConsumeIntegral<int32_t>());
                 },
                 [&]() -> void {
                     // Need to reconfigure with 0 or you risk a NPE.
                     std::list<NotifyArgs> unused =
-                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), &policyConfig, 0);
+                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig, 0);
                     InputDeviceInfo info;
                     mapper.populateDeviceInfo(info);
                 },
@@ -71,7 +71,7 @@
 
                     // Need to reconfigure with 0 or you risk a NPE.
                     std::list<NotifyArgs> unused =
-                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), &policyConfig, 0);
+                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig, 0);
                     RawEvent rawEvent{fdp->ConsumeIntegral<nsecs_t>(),
                                       fdp->ConsumeIntegral<nsecs_t>(),
                                       fdp->ConsumeIntegral<int32_t>(),
@@ -90,7 +90,7 @@
                 [&]() -> void {
                     // Need to reconfigure with 0 or you risk a NPE.
                     std::list<NotifyArgs> unused =
-                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), &policyConfig, 0);
+                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig, 0);
                     mapper.getAssociatedDisplayId();
                 },
         })();
diff --git a/services/inputflinger/tests/fuzzers/FuzzContainer.h b/services/inputflinger/tests/fuzzers/FuzzContainer.h
index 76d2bcd..d42d11c 100644
--- a/services/inputflinger/tests/fuzzers/FuzzContainer.h
+++ b/services/inputflinger/tests/fuzzers/FuzzContainer.h
@@ -59,7 +59,7 @@
     void configureDevice() {
         nsecs_t arbitraryTime = mFdp->ConsumeIntegral<nsecs_t>();
         std::list<NotifyArgs> out;
-        out += mFuzzDevice->configure(arbitraryTime, &mPolicyConfig, 0);
+        out += mFuzzDevice->configure(arbitraryTime, mPolicyConfig, 0);
         out += mFuzzDevice->reset(arbitraryTime);
         for (const NotifyArgs& args : out) {
             mFuzzListener.notify(args);
diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
index 14cb8a5..00b44b5 100644
--- a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
@@ -64,7 +64,7 @@
                 [&]() -> void { mapper.getSources(); },
                 [&]() -> void {
                     std::list<NotifyArgs> unused =
-                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), &policyConfig,
+                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
                                                fdp->ConsumeIntegral<uint32_t>());
                 },
                 [&]() -> void {
diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
index 8352a90..70908ff 100644
--- a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
@@ -79,7 +79,7 @@
                 [&]() -> void { mapper.getSources(); },
                 [&]() -> void {
                     std::list<NotifyArgs> unused =
-                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), &policyConfig,
+                            mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
                                                fdp->ConsumeIntegral<uint32_t>());
                 },
                 [&]() -> void {
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index e720af5..d64231f 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -1574,9 +1574,10 @@
 }
 
 void Output::renderCachedSets(const CompositionRefreshArgs& refreshArgs) {
-    if (mPlanner) {
-        mPlanner->renderCachedSets(getState(), refreshArgs.scheduledFrameTime,
-                                   getState().usesDeviceComposition || getSkipColorTransform());
+    const auto& outputState = getState();
+    if (mPlanner && outputState.isEnabled) {
+        mPlanner->renderCachedSets(outputState, refreshArgs.scheduledFrameTime,
+                                   outputState.usesDeviceComposition || getSkipColorTransform());
     }
 }
 
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
index c30465f..5913d4b 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -30,7 +30,7 @@
     auto lhsLayer = lhs.first->getLayer();
     auto rhsLayer = rhs.first->getLayer();
     if (lhsLayer->layerStack.id != rhsLayer->layerStack.id) {
-        return lhsLayer->layerStack.id > rhsLayer->layerStack.id;
+        return lhsLayer->layerStack.id < rhsLayer->layerStack.id;
     }
     if (lhsLayer->z != rhsLayer->z) {
         return lhsLayer->z < rhsLayer->z;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 25cbe7a..ce7d37e 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -817,7 +817,8 @@
         snapshot.frameRateSelectionPriority = requested.frameRateSelectionPriority;
     }
 
-    if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Content)) {
+    if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Content) ||
+        snapshot.changes.any(RequestedLayerState::Changes::AffectsChildren)) {
         snapshot.color.rgb = requested.getColor().rgb;
         snapshot.isColorspaceAgnostic = requested.colorSpaceAgnostic;
         snapshot.backgroundBlurRadius = args.supportsBlur
@@ -1069,6 +1070,10 @@
     // touches from going outside the cloned area.
     if (path.isClone()) {
         snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::CLONE;
+        // Cloned layers shouldn't handle watch outside since their z order is not determined by
+        // WM or the client.
+        snapshot.inputInfo.inputConfig.clear(gui::WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH);
+
         mNeedsTouchableRegionCrop.insert(path);
     }
 }
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 4dcdd96..b397b82 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -32,10 +32,6 @@
 using namespace ftl::flag_operators;
 
 namespace {
-std::string layerIdToString(uint32_t layerId) {
-    return layerId == UNASSIGNED_LAYER_ID ? "none" : std::to_string(layerId);
-}
-
 std::string layerIdsToString(const std::vector<uint32_t>& layerIds) {
     std::stringstream stream;
     stream << "{";
@@ -326,9 +322,13 @@
 
 std::string RequestedLayerState::getDebugString() const {
     std::stringstream debug;
-    debug << "RequestedLayerState{" << name << " parent=" << layerIdToString(parentId)
-          << " relativeParent=" << layerIdToString(relativeParentId)
-          << " mirrorId=" << layerIdsToString(mirrorIds) << " handle=" << handleAlive << " z=" << z;
+    debug << "RequestedLayerState{" << name;
+    if (parentId != UNASSIGNED_LAYER_ID) debug << " parentId=" << parentId;
+    if (relativeParentId != UNASSIGNED_LAYER_ID) debug << " relativeParentId=" << relativeParentId;
+    if (!mirrorIds.empty()) debug << " mirrorId=" << layerIdsToString(mirrorIds);
+    if (!handleAlive) debug << " !handle";
+    if (z != 0) debug << " z=" << z;
+    if (layerStack.id != 0) debug << " layerStack=" << layerStack.id;
     return debug.str();
 }
 
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 5a90d58..bae3739 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -78,17 +78,16 @@
                                           .count();
 }
 
-bool LayerInfo::isFrequent(nsecs_t now) const {
-    using fps_approx_ops::operator>=;
+LayerInfo::Frequent LayerInfo::isFrequent(nsecs_t now) const {
     // If we know nothing about this layer (e.g. after touch event),
     // we consider it as frequent as it might be the start of an animation.
     if (mFrameTimes.size() < kFrequentLayerWindowSize) {
-        return true;
+        return {/* isFrequent */ true, /* clearHistory */ false, /* isConclusive */ true};
     }
 
     // Non-active layers are also infrequent
     if (mLastUpdatedTime < getActiveLayerThreshold(now)) {
-        return false;
+        return {/* isFrequent */ false, /* clearHistory */ false, /* isConclusive */ true};
     }
 
     // We check whether we can classify this layer as frequent or infrequent:
@@ -111,12 +110,20 @@
     }
 
     if (isFrequent || isInfrequent) {
-        return isFrequent;
+        // If the layer was previously inconclusive, we clear
+        // the history as indeterminate layers changed to frequent,
+        // and we should not look at the stale data.
+        return {isFrequent, isFrequent && !mIsFrequencyConclusive, /* isConclusive */ true};
     }
 
     // If we can't determine whether the layer is frequent or not, we return
-    // the last known classification.
-    return !mLastRefreshRate.infrequent;
+    // the last known classification and mark the layer frequency as inconclusive.
+    isFrequent = !mLastRefreshRate.infrequent;
+
+    // If the layer was previously tagged as animating, we clear
+    // the history as it is likely the layer just changed its behavior,
+    // and we should not look at stale data.
+    return {isFrequent, isFrequent && mLastRefreshRate.animating, /* isConclusive */ false};
 }
 
 Fps LayerInfo::getFps(nsecs_t now) const {
@@ -273,19 +280,18 @@
         return {LayerHistory::LayerVoteType::Max, Fps()};
     }
 
-    if (!isFrequent(now)) {
+    const LayerInfo::Frequent frequent = isFrequent(now);
+    mIsFrequencyConclusive = frequent.isConclusive;
+    if (!frequent.isFrequent) {
         ATRACE_FORMAT_INSTANT("infrequent");
         ALOGV("%s is infrequent", mName.c_str());
         mLastRefreshRate.infrequent = true;
-        // Infrequent layers vote for mininal refresh rate for
+        // Infrequent layers vote for minimal refresh rate for
         // battery saving purposes and also to prevent b/135718869.
         return {LayerHistory::LayerVoteType::Min, Fps()};
     }
 
-    // If the layer was previously tagged as animating or infrequent, we clear
-    // the history as it is likely the layer just changed its behavior
-    // and we should not look at stale data
-    if (mLastRefreshRate.animating || mLastRefreshRate.infrequent) {
+    if (frequent.clearHistory) {
         clearHistory(now);
     }
 
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index a3523ac..c5a6057 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -181,6 +181,7 @@
         mFrameTimeValidSince = std::chrono::time_point<std::chrono::steady_clock>(timePoint);
         mLastRefreshRate = {};
         mRefreshRateHistory.clear();
+        mIsFrequencyConclusive = true;
     }
 
     void clearHistory(nsecs_t now) {
@@ -251,7 +252,15 @@
         static constexpr float MARGIN_CONSISTENT_FPS = 1.0;
     };
 
-    bool isFrequent(nsecs_t now) const;
+    // Represents whether we were able to determine either layer is frequent or infrequent
+    bool mIsFrequencyConclusive = true;
+    struct Frequent {
+        bool isFrequent;
+        bool clearHistory;
+        // Represents whether we were able to determine isFrequent conclusively
+        bool isConclusive;
+    };
+    Frequent isFrequent(nsecs_t now) const;
     bool isAnimating(nsecs_t now) const;
     bool hasEnoughDataForHeuristic() const;
     std::optional<Fps> calculateRefreshRateIfPossible(const RefreshRateSelector&, nsecs_t now);
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 3e12db6..8ddcfa1 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -130,7 +130,7 @@
 
         pacesetterVsyncSchedule = promotePacesetterDisplayLocked();
     }
-    applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule));
+    applyNewVsyncScheduleIfNonNull(std::move(pacesetterVsyncSchedule));
 }
 
 void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) {
@@ -149,7 +149,7 @@
 
         pacesetterVsyncSchedule = promotePacesetterDisplayLocked();
     }
-    applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule));
+    applyNewVsyncScheduleIfNonNull(std::move(pacesetterVsyncSchedule));
 }
 
 void Scheduler::run() {
@@ -693,16 +693,17 @@
         pacesetterVsyncSchedule = promotePacesetterDisplayLocked(pacesetterIdOpt);
     }
 
-    applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule));
+    applyNewVsyncScheduleIfNonNull(std::move(pacesetterVsyncSchedule));
 }
 
 std::shared_ptr<VsyncSchedule> Scheduler::promotePacesetterDisplayLocked(
         std::optional<PhysicalDisplayId> pacesetterIdOpt) {
     // TODO(b/241286431): Choose the pacesetter display.
+    const auto oldPacesetterDisplayIdOpt = mPacesetterDisplayId;
     mPacesetterDisplayId = pacesetterIdOpt.value_or(mRefreshRateSelectors.begin()->first);
     ALOGI("Display %s is the pacesetter", to_string(*mPacesetterDisplayId).c_str());
 
-    auto vsyncSchedule = getVsyncScheduleLocked(*mPacesetterDisplayId);
+    auto newVsyncSchedule = getVsyncScheduleLocked(*mPacesetterDisplayId);
     if (const auto pacesetterPtr = pacesetterSelectorPtrLocked()) {
         pacesetterPtr->setIdleTimerCallbacks(
                 {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); },
@@ -713,15 +714,28 @@
 
         pacesetterPtr->startIdleTimer();
 
+        // Track the new period, which may have changed due to switching to a
+        // new pacesetter or due to a hotplug event. In the former case, this
+        // is important so that VSYNC modulation does not get stuck in the
+        // initiated state if a transition started on the old pacesetter.
         const Fps refreshRate = pacesetterPtr->getActiveMode().modePtr->getFps();
-        vsyncSchedule->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod(),
-                                             true /* force */);
+        newVsyncSchedule->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod(),
+                                                true /* force */);
     }
-    return vsyncSchedule;
+    if (oldPacesetterDisplayIdOpt == mPacesetterDisplayId) {
+        return nullptr;
+    }
+    return newVsyncSchedule;
 }
 
-void Scheduler::applyNewVsyncSchedule(std::shared_ptr<VsyncSchedule> vsyncSchedule) {
-    onNewVsyncSchedule(vsyncSchedule->getDispatch());
+void Scheduler::applyNewVsyncScheduleIfNonNull(
+        std::shared_ptr<VsyncSchedule> pacesetterSchedulePtr) {
+    if (!pacesetterSchedulePtr) {
+        // The pacesetter has not changed, so there is no new VsyncSchedule to
+        // apply.
+        return;
+    }
+    onNewVsyncSchedule(pacesetterSchedulePtr->getDispatch());
     std::vector<android::EventThread*> threads;
     {
         std::lock_guard<std::mutex> lock(mConnectionsLock);
@@ -731,7 +745,7 @@
         }
     }
     for (auto* thread : threads) {
-        thread->onNewVsyncSchedule(vsyncSchedule);
+        thread->onNewVsyncSchedule(pacesetterSchedulePtr);
     }
 }
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 3423652..720a1cb 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -329,10 +329,12 @@
     // MessageQueue and EventThread need to use the new pacesetter's
     // VsyncSchedule, and this must happen while mDisplayLock is *not* locked,
     // or else we may deadlock with EventThread.
+    // Returns the new pacesetter's VsyncSchedule, or null if the pacesetter is
+    // unchanged.
     std::shared_ptr<VsyncSchedule> promotePacesetterDisplayLocked(
             std::optional<PhysicalDisplayId> pacesetterIdOpt = std::nullopt)
             REQUIRES(kMainThreadContext, mDisplayLock);
-    void applyNewVsyncSchedule(std::shared_ptr<VsyncSchedule>) EXCLUDES(mDisplayLock);
+    void applyNewVsyncScheduleIfNonNull(std::shared_ptr<VsyncSchedule>) EXCLUDES(mDisplayLock);
 
     // Blocks until the pacesetter's idle timer thread exits. `mDisplayLock` must not be locked by
     // the caller on the main thread to avoid deadlock, since the timer thread locks it before exit.
diff --git a/services/surfaceflinger/TEST_MAPPING b/services/surfaceflinger/TEST_MAPPING
index 57752b7..155a275 100644
--- a/services/surfaceflinger/TEST_MAPPING
+++ b/services/surfaceflinger/TEST_MAPPING
@@ -7,6 +7,14 @@
       "name": "libcompositionengine_test"
     },
     {
+      "name": "libgui_test",
+      "options": [
+        {
+          "native-test-flag": "--gtest_filter=\"InputSurfacesTest*:MultiDisplayTests*\""
+        }
+      ]
+    },
+    {
       "name": "libscheduler_test"
     }
   ],
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
index ddf3363..88d39db 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
@@ -662,10 +662,9 @@
     mLifecycleManager.commitChanges();
     LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
     UPDATE_AND_VERIFY(hierarchyBuilder);
-    std::vector<uint32_t> expectedTraversalPath = {1, 11, 2, 21};
+    std::vector<uint32_t> expectedTraversalPath = {2, 21, 1, 11};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
 
-    expectedTraversalPath = {1, 11, 2, 21};
     EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
     expectedTraversalPath = {};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
@@ -678,8 +677,8 @@
     setLayerStack(3, 1);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
-    std::vector<uint32_t> expected = {3, 1,  11,  111, 12,  121, 122,  1221, 13, 2,
-                                      1, 11, 111, 12,  121, 122, 1221, 13,   2};
+    std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3,
+                                      1, 11, 111, 12, 121, 122, 1221, 13, 2};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
     EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
     expected = {};
@@ -693,7 +692,7 @@
     setLayerStack(3, 1);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
-    std::vector<uint32_t> expected = {3, 1, 11, 111, 12, 121, 122, 1221, 13, 2};
+    std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
     EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
     expected = {};
@@ -710,8 +709,8 @@
     createRootLayer(4);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
-    std::vector<uint32_t> expected = {3, 1,  11,  111, 12,  121, 122,  1221, 13, 2, 4,
-                                      1, 11, 111, 12,  121, 122, 1221, 13,   2,  4};
+    std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 4, 3,
+                                      1, 11, 111, 12, 121, 122, 1221, 13, 2, 4};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
     EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
     expected = {};
@@ -729,7 +728,7 @@
     destroyLayerHandle(1);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
-    std::vector<uint32_t> expected = {3, 2, 2};
+    std::vector<uint32_t> expected = {2, 3, 2};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
     EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
     expected = {11, 111, 12, 121, 122, 1221, 13};
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index 79cfd6a..4301186 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -297,6 +297,17 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
+    void setBackgroundBlurRadius(uint32_t id, uint32_t backgroundBlurRadius) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eBackgroundBlurRadiusChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.backgroundBlurRadius = backgroundBlurRadius;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
     LayerLifecycleManager mLifecycleManager;
 };
 
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index b767276..85d86a7 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -84,7 +84,7 @@
     auto frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
         const auto& infos = history().mActiveLayerInfos;
         return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) {
-            return pair.second.second->isFrequent(now);
+            return pair.second.second->isFrequent(now).isFrequent;
         });
     }
 
@@ -95,6 +95,13 @@
         });
     }
 
+    auto clearLayerHistoryCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+        const auto& infos = history().mActiveLayerInfos;
+        return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) {
+            return pair.second.second->isFrequent(now).clearHistory;
+        });
+    }
+
     void setDefaultLayerVote(Layer* layer,
                              LayerHistory::LayerVoteType vote) NO_THREAD_SAFETY_ANALYSIS {
         auto [found, layerPair] = history().findLayer(layer->getSequence());
@@ -764,6 +771,7 @@
     time += std::chrono::nanoseconds(3s).count();
     history().record(layer->getSequence(), layer->getLayerProps(), time, time,
                      LayerHistory::LayerUpdateType::Buffer);
+    EXPECT_EQ(0, clearLayerHistoryCount(time));
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summarizeLayerHistory(time)[0].vote);
     EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
@@ -778,6 +786,7 @@
     time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count();
     history().record(layer->getSequence(), layer->getLayerProps(), time, time,
                      LayerHistory::LayerUpdateType::Buffer);
+    EXPECT_EQ(0, clearLayerHistoryCount(time));
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
     EXPECT_EQ(1, activeLayerCount());
@@ -787,6 +796,7 @@
     // posting another buffer should keep the layer infrequent
     history().record(layer->getSequence(), layer->getLayerProps(), time, time,
                      LayerHistory::LayerUpdateType::Buffer);
+    EXPECT_EQ(0, clearLayerHistoryCount(time));
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
     EXPECT_EQ(1, activeLayerCount());
@@ -798,6 +808,7 @@
                      LayerHistory::LayerUpdateType::Buffer);
     history().record(layer->getSequence(), layer->getLayerProps(), time, time,
                      LayerHistory::LayerUpdateType::Buffer);
+    EXPECT_EQ(1, clearLayerHistoryCount(time));
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
     EXPECT_EQ(1, activeLayerCount());
@@ -808,6 +819,7 @@
     time += std::chrono::nanoseconds(3s).count();
     history().record(layer->getSequence(), layer->getLayerProps(), time, time,
                      LayerHistory::LayerUpdateType::Buffer);
+    EXPECT_EQ(0, clearLayerHistoryCount(time));
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
     EXPECT_EQ(1, activeLayerCount());
@@ -818,6 +830,64 @@
     time += (60_Hz).getPeriodNsecs();
     history().record(layer->getSequence(), layer->getLayerProps(), time, time,
                      LayerHistory::LayerUpdateType::Buffer);
+    EXPECT_EQ(0, clearLayerHistoryCount(time));
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, inconclusiveLayerBecomingFrequent) {
+    auto layer = createLayer();
+
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // Fill up the window with frequent updates
+    for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE; i++) {
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        time += (60_Hz).getPeriodNsecs();
+
+        EXPECT_EQ(1, layerCount());
+        ASSERT_EQ(1, summarizeLayerHistory(time).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+        EXPECT_EQ(1, activeLayerCount());
+        EXPECT_EQ(1, frequentLayerCount(time));
+    }
+
+    // posting infrequent buffers after long inactivity should make the layer
+    // inconclusive but frequent.
+    time += std::chrono::nanoseconds(3s).count();
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
+    time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count();
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
+    EXPECT_EQ(0, clearLayerHistoryCount(time));
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // posting more buffers should make the layer frequent and switch the refresh rate to max
+    // by clearing the history
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                     LayerHistory::LayerUpdateType::Buffer);
+    EXPECT_EQ(1, clearLayerHistoryCount(time));
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
     EXPECT_EQ(1, activeLayerCount());
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 5a066a6..b8a7446 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -79,6 +79,7 @@
                                         .displays = mFrontEndDisplayInfos,
                                         .displayChanges = hasDisplayChanges,
                                         .globalShadowSettings = globalShadowSettings,
+                                        .supportsBlur = true,
                                         .supportedLayerGenericMetadata = {},
                                         .genericLayerMetadataKeyMap = {}};
         actualBuilder.update(args);
@@ -333,6 +334,19 @@
     EXPECT_EQ(getSnapshot({.id = 111})->inputInfo.touchableRegion.bounds(), modifiedTouchCrop);
 }
 
+TEST_F(LayerSnapshotTest, blurUpdatesWhenAlphaChanges) {
+    static constexpr int blurRadius = 42;
+    setBackgroundBlurRadius(1221, blurRadius);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1221})->backgroundBlurRadius, blurRadius);
+
+    static constexpr float alpha = 0.5;
+    setAlpha(12, alpha);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1221})->backgroundBlurRadius, blurRadius * alpha);
+}
+
 // Display Mirroring Tests
 // tree with 3 levels of children
 // ROOT (DISPLAY 0)
@@ -352,7 +366,7 @@
     createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
     setLayerStack(3, 1);
 
-    std::vector<uint32_t> expected = {3, 1, 11, 111, 13, 2, 1, 11, 111, 12, 121, 122, 1221, 13, 2};
+    std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3, 1, 11, 111, 13, 2};
     UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
 }
 
@@ -371,8 +385,8 @@
     createDisplayMirrorLayer(4, ui::LayerStack::fromValue(0));
     setLayerStack(4, 4);
 
-    std::vector<uint32_t> expected = {4,   1,  11, 111, 13, 2,   3,  1, 11,
-                                      111, 13, 2,  1,   11, 111, 13, 2};
+    std::vector<uint32_t> expected = {1,  11, 111, 13, 2,  3,   1,  11, 111,
+                                      13, 2,  4,   1,  11, 111, 13, 2};
     UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
     EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootId = 3})->outputFilter.layerStack.id, 3u);
     EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootId = 4})->outputFilter.layerStack.id, 4u);
@@ -395,7 +409,7 @@
     setCrop(111, Rect{200, 200});
     Region touch{Rect{0, 0, 1000, 1000}};
     setTouchableRegion(111, touch);
-    std::vector<uint32_t> expected = {3, 1, 11, 111, 13, 2, 1, 11, 111, 13, 2};
+    std::vector<uint32_t> expected = {1, 11, 111, 13, 2, 3, 1, 11, 111, 13, 2};
     UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
     EXPECT_TRUE(getSnapshot({.id = 111})->inputInfo.touchableRegion.hasSameRects(touch));
     Region touchCroppedByMirrorRoot{Rect{0, 0, 50, 50}};
@@ -407,7 +421,7 @@
     setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
     createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
     setLayerStack(3, 1);
-    std::vector<uint32_t> expected = {3, 1, 11, 111, 13, 2, 1, 11, 111, 12, 121, 122, 1221, 13, 2};
+    std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3, 1, 11, 111, 13, 2};
     UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
     destroyLayerHandle(3);
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
@@ -417,8 +431,8 @@
     size_t startingNumSnapshots = mSnapshotBuilder.getSnapshots().size();
     createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
     setLayerStack(3, 1);
-    std::vector<uint32_t> expected = {3, 1,  11,  111, 12,  121, 122,  1221, 13, 2,
-                                      1, 11, 111, 12,  121, 122, 1221, 13,   2};
+    std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3,
+                                      1, 11, 111, 12, 121, 122, 1221, 13, 2};
     UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
     destroyLayerHandle(3);
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index dc76b4c..0c43831 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -384,4 +384,23 @@
     }
 }
 
+TEST_F(SchedulerTest, changingPacesetterChangesVsyncSchedule) {
+    // Add a second display so we can change the pacesetter.
+    mScheduler->registerDisplay(kDisplayId2,
+                                std::make_shared<RefreshRateSelector>(kDisplay2Modes,
+                                                                      kDisplay2Mode60->getId()));
+    // Ensure that the pacesetter is the one we expect.
+    mScheduler->setPacesetterDisplay(kDisplayId1);
+
+    // Switching to the other will call onNewVsyncSchedule.
+    EXPECT_CALL(*mEventThread, onNewVsyncSchedule(mScheduler->getVsyncSchedule(kDisplayId2)))
+            .Times(1);
+    mScheduler->setPacesetterDisplay(kDisplayId2);
+}
+
+TEST_F(SchedulerTest, promotingSamePacesetterDoesNotChangeVsyncSchedule) {
+    EXPECT_CALL(*mEventThread, onNewVsyncSchedule(_)).Times(0);
+    mScheduler->setPacesetterDisplay(kDisplayId1);
+}
+
 } // namespace android::scheduler