Merge "jpegrecoverymap: Fix XMP metadata test"
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index bdd5172..a737bd3 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -167,6 +167,12 @@
 }
 
 prebuilt_etc {
+    name: "android.hardware.telephony.satellite.prebuilt.xml",
+    src: "android.hardware.telephony.satellite.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.hardware.usb.accessory.prebuilt.xml",
     src: "android.hardware.usb.accessory.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/data/etc/android.hardware.telephony.satellite.xml b/data/etc/android.hardware.telephony.satellite.xml
new file mode 100644
index 0000000..5966cba
--- /dev/null
+++ b/data/etc/android.hardware.telephony.satellite.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Feature for devices that support Satellite communication via Satellite HAL APIs. -->
+<permissions>
+    <feature name="android.hardware.telephony.satellite" />
+</permissions>
diff --git a/include/input/Input.h b/include/input/Input.h
index 62d84e1..e281675 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -1132,6 +1132,7 @@
     TYPE_ZOOM_OUT = 1019,
     TYPE_GRAB = 1020,
     TYPE_GRABBING = 1021,
+    TYPE_HANDWRITING = 1022,
 
     TYPE_SPOT_HOVER = 2000,
     TYPE_SPOT_TOUCH = 2001,
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 09933d3..66d3435 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -214,6 +214,12 @@
     std::string layoutType;
 };
 
+// The version of the Universal Stylus Initiative (USI) protocol supported by the input device.
+struct InputDeviceUsiVersion {
+    int32_t majorVersion = -1;
+    int32_t minorVersion = -1;
+};
+
 /*
  * Describes the characteristics and capabilities of an input device.
  */
@@ -235,7 +241,7 @@
 
     void initialize(int32_t id, int32_t generation, int32_t controllerNumber,
                     const InputDeviceIdentifier& identifier, const std::string& alias,
-                    bool isExternal, bool hasMic);
+                    bool isExternal, bool hasMic, int32_t associatedDisplayId);
 
     inline int32_t getId() const { return mId; }
     inline int32_t getControllerNumber() const { return mControllerNumber; }
@@ -295,8 +301,12 @@
 
     std::vector<InputDeviceLightInfo> getLights();
 
-    inline void setSupportsUsi(bool supportsUsi) { mSupportsUsi = supportsUsi; }
-    inline bool supportsUsi() const { return mSupportsUsi; }
+    inline void setUsiVersion(std::optional<InputDeviceUsiVersion> usiVersion) {
+        mUsiVersion = std::move(usiVersion);
+    }
+    inline std::optional<InputDeviceUsiVersion> getUsiVersion() const { return mUsiVersion; }
+
+    inline int32_t getAssociatedDisplayId() const { return mAssociatedDisplayId; }
 
 private:
     int32_t mId;
@@ -310,8 +320,8 @@
     uint32_t mSources;
     int32_t mKeyboardType;
     std::shared_ptr<KeyCharacterMap> mKeyCharacterMap;
-    // Whether this device supports the Universal Stylus Initiative (USI) protocol for styluses.
-    bool mSupportsUsi;
+    std::optional<InputDeviceUsiVersion> mUsiVersion;
+    int32_t mAssociatedDisplayId;
 
     bool mHasVibrator;
     bool mHasBattery;
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 19445d1..808b1ec 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -76,7 +76,6 @@
 
     srcs: [
         "Binder.cpp",
-        "BinderRecordReplay.cpp",
         "BpBinder.cpp",
         "Debug.cpp",
         "FdTrigger.cpp",
@@ -84,6 +83,7 @@
         "IResultReceiver.cpp",
         "Parcel.cpp",
         "ParcelFileDescriptor.cpp",
+        "RecordedTransaction.cpp",
         "RpcSession.cpp",
         "RpcServer.cpp",
         "RpcState.cpp",
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index 5fa8bfa..3e49656 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -21,13 +21,13 @@
 
 #include <android-base/logging.h>
 #include <android-base/unique_fd.h>
-#include <binder/BinderRecordReplay.h>
 #include <binder/BpBinder.h>
 #include <binder/IInterface.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IResultReceiver.h>
 #include <binder/IShellCallback.h>
 #include <binder/Parcel.h>
+#include <binder/RecordedTransaction.h>
 #include <binder/RpcServer.h>
 #include <cutils/compiler.h>
 #include <private/android_filesystem_config.h>
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index 1c470a1..d03326e 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -388,7 +388,7 @@
 {
     if (isRpcBinder()) {
         if (rpcSession()->getMaxIncomingThreads() < 1) {
-            LOG_ALWAYS_FATAL("Cannot register a DeathRecipient without any incoming connections.");
+            ALOGE("Cannot register a DeathRecipient without any incoming connections.");
             return INVALID_OPERATION;
         }
     } else if constexpr (!kEnableKernelIpc) {
diff --git a/libs/binder/BinderRecordReplay.cpp b/libs/binder/RecordedTransaction.cpp
similarity index 99%
rename from libs/binder/BinderRecordReplay.cpp
rename to libs/binder/RecordedTransaction.cpp
index 389c49e..5406205 100644
--- a/libs/binder/BinderRecordReplay.cpp
+++ b/libs/binder/RecordedTransaction.cpp
@@ -17,7 +17,7 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/unique_fd.h>
-#include <binder/BinderRecordReplay.h>
+#include <binder/RecordedTransaction.h>
 #include <sys/mman.h>
 #include <algorithm>
 
diff --git a/libs/binder/include/binder/Binder.h b/libs/binder/include/binder/Binder.h
index 08dbd13..d960a0b 100644
--- a/libs/binder/include/binder/Binder.h
+++ b/libs/binder/include/binder/Binder.h
@@ -106,7 +106,7 @@
                                              const sp<IBinder>& keepAliveBinder);
 
     // Start recording transactions to the unique_fd in data.
-    // See BinderRecordReplay.h for more details.
+    // See RecordedTransaction.h for more details.
     [[nodiscard]] status_t startRecordingTransactions(const Parcel& data);
     // Stop the current recording.
     [[nodiscard]] status_t stopRecordingTransactions();
diff --git a/libs/binder/include/binder/BpBinder.h b/libs/binder/include/binder/BpBinder.h
index 57e103d..5496d61 100644
--- a/libs/binder/include/binder/BpBinder.h
+++ b/libs/binder/include/binder/BpBinder.h
@@ -91,7 +91,7 @@
     std::optional<int32_t> getDebugBinderHandle() const;
 
     // Start recording transactions to the unique_fd.
-    // See BinderRecordReplay.h for more details.
+    // See RecordedTransaction.h for more details.
     status_t startRecordingBinder(const android::base::unique_fd& fd);
     // Stop the current recording.
     status_t stopRecordingBinder();
diff --git a/libs/binder/include/binder/BinderRecordReplay.h b/libs/binder/include/binder/RecordedTransaction.h
similarity index 98%
rename from libs/binder/include/binder/BinderRecordReplay.h
rename to libs/binder/include/binder/RecordedTransaction.h
index 9602c9f..4966330 100644
--- a/libs/binder/include/binder/BinderRecordReplay.h
+++ b/libs/binder/include/binder/RecordedTransaction.h
@@ -26,7 +26,7 @@
 
 // Warning: Transactions are sequentially recorded to the file descriptor in a
 // non-stable format. A detailed description of the recording format can be found in
-// BinderRecordReplay.cpp.
+// RecordedTransaction.cpp.
 
 class RecordedTransaction {
 public:
diff --git a/libs/binder/tests/binderRecordedTransactionTest.cpp b/libs/binder/tests/binderRecordedTransactionTest.cpp
index d900e86..2f5c8c6 100644
--- a/libs/binder/tests/binderRecordedTransactionTest.cpp
+++ b/libs/binder/tests/binderRecordedTransactionTest.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include <binder/BinderRecordReplay.h>
+#include <binder/RecordedTransaction.h>
 #include <gtest/gtest.h>
 #include <utils/Errors.h>
 
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 9be5b87..36c8d8c 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -683,7 +683,7 @@
     proc.expectAlreadyShutdown = true;
 }
 
-TEST_P(BinderRpc, DeathRecipientFatalWithoutIncoming) {
+TEST_P(BinderRpc, DeathRecipientFailsWithoutIncoming) {
     class MyDeathRec : public IBinder::DeathRecipient {
     public:
         void binderDied(const wp<IBinder>& /* who */) override {}
@@ -693,8 +693,7 @@
             {.numThreads = 1, .numSessions = 1, .numIncomingConnections = 0});
 
     auto dr = sp<MyDeathRec>::make();
-    EXPECT_DEATH(proc.rootBinder->linkToDeath(dr, (void*)1, 0),
-                 "Cannot register a DeathRecipient without any incoming connections.");
+    EXPECT_EQ(INVALID_OPERATION, proc.rootBinder->linkToDeath(dr, (void*)1, 0));
 }
 
 TEST_P(BinderRpc, UnlinkDeathRecipient) {
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index 87333f2..9c7c0c1 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -22,6 +22,7 @@
 
 #include <android-base/stringprintf.h>
 #include <ftl/enum.h>
+#include <gui/constants.h>
 #include <input/InputDevice.h>
 #include <input/InputEventLabels.h>
 
@@ -166,7 +167,7 @@
 // --- InputDeviceInfo ---
 
 InputDeviceInfo::InputDeviceInfo() {
-    initialize(-1, 0, -1, InputDeviceIdentifier(), "", false, false);
+    initialize(-1, 0, -1, InputDeviceIdentifier(), "", false, false, ADISPLAY_ID_NONE);
 }
 
 InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other)
@@ -181,7 +182,8 @@
         mSources(other.mSources),
         mKeyboardType(other.mKeyboardType),
         mKeyCharacterMap(other.mKeyCharacterMap),
-        mSupportsUsi(other.mSupportsUsi),
+        mUsiVersion(other.mUsiVersion),
+        mAssociatedDisplayId(other.mAssociatedDisplayId),
         mHasVibrator(other.mHasVibrator),
         mHasBattery(other.mHasBattery),
         mHasButtonUnderPad(other.mHasButtonUnderPad),
@@ -195,7 +197,7 @@
 
 void InputDeviceInfo::initialize(int32_t id, int32_t generation, int32_t controllerNumber,
                                  const InputDeviceIdentifier& identifier, const std::string& alias,
-                                 bool isExternal, bool hasMic) {
+                                 bool isExternal, bool hasMic, int32_t associatedDisplayId) {
     mId = id;
     mGeneration = generation;
     mControllerNumber = controllerNumber;
@@ -205,11 +207,12 @@
     mHasMic = hasMic;
     mSources = 0;
     mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
+    mAssociatedDisplayId = associatedDisplayId;
     mHasVibrator = false;
     mHasBattery = false;
     mHasButtonUnderPad = false;
     mHasSensor = false;
-    mSupportsUsi = false;
+    mUsiVersion.reset();
     mMotionRanges.clear();
     mSensors.clear();
     mLights.clear();
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp
index c9bed70..01318dc 100644
--- a/libs/jpegrecoverymap/Android.bp
+++ b/libs/jpegrecoverymap/Android.bp
@@ -39,6 +39,7 @@
         "libjpeg",
         "libjpegencoder",
         "libjpegdecoder",
+        "liblog",
     ],
 }
 
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
index 2f9799d..0695bb7 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
@@ -17,12 +17,15 @@
 #ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
 #define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
 
+#include <cmath>
 #include <stdint.h>
 
 #include <jpegrecoverymap/recoverymap.h>
 
 namespace android::recoverymap {
 
+#define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x)
+
 ////////////////////////////////////////////////////////////////////////////////
 // Framework
 
@@ -112,6 +115,31 @@
   return temp /= rhs;
 }
 
+constexpr size_t kRecoveryFactorPrecision = 10;
+constexpr size_t kRecoveryFactorNumEntries = 1 << kRecoveryFactorPrecision;
+struct RecoveryLUT {
+  RecoveryLUT(float hdrRatio) {
+    float increment = 2.0 / kRecoveryFactorNumEntries;
+    float value = -1.0f;
+    for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++, value += increment) {
+      mRecoveryTable[idx] = pow(hdrRatio, value);
+    }
+  }
+
+  ~RecoveryLUT() {
+  }
+
+  float getRecoveryFactor(float recovery) {
+    uint32_t value = static_cast<uint32_t>(((recovery + 1.0f) / 2.0f) * kRecoveryFactorNumEntries);
+    //TODO() : Remove once conversion modules have appropriate clamping in place
+    value = CLIP3(value, 0, kRecoveryFactorNumEntries - 1);
+    return mRecoveryTable[value];
+  }
+
+private:
+  float mRecoveryTable[kRecoveryFactorNumEntries];
+};
+
 struct ShepardsIDW {
   ShepardsIDW(int mapScaleFactor) : mMapScaleFactor{mapScaleFactor} {
     const int size = mMapScaleFactor * mMapScaleFactor * 4;
@@ -158,7 +186,6 @@
   void fillShepardsIDW(float *weights, int incR, int incB);
 };
 
-
 ////////////////////////////////////////////////////////////////////////////////
 // sRGB transformations
 // NOTE: sRGB has the same color primaries as BT.709, but different transfer
@@ -189,7 +216,8 @@
  */
 float srgbInvOetf(float e_gamma);
 Color srgbInvOetf(Color e_gamma);
-
+float srgbInvOetfLUT(float e_gamma);
+Color srgbInvOetfLUT(Color e_gamma);
 
 ////////////////////////////////////////////////////////////////////////////////
 // Display-P3 transformations
@@ -229,6 +257,8 @@
  */
 float hlgOetf(float e);
 Color hlgOetf(Color e);
+float hlgOetfLUT(float e);
+Color hlgOetfLUT(Color e);
 
 /*
  * Convert from HLG to scene luminance.
@@ -237,6 +267,8 @@
  */
 float hlgInvOetf(float e_gamma);
 Color hlgInvOetf(Color e_gamma);
+float hlgInvOetfLUT(float e_gamma);
+Color hlgInvOetfLUT(Color e_gamma);
 
 /*
  * Convert from scene luminance to PQ.
@@ -245,6 +277,8 @@
  */
 float pqOetf(float e);
 Color pqOetf(Color e);
+float pqOetfLUT(float e);
+Color pqOetfLUT(Color e);
 
 /*
  * Convert from PQ to scene luminance in nits.
@@ -253,6 +287,8 @@
  */
 float pqInvOetf(float e_gamma);
 Color pqInvOetf(Color e_gamma);
+float pqInvOetfLUT(float e_gamma);
+Color pqInvOetfLUT(Color e_gamma);
 
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -297,6 +333,7 @@
  * value, with the given hdr ratio, to the given sdr input in the range [0, 1].
  */
 Color applyRecovery(Color e, float recovery, float hdr_ratio);
+Color applyRecoveryLUT(Color e, float recovery, RecoveryLUT& recoveryLUT);
 
 /*
  * Helper for sampling from YUV 420 images.
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index 22fa35b..eb557e5 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -25,17 +25,30 @@
 #include <image_io/jpeg/jpeg_scanner.h>
 #include <image_io/jpeg/jpeg_info_builder.h>
 #include <image_io/base/data_segment_data_source.h>
+#include <utils/Log.h>
 
 #include <memory>
 #include <sstream>
 #include <string>
 #include <cmath>
+#include <condition_variable>
+#include <deque>
+#include <mutex>
+#include <thread>
+#include <unistd.h>
 
 using namespace std;
 using namespace photos_editing_formats::image_io;
 
 namespace android::recoverymap {
 
+#define USE_SRGB_INVOETF_LUT 1
+#define USE_HLG_OETF_LUT 1
+#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 JPEGR_CHECK(x)          \
   {                             \
     status_t status = (x);      \
@@ -49,6 +62,10 @@
 
 // Map is quarter res / sixteenth size
 static const size_t kMapDimensionScaleFactor = 4;
+// JPEG block size.
+// 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
 static const int kMapCompressQuality = 85;
 
@@ -62,6 +79,20 @@
   1.0f,
 };
 
+#define CONFIG_MULTITHREAD 1
+int GetCPUCoreCount() {
+  int cpuCoreCount = 1;
+#if CONFIG_MULTITHREAD
+#if defined(_SC_NPROCESSORS_ONLN)
+  cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
+#else
+  // _SC_NPROC_ONLN must be defined...
+  cpuCoreCount = sysconf(_SC_NPROC_ONLN);
+#endif
+#endif
+  return cpuCoreCount;
+}
+
 /*
  * Helper function used for writing data to destination.
  *
@@ -230,6 +261,13 @@
     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;
+  }
+
   jpegr_metadata metadata;
   metadata.version = kJpegrVersion;
   metadata.transferFunction = hdr_tf;
@@ -304,6 +342,13 @@
     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;
+  }
+
   jpegr_metadata metadata;
   metadata.version = kJpegrVersion;
   metadata.transferFunction = hdr_tf;
@@ -369,6 +414,13 @@
     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;
+  }
+
   jpegr_metadata metadata;
   metadata.version = kJpegrVersion;
   metadata.transferFunction = hdr_tf;
@@ -444,6 +496,13 @@
     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;
+  }
+
   JpegDecoder jpeg_decoder;
   if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
     return ERROR_JPEGR_DECODE_ERROR;
@@ -626,6 +685,62 @@
   return NO_ERROR;
 }
 
+const int kJobSzInRows = 16;
+static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
+              "align job size to kMapDimensionScaleFactor");
+
+class JobQueue {
+ public:
+  bool dequeueJob(size_t& rowStart, size_t& rowEnd);
+  void enqueueJob(size_t rowStart, size_t rowEnd);
+  void markQueueForEnd();
+  void reset();
+
+ private:
+  bool mQueuedAllJobs = false;
+  std::deque<std::tuple<size_t, size_t>> mJobs;
+  std::mutex mMutex;
+  std::condition_variable mCv;
+};
+
+bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) {
+  std::unique_lock<std::mutex> lock{mMutex};
+  while (true) {
+    if (mJobs.empty()) {
+      if (mQueuedAllJobs) {
+        return false;
+      } else {
+        mCv.wait(lock);
+      }
+    } else {
+      auto it = mJobs.begin();
+      rowStart = std::get<0>(*it);
+      rowEnd = std::get<1>(*it);
+      mJobs.erase(it);
+      return true;
+    }
+  }
+  return false;
+}
+
+void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) {
+  std::unique_lock<std::mutex> lock{mMutex};
+  mJobs.push_back(std::make_tuple(rowStart, rowEnd));
+  lock.unlock();
+  mCv.notify_one();
+}
+
+void JobQueue::markQueueForEnd() {
+  std::unique_lock<std::mutex> lock{mMutex};
+  mQueuedAllJobs = true;
+}
+
+void JobQueue::reset() {
+  std::unique_lock<std::mutex> lock{mMutex};
+  mJobs.clear();
+  mQueuedAllJobs = false;
+}
+
 status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
                                           jr_uncompressed_ptr uncompressed_p010_image,
                                           jr_metadata_ptr metadata,
@@ -651,11 +766,14 @@
   size_t image_height = uncompressed_yuv_420_image->height;
   size_t map_width = image_width / kMapDimensionScaleFactor;
   size_t map_height = image_height / kMapDimensionScaleFactor;
+  size_t map_stride = static_cast<size_t>(
+          floor((map_width + kJpegBlock - 1) / kJpegBlock)) * kJpegBlock;
+  size_t map_height_aligned = ((map_height + 1) >> 1) << 1;
 
-  dest->width = map_width;
-  dest->height = map_height;
+  dest->width = map_stride;
+  dest->height = map_height_aligned;
   dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
-  dest->data = new uint8_t[map_width * map_height];
+  dest->data = new uint8_t[map_stride * map_height_aligned];
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
 
@@ -666,11 +784,19 @@
       hdrInvOetf = identityConversion;
       break;
     case JPEGR_TF_HLG:
+#if USE_HLG_INVOETF_LUT
+      hdrInvOetf = hlgInvOetfLUT;
+#else
       hdrInvOetf = hlgInvOetf;
+#endif
       hdr_white_nits = kHlgMaxNits;
       break;
     case JPEGR_TF_PQ:
+#if USE_PQ_INVOETF_LUT
+      hdrInvOetf = pqInvOetfLUT;
+#else
       hdrInvOetf = pqInvOetf;
+#endif
       hdr_white_nits = kPqMaxNits;
       break;
     case JPEGR_TF_UNSPECIFIED:
@@ -697,22 +823,87 @@
       return ERROR_JPEGR_INVALID_COLORGAMUT;
   }
 
+  std::mutex mutex;
   float hdr_y_nits_max = 0.0f;
   double hdr_y_nits_avg = 0.0f;
-  for (size_t y = 0; y < image_height; ++y) {
-    for (size_t x = 0; x < image_width; ++x) {
-      Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
-      Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
-      Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
-      hdr_rgb = hdrGamutConversionFn(hdr_rgb);
-      float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
+  const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
+  size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
+  JobQueue jobQueue;
 
-      hdr_y_nits_avg += hdr_y_nits;
-      if (hdr_y_nits > hdr_y_nits_max) {
-        hdr_y_nits_max = hdr_y_nits;
+  std::function<void()> computeMetadata = [uncompressed_p010_image, hdrInvOetf,
+                                           hdrGamutConversionFn, luminanceFn, hdr_white_nits,
+                                           threads, &mutex, &hdr_y_nits_avg,
+                                           &hdr_y_nits_max, &jobQueue]() -> void {
+    size_t rowStart, rowEnd;
+    float hdr_y_nits_max_th = 0.0f;
+    double hdr_y_nits_avg_th = 0.0f;
+    while (jobQueue.dequeueJob(rowStart, rowEnd)) {
+      for (size_t y = rowStart; y < rowEnd; ++y) {
+        for (size_t x = 0; x < uncompressed_p010_image->width; ++x) {
+          Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
+          Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
+          Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
+          hdr_rgb = hdrGamutConversionFn(hdr_rgb);
+          float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
+
+          hdr_y_nits_avg_th += hdr_y_nits;
+          if (hdr_y_nits > hdr_y_nits_max_th) {
+            hdr_y_nits_max_th = hdr_y_nits;
+          }
+        }
       }
     }
+    std::unique_lock<std::mutex> lock{mutex};
+    hdr_y_nits_avg += hdr_y_nits_avg_th;
+    hdr_y_nits_max = std::max(hdr_y_nits_max, hdr_y_nits_max_th);
+  };
+
+  std::function<void()> generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image,
+                                       metadata, dest, hdrInvOetf, hdrGamutConversionFn,
+                                       luminanceFn, hdr_white_nits, &jobQueue]() -> void {
+    size_t rowStart, rowEnd;
+    while (jobQueue.dequeueJob(rowStart, rowEnd)) {
+      for (size_t y = rowStart; y < rowEnd; ++y) {
+        for (size_t x = 0; x < dest->width; ++x) {
+          Color sdr_yuv_gamma =
+              sampleYuv420(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
+          Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
+#if USE_SRGB_INVOETF_LUT
+          Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
+#else
+          Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
+#endif
+          float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
+
+          Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
+          Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
+          Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
+          hdr_rgb = hdrGamutConversionFn(hdr_rgb);
+          float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
+
+          size_t pixel_idx = x + y * dest->width;
+          reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
+              encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
+        }
+      }
+    }
+  };
+
+  std::vector<std::thread> workers;
+  for (int th = 0; th < threads - 1; th++) {
+    workers.push_back(std::thread(computeMetadata));
   }
+
+  // compute metadata
+  for (size_t rowStart = 0; rowStart < image_height;) {
+    size_t rowEnd = std::min(rowStart + rowStep, image_height);
+    jobQueue.enqueueJob(rowStart, rowEnd);
+    rowStart = rowEnd;
+  }
+  jobQueue.markQueueForEnd();
+  computeMetadata();
+  std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
+  workers.clear();
   hdr_y_nits_avg /= image_width * image_height;
 
   metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits;
@@ -721,26 +912,22 @@
     metadata->hdr10Metadata.maxCLL = hdr_y_nits_max;
   }
 
-  for (size_t y = 0; y < map_height; ++y) {
-    for (size_t x = 0; x < map_width; ++x) {
-      Color sdr_yuv_gamma = sampleYuv420(uncompressed_yuv_420_image,
-                                         kMapDimensionScaleFactor, x, y);
-      Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
-      Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
-      float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
-
-      Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
-      Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
-      Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
-      hdr_rgb = hdrGamutConversionFn(hdr_rgb);
-      float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
-
-      size_t pixel_idx =  x + y * map_width;
-      reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
-          encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
-    }
+  // generate map
+  jobQueue.reset();
+  for (int th = 0; th < threads - 1; th++) {
+    workers.push_back(std::thread(generateMap));
   }
 
+  rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
+  for (size_t rowStart = 0; rowStart < map_height;) {
+    size_t rowEnd = std::min(rowStart + rowStep, map_height);
+    jobQueue.enqueueJob(rowStart, rowEnd);
+    rowStart = rowEnd;
+  }
+  jobQueue.markQueueForEnd();
+  generateMap();
+  std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
+
   map_data.release();
   return NO_ERROR;
 }
@@ -756,58 +943,95 @@
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  size_t width = uncompressed_yuv_420_image->width;
-  size_t height = uncompressed_yuv_420_image->height;
-
-  dest->width = width;
-  dest->height = height;
-  size_t pixel_count = width * height;
-
-  ColorTransformFn hdrOetf = nullptr;
-  switch (metadata->transferFunction) {
-    case JPEGR_TF_LINEAR:
-      hdrOetf = identityConversion;
-      break;
-    case JPEGR_TF_HLG:
-      hdrOetf = hlgOetf;
-      break;
-    case JPEGR_TF_PQ:
-      hdrOetf = pqOetf;
-      break;
-    case JPEGR_TF_UNSPECIFIED:
-      // Should be impossible to hit after input validation.
-      return ERROR_JPEGR_INVALID_TRANS_FUNC;
-  }
-
+  dest->width = uncompressed_yuv_420_image->width;
+  dest->height = uncompressed_yuv_420_image->height;
   ShepardsIDW idwTable(kMapDimensionScaleFactor);
+  RecoveryLUT recoveryLUT(metadata->rangeScalingFactor);
 
-  for (size_t y = 0; y < height; ++y) {
-    for (size_t x = 0; x < width; ++x) {
-      Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
-      Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
-      Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
+  JobQueue jobQueue;
+  std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_recovery_map,
+                                       metadata, dest, &jobQueue, &idwTable,
+                                       &recoveryLUT]() -> void {
+    const float hdr_ratio = metadata->rangeScalingFactor;
+    size_t width = uncompressed_yuv_420_image->width;
+    size_t height = uncompressed_yuv_420_image->height;
 
-      float recovery;
-      // 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);
-      } else {
-        recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y,
-                             idwTable);
-      }
-      Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata->rangeScalingFactor);
-
-      Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->rangeScalingFactor);
-      uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
-
-      size_t pixel_idx =  x + y * width;
-      reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
+    ColorTransformFn hdrOetf = nullptr;
+    switch (metadata->transferFunction) {
+      case JPEGR_TF_LINEAR:
+        hdrOetf = identityConversion;
+        break;
+      case JPEGR_TF_HLG:
+#if USE_HLG_OETF_LUT
+        hdrOetf = hlgOetfLUT;
+#else
+        hdrOetf = hlgOetf;
+#endif
+        break;
+      case JPEGR_TF_PQ:
+#if USE_PQ_OETF_LUT
+        hdrOetf = pqOetfLUT;
+#else
+        hdrOetf = pqOetf;
+#endif
+        break;
+      case JPEGR_TF_UNSPECIFIED:
+        // Should be impossible to hit after input validation.
+        hdrOetf = identityConversion;
     }
+
+    size_t rowStart, rowEnd;
+    while (jobQueue.dequeueJob(rowStart, rowEnd)) {
+      for (size_t y = rowStart; y < rowEnd; ++y) {
+        for (size_t x = 0; x < width; ++x) {
+          Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
+          Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
+#if USE_SRGB_INVOETF_LUT
+          Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
+#else
+          Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
+#endif
+          float recovery;
+          // 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);
+          } else {
+            recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y,
+                                idwTable);
+          }
+#if USE_APPLY_RECOVERY_LUT
+          Color rgb_hdr = applyRecoveryLUT(rgb_sdr, recovery, recoveryLUT);
+#else
+          Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio);
+#endif
+          Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->rangeScalingFactor);
+          uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
+
+          size_t pixel_idx = x + y * width;
+          reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
+        }
+      }
+    }
+  };
+
+  const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
+  std::vector<std::thread> workers;
+  for (int th = 0; th < threads - 1; th++) {
+    workers.push_back(std::thread(applyRecMap));
   }
+  const int rowStep = threads == 1 ? uncompressed_yuv_420_image->height : kJobSzInRows;
+  for (int rowStart = 0; rowStart < uncompressed_yuv_420_image->height;) {
+    int rowEnd = std::min(rowStart + rowStep, uncompressed_yuv_420_image->height);
+    jobQueue.enqueueJob(rowStart, rowEnd);
+    rowStart = rowEnd;
+  }
+  jobQueue.markQueueForEnd();
+  applyRecMap();
+  std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
   return NO_ERROR;
 }
 
diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp
index 4a6feca..4f21ac6 100644
--- a/libs/jpegrecoverymap/recoverymapmath.cpp
+++ b/libs/jpegrecoverymap/recoverymapmath.cpp
@@ -15,11 +15,74 @@
  */
 
 #include <cmath>
-
+#include <vector>
 #include <jpegrecoverymap/recoverymapmath.h>
 
 namespace android::recoverymap {
 
+constexpr size_t kPqOETFPrecision = 10;
+constexpr size_t kPqOETFNumEntries = 1 << kPqOETFPrecision;
+
+static const std::vector<float> kPqOETF = [] {
+    std::vector<float> result;
+    float increment = 1.0 / kPqOETFNumEntries;
+    float value = 0.0f;
+    for (int idx = 0; idx < kPqOETFNumEntries; idx++, value += increment) {
+      result.push_back(pqOetf(value));
+    }
+    return result;
+}();
+
+constexpr size_t kPqInvOETFPrecision = 10;
+constexpr size_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision;
+
+static const std::vector<float> kPqInvOETF = [] {
+    std::vector<float> result;
+    float increment = 1.0 / kPqInvOETFNumEntries;
+    float value = 0.0f;
+    for (int idx = 0; idx < kPqInvOETFNumEntries; idx++, value += increment) {
+      result.push_back(pqInvOetf(value));
+    }
+    return result;
+}();
+
+constexpr size_t kHlgOETFPrecision = 10;
+constexpr size_t kHlgOETFNumEntries = 1 << kHlgOETFPrecision;
+
+static const std::vector<float> kHlgOETF = [] {
+    std::vector<float> result;
+    float increment = 1.0 / kHlgOETFNumEntries;
+    float value = 0.0f;
+    for (int idx = 0; idx < kHlgOETFNumEntries; idx++, value += increment) {
+      result.push_back(hlgOetf(value));
+    }
+    return result;
+}();
+
+constexpr size_t kHlgInvOETFPrecision = 10;
+constexpr size_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision;
+
+static const std::vector<float> kHlgInvOETF = [] {
+    std::vector<float> result;
+    float increment = 1.0 / kHlgInvOETFNumEntries;
+    float value = 0.0f;
+    for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++, value += increment) {
+      result.push_back(hlgInvOetf(value));
+    }
+    return result;
+}();
+
+constexpr size_t kSRGBInvOETFPrecision = 10;
+constexpr size_t kSRGBInvOETFNumEntries = 1 << kSRGBInvOETFPrecision;
+static const std::vector<float> kSRGBInvOETF = [] {
+    std::vector<float> result;
+    float increment = 1.0 / kSRGBInvOETFNumEntries;
+    float value = 0.0f;
+    for (int idx = 0; idx < kSRGBInvOETFNumEntries; idx++, value += increment) {
+      result.push_back(srgbInvOetf(value));
+    }
+    return result;
+}();
 
 // Use Shepard's method for inverse distance weighting. For more information:
 // en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method
@@ -70,6 +133,11 @@
 ////////////////////////////////////////////////////////////////////////////////
 // sRGB transformations
 
+static const float kMaxPixelFloat = 1.0f;
+static float clampPixelFloat(float value) {
+    return (value < 0.0f) ? 0.0f : (value > kMaxPixelFloat) ? kMaxPixelFloat : value;
+}
+
 // See IEC 61966-2-1, Equation F.7.
 static const float kSrgbR = 0.2126f, kSrgbG = 0.7152f, kSrgbB = 0.0722f;
 
@@ -81,9 +149,9 @@
 static const float kSrgbRCr = 1.402f, kSrgbGCb = 0.34414f, kSrgbGCr = 0.71414f, kSrgbBCb = 1.772f;
 
 Color srgbYuvToRgb(Color e_gamma) {
-  return {{{ e_gamma.y + kSrgbRCr * e_gamma.v,
-             e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v,
-             e_gamma.y + kSrgbBCb * e_gamma.u }}};
+  return {{{ clampPixelFloat(e_gamma.y + kSrgbRCr * e_gamma.v),
+             clampPixelFloat(e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v),
+             clampPixelFloat(e_gamma.y + kSrgbBCb * e_gamma.u) }}};
 }
 
 // See ECMA TR/98, Section 7.
@@ -112,6 +180,19 @@
              srgbInvOetf(e_gamma.b) }}};
 }
 
+// See IEC 61966-2-1, Equations F.5 and F.6.
+float srgbInvOetfLUT(float e_gamma) {
+  uint32_t value = static_cast<uint32_t>(e_gamma * kSRGBInvOETFNumEntries);
+  //TODO() : Remove once conversion modules have appropriate clamping in place
+  value = CLIP3(value, 0, kSRGBInvOETFNumEntries - 1);
+  return kSRGBInvOETF[value];
+}
+
+Color srgbInvOetfLUT(Color e_gamma) {
+  return {{{ srgbInvOetfLUT(e_gamma.r),
+             srgbInvOetfLUT(e_gamma.g),
+             srgbInvOetfLUT(e_gamma.b) }}};
+}
 
 ////////////////////////////////////////////////////////////////////////////////
 // Display-P3 transformations
@@ -169,9 +250,9 @@
 static const float kBt2100GCr = kBt2100R * kBt2100Cr / kBt2100G;
 
 Color bt2100YuvToRgb(Color e_gamma) {
-  return {{{ e_gamma.y + kBt2100Cr * e_gamma.v,
-             e_gamma.y - kBt2100GCb * e_gamma.u - kBt2100GCr * e_gamma.v,
-             e_gamma.y + kBt2100Cb * e_gamma.u }}};
+  return {{{ clampPixelFloat(e_gamma.y + kBt2100Cr * e_gamma.v),
+             clampPixelFloat(e_gamma.y - kBt2100GCb * e_gamma.u - kBt2100GCr * e_gamma.v),
+             clampPixelFloat(e_gamma.y + kBt2100Cb * e_gamma.u) }}};
 }
 
 // See ITU-R BT.2100-2, Table 5, HLG Reference OETF.
@@ -189,6 +270,18 @@
   return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}};
 }
 
+float hlgOetfLUT(float e) {
+  uint32_t value = static_cast<uint32_t>(e * kHlgOETFNumEntries);
+  //TODO() : Remove once conversion modules have appropriate clamping in place
+  value = CLIP3(value, 0, kHlgOETFNumEntries - 1);
+
+  return kHlgOETF[value];
+}
+
+Color hlgOetfLUT(Color e) {
+  return {{{ hlgOetfLUT(e.r), hlgOetfLUT(e.g), hlgOetfLUT(e.b) }}};
+}
+
 // See ITU-R BT.2100-2, Table 5, HLG Reference EOTF.
 float hlgInvOetf(float e_gamma) {
   if (e_gamma <= 0.5f) {
@@ -204,6 +297,20 @@
              hlgInvOetf(e_gamma.b) }}};
 }
 
+float hlgInvOetfLUT(float e_gamma) {
+  uint32_t value = static_cast<uint32_t>(e_gamma * kHlgInvOETFNumEntries);
+  //TODO() : Remove once conversion modules have appropriate clamping in place
+  value = CLIP3(value, 0, kHlgInvOETFNumEntries - 1);
+
+  return kHlgInvOETF[value];
+}
+
+Color hlgInvOetfLUT(Color e_gamma) {
+  return {{{ hlgInvOetfLUT(e_gamma.r),
+             hlgInvOetfLUT(e_gamma.g),
+             hlgInvOetfLUT(e_gamma.b) }}};
+}
+
 // See ITU-R BT.2100-2, Table 4, Reference PQ OETF.
 static const float kPqM1 = 2610.0f / 16384.0f, kPqM2 = 2523.0f / 4096.0f * 128.0f;
 static const float kPqC1 = 3424.0f / 4096.0f, kPqC2 = 2413.0f / 4096.0f * 32.0f,
@@ -219,6 +326,18 @@
   return {{{ pqOetf(e.r), pqOetf(e.g), pqOetf(e.b) }}};
 }
 
+float pqOetfLUT(float e) {
+  uint32_t value = static_cast<uint32_t>(e * kPqOETFNumEntries);
+  //TODO() : Remove once conversion modules have appropriate clamping in place
+  value = CLIP3(value, 0, kPqOETFNumEntries - 1);
+
+  return kPqOETF[value];
+}
+
+Color pqOetfLUT(Color e) {
+  return {{{ pqOetfLUT(e.r), pqOetfLUT(e.g), pqOetfLUT(e.b) }}};
+}
+
 // Derived from the inverse of the Reference PQ OETF.
 static const float kPqInvA = 128.0f, kPqInvB = 107.0f, kPqInvC = 2413.0f, kPqInvD = 2392.0f,
                    kPqInvE = 6.2773946361f, kPqInvF = 0.0126833f;
@@ -239,6 +358,20 @@
              pqInvOetf(e_gamma.b) }}};
 }
 
+float pqInvOetfLUT(float e_gamma) {
+  uint32_t value = static_cast<uint32_t>(e_gamma * kPqInvOETFNumEntries);
+  //TODO() : Remove once conversion modules have appropriate clamping in place
+  value = CLIP3(value, 0, kPqInvOETFNumEntries - 1);
+
+  return kPqInvOETF[value];
+}
+
+Color pqInvOetfLUT(Color e_gamma) {
+  return {{{ pqInvOetfLUT(e_gamma.r),
+             pqInvOetfLUT(e_gamma.g),
+             pqInvOetfLUT(e_gamma.b) }}};
+}
+
 
 ////////////////////////////////////////////////////////////////////////////////
 // Color conversions
@@ -346,6 +479,11 @@
   return e * recoveryFactor;
 }
 
+Color applyRecoveryLUT(Color e, float recovery, RecoveryLUT& recoveryLUT) {
+  float recoveryFactor = recoveryLUT.getRecoveryFactor(recovery);
+  return e * recoveryFactor;
+}
+
 Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
   size_t pixel_count = image->width * image->height;
 
diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
index 67f3350..dfab76a 100644
--- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <jpegrecoverymap/recoverymap.h>
+#include <jpegrecoverymap/recoverymapmath.h>
 #include <jpegrecoverymap/recoverymaputils.h>
 #include <fcntl.h>
 #include <fstream>
@@ -30,6 +31,7 @@
 
 #define SAVE_ENCODING_RESULT true
 #define SAVE_DECODING_RESULT true
+#define SAVE_INPUT_RGBA true
 
 namespace android::recoverymap {
 
@@ -313,6 +315,29 @@
   mRawP010Image.height = TEST_IMAGE_HEIGHT;
   mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
 
+  if (SAVE_INPUT_RGBA) {
+    size_t rgbaSize = mRawP010Image.width * mRawP010Image.height * sizeof(uint32_t);
+    uint32_t *data = (uint32_t *)malloc(rgbaSize);
+
+    for (size_t y = 0; y < mRawP010Image.height; ++y) {
+      for (size_t x = 0; x < mRawP010Image.width; ++x) {
+        Color hdr_yuv_gamma = getP010Pixel(&mRawP010Image, x, y);
+        Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
+        uint32_t rgba1010102 = colorToRgba1010102(hdr_rgb_gamma);
+        size_t pixel_idx =  x + y * mRawP010Image.width;
+        reinterpret_cast<uint32_t*>(data)[pixel_idx] = rgba1010102;
+      }
+    }
+
+    // Output image data to file
+    std::string filePath = "/sdcard/Documents/input_from_p010.rgb10";
+    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+    if (!imageFile.is_open()) {
+      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+    }
+    imageFile.write((const char*)data, rgbaSize);
+    free(data);
+  }
   if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) {
     FAIL() << "Load file " << JPEG_IMAGE << " failed";
   }
diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
index 58c139d..1d522d1 100644
--- a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
@@ -517,6 +517,65 @@
   EXPECT_RGB_NEAR(pqInvOetf(e_gamma), e);
 }
 
+TEST_F(RecoveryMapMathTest, PqInvOetfLUT) {
+    float increment = 1.0 / 1024.0;
+    float value = 0.0f;
+    for (int idx = 0; idx < 1024; idx++, value += increment) {
+      EXPECT_FLOAT_EQ(pqInvOetf(value), pqInvOetfLUT(value));
+    }
+}
+
+TEST_F(RecoveryMapMathTest, HlgInvOetfLUT) {
+    float increment = 1.0 / 1024.0;
+    float value = 0.0f;
+    for (int idx = 0; idx < 1024; idx++, value += increment) {
+      EXPECT_FLOAT_EQ(hlgInvOetf(value), hlgInvOetfLUT(value));
+    }
+}
+
+TEST_F(RecoveryMapMathTest, pqOetfLUT) {
+    float increment = 1.0 / 1024.0;
+    float value = 0.0f;
+    for (int idx = 0; idx < 1024; idx++, value += increment) {
+      EXPECT_FLOAT_EQ(pqOetf(value), pqOetfLUT(value));
+    }
+}
+
+TEST_F(RecoveryMapMathTest, hlgOetfLUT) {
+    float increment = 1.0 / 1024.0;
+    float value = 0.0f;
+    for (int idx = 0; idx < 1024; idx++, value += increment) {
+      EXPECT_FLOAT_EQ(hlgOetf(value), hlgOetfLUT(value));
+    }
+}
+
+TEST_F(RecoveryMapMathTest, srgbInvOetfLUT) {
+    float increment = 1.0 / 1024.0;
+    float value = 0.0f;
+    for (int idx = 0; idx < 1024; idx++, value += increment) {
+      EXPECT_FLOAT_EQ(srgbInvOetf(value), srgbInvOetfLUT(value));
+    }
+}
+
+TEST_F(RecoveryMapMathTest, applyRecoveryLUT) {
+  float increment = 2.0 / kRecoveryFactorNumEntries;
+  for (float hdrRatio = 1.0f; hdrRatio <= 10.0f; hdrRatio += 1.0f)  {
+    RecoveryLUT recoveryLUT(hdrRatio);
+    for (float value = -1.0f; value <= -1.0f; value += increment) {
+      EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, hdrRatio),
+                      applyRecoveryLUT(RgbBlack(), value, recoveryLUT));
+      EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, hdrRatio),
+                      applyRecoveryLUT(RgbWhite(), value, recoveryLUT));
+      EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, hdrRatio),
+                      applyRecoveryLUT(RgbRed(), value, recoveryLUT));
+      EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, hdrRatio),
+                      applyRecoveryLUT(RgbGreen(), value, recoveryLUT));
+      EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, hdrRatio),
+                      applyRecoveryLUT(RgbBlue(), value, recoveryLUT));
+    }
+  }
+}
+
 TEST_F(RecoveryMapMathTest, PqTransferFunctionRoundtrip) {
   EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(0.0f)), 0.0f);
   EXPECT_NEAR(pqInvOetf(pqOetf(0.01f)), 0.01f, ComparisonEpsilon());
diff --git a/libs/nativewindow/include/android/hardware_buffer.h b/libs/nativewindow/include/android/hardware_buffer.h
index b2e8bea..85a5249 100644
--- a/libs/nativewindow/include/android/hardware_buffer.h
+++ b/libs/nativewindow/include/android/hardware_buffer.h
@@ -311,6 +311,16 @@
      */
     AHARDWAREBUFFER_USAGE_GPU_MIPMAP_COMPLETE   = 1UL << 26,
 
+    /**
+     * Usage: The buffer is used for front-buffer rendering. When
+     * front-buffering rendering is specified, different usages may adjust their
+     * behavior as a result. For example, when used as GPU_COLOR_OUTPUT the buffer
+     * will behave similar to a single-buffered window. When used with
+     * COMPOSER_OVERLAY, the system will try to prioritize the buffer receiving
+     * an overlay plane & avoid caching it in intermediate composition buffers.
+     */
+    AHARDWAREBUFFER_USAGE_FRONT_BUFFER = 1UL << 32,
+
     AHARDWAREBUFFER_USAGE_VENDOR_0  = 1ULL << 28,
     AHARDWAREBUFFER_USAGE_VENDOR_1  = 1ULL << 29,
     AHARDWAREBUFFER_USAGE_VENDOR_2  = 1ULL << 30,
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index dd14bcf..34b1251 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -502,7 +502,6 @@
     void* so = do_android_dlopen_ext(name.c_str(), RTLD_LOCAL | RTLD_NOW, &dlextinfo);
 
     if (so) {
-        ALOGD("dlopen_ext from APK (%s) success at %p", name.c_str(), so);
         return so;
     } else {
         ALOGE("dlopen_ext(\"%s\") failed: %s", name.c_str(), dlerror());
diff --git a/services/automotive/display/Android.bp b/services/automotive/display/Android.bp
index 72bd292..614a78e 100644
--- a/services/automotive/display/Android.bp
+++ b/services/automotive/display/Android.bp
@@ -53,4 +53,6 @@
     vintf_fragments: [
         "manifest_android.frameworks.automotive.display@1.0.xml",
     ],
+
+    system_ext_specific: true,
 }
diff --git a/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc b/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
index 5c7f344..ea1077a 100644
--- a/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
+++ b/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
@@ -1,4 +1,4 @@
-service automotive_display /system/bin/android.frameworks.automotive.display@1.0-service
+service automotive_display /system_ext/bin/android.frameworks.automotive.display@1.0-service
     class hal
     user graphics
     group automotive_evs
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 6e78e82..9bd50f7 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -452,7 +452,8 @@
 InputDeviceInfo InputDevice::getDeviceInfo() {
     InputDeviceInfo outDeviceInfo;
     outDeviceInfo.initialize(mId, mGeneration, mControllerNumber, mIdentifier, mAlias, mIsExternal,
-                             mHasMic);
+                             mHasMic, getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE));
+
     for_each_mapper(
             [&outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(&outDeviceInfo); });
 
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 8cd2cf0..ddddca2 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -55,6 +55,10 @@
     return p.x >= rect.left && p.x < rect.right && p.y >= rect.top && p.y < rect.bottom;
 }
 
+static std::string toString(const InputDeviceUsiVersion& v) {
+    return base::StringPrintf("%d.%d", v.majorVersion, v.minorVersion);
+}
+
 template <typename T>
 inline static void swap(T& a, T& b) {
     T temp = a;
@@ -188,7 +192,7 @@
         info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
     }
     info->setButtonUnderPad(mParameters.hasButtonUnderPad);
-    info->setSupportsUsi(mParameters.supportsUsi);
+    info->setUsiVersion(mParameters.usiVersion);
 }
 
 void TouchInputMapper::dump(std::string& dump) {
@@ -421,9 +425,13 @@
     mParameters.wake = getDeviceContext().isExternal();
     getDeviceContext().getConfiguration().tryGetProperty("touch.wake", mParameters.wake);
 
-    mParameters.supportsUsi = false;
-    getDeviceContext().getConfiguration().tryGetProperty("touch.supportsUsi",
-                                                         mParameters.supportsUsi);
+    InputDeviceUsiVersion usiVersion;
+    if (getDeviceContext().getConfiguration().tryGetProperty("touch.usiVersionMajor",
+                                                             usiVersion.majorVersion) &&
+        getDeviceContext().getConfiguration().tryGetProperty("touch.usiVersionMinor",
+                                                             usiVersion.minorVersion)) {
+        mParameters.usiVersion = usiVersion;
+    }
 
     mParameters.enableForInactiveViewport = false;
     getDeviceContext().getConfiguration().tryGetProperty("touch.enableForInactiveViewport",
@@ -472,7 +480,8 @@
                          mParameters.uniqueDisplayId.c_str());
     dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware));
     dump += INDENT4 "Orientation: " + ftl::enum_string(mParameters.orientation) + "\n";
-    dump += StringPrintf(INDENT4 "SupportsUsi: %s\n", toString(mParameters.supportsUsi));
+    dump += StringPrintf(INDENT4 "UsiVersion: %s\n",
+                         toString(mParameters.usiVersion, toString).c_str());
     dump += StringPrintf(INDENT4 "EnableForInactiveViewport: %s\n",
                          toString(mParameters.enableForInactiveViewport));
 }
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 6e35b46..87deb39 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -234,8 +234,8 @@
 
         bool wake;
 
-        // Whether the device supports the Universal Stylus Initiative (USI) protocol for styluses.
-        bool supportsUsi;
+        // The Universal Stylus Initiative (USI) protocol version supported by this device.
+        std::optional<InputDeviceUsiVersion> usiVersion;
 
         // Allows touches while the display is off.
         bool enableForInactiveViewport;
diff --git a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
index bd05360..7265362 100644
--- a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
+++ b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <gtest/gtest.h>
+#include <gui/constants.h>
 #include "../PreferStylusOverTouchBlocker.h"
 
 namespace android {
@@ -438,7 +439,7 @@
     InputDeviceInfo stylusDevice;
     stylusDevice.initialize(STYLUS_DEVICE_ID, 1 /*generation*/, 1 /*controllerNumber*/,
                             {} /*identifier*/, "stylus device", false /*external*/,
-                            false /*hasMic*/);
+                            false /*hasMic*/, ADISPLAY_ID_NONE);
     notifyInputDevicesChanged({stylusDevice});
     // The touchscreen device was removed, so we no longer remember anything about it. We should
     // again start blocking touch events from it.
diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
index 4c84160..e12f88e 100644
--- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
+++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
@@ -105,7 +105,7 @@
 
     auto info = InputDeviceInfo();
     info.initialize(DEVICE_ID, /*generation*/ 1, /*controllerNumber*/ 1, identifier, "alias",
-                    /*isExternal*/ false, /*hasMic*/ false);
+                    /*isExternal*/ false, /*hasMic*/ false, ADISPLAY_ID_NONE);
     info.addSource(AINPUT_SOURCE_TOUCHSCREEN);
     info.addMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN, 0, 1599, /*flat*/ 0,
                         /*fuzz*/ 0, X_RESOLUTION);
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 0982077..2b6a519 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -479,8 +479,9 @@
     const auto& desiredMode = *info.modeOpt->modePtr;
 
     // Check if we are already at the desired mode
-    if (!force && refreshRateSelector().getActiveMode().modePtr->getId() == desiredMode.getId()) {
-        if (refreshRateSelector().getActiveMode() == info.modeOpt) {
+    const auto currentMode = refreshRateSelector().getActiveMode();
+    if (!force && currentMode.modePtr->getId() == desiredMode.getId()) {
+        if (currentMode == info.modeOpt) {
             return DesiredActiveModeAction::None;
         }
 
@@ -488,6 +489,11 @@
         return DesiredActiveModeAction::InitiateRenderRateSwitch;
     }
 
+    // Set the render frame rate to the current physical refresh rate to schedule the next
+    // frame as soon as possible.
+    setActiveMode(currentMode.modePtr->getId(), currentMode.modePtr->getFps(),
+                  currentMode.modePtr->getFps());
+
     // Initiate a mode change.
     mDesiredActiveModeChanged = true;
     mDesiredActiveMode = info;
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index 31074b1..d54d22d 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -314,12 +314,12 @@
     virtual void parseArgs(const Vector<String16>& args, std::string& result) = 0;
 
     // Sets the max number of display frames that can be stored. Called by SF backdoor.
-    virtual void setMaxDisplayFrames(uint32_t size);
+    virtual void setMaxDisplayFrames(uint32_t size) = 0;
 
     // Computes the historical fps for the provided set of layer IDs
     // The fps is compted from the linear timeline of present timestamps for DisplayFrames
     // containing at least one layer ID.
-    virtual float computeFps(const std::unordered_set<int32_t>& layerIds);
+    virtual float computeFps(const std::unordered_set<int32_t>& layerIds) = 0;
 
     // Restores the max number of display frames to default. Called by SF backdoor.
     virtual void reset() = 0;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 04c2d41..30821d8 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -32,6 +32,7 @@
 #include <ftl/fake_guard.h>
 #include <ftl/match.h>
 #include <ftl/unit.h>
+#include <gui/TraceUtils.h>
 #include <scheduler/FrameRateMode.h>
 #include <utils/Trace.h>
 
@@ -416,8 +417,10 @@
     // Keep the display at max frame rate for the duration of powering on the display.
     if (signals.powerOnImminent) {
         ALOGV("Power On Imminent");
-        return {rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Descending),
-                GlobalSignals{.powerOnImminent = true}};
+        const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Descending);
+        ATRACE_FORMAT_INSTANT("%s (Power On Imminent)",
+                              to_string(ranking.front().frameRateMode.fps).c_str());
+        return {ranking, GlobalSignals{.powerOnImminent = true}};
     }
 
     int noVoteLayers = 0;
@@ -476,8 +479,10 @@
     // selected a refresh rate to see if we should apply touch boost.
     if (signals.touch && !hasExplicitVoteLayers) {
         ALOGV("Touch Boost");
-        return {rankFrameRates(anchorGroup, RefreshRateOrder::Descending),
-                GlobalSignals{.touch = true}};
+        const auto ranking = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
+        ATRACE_FORMAT_INSTANT("%s (Touch Boost)",
+                              to_string(ranking.front().frameRateMode.fps).c_str());
+        return {ranking, GlobalSignals{.touch = true}};
     }
 
     // If the primary range consists of a single refresh rate then we can only
@@ -488,19 +493,26 @@
 
     if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
         ALOGV("Idle");
-        return {rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending),
-                GlobalSignals{.idle = true}};
+        const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending);
+        ATRACE_FORMAT_INSTANT("%s (Idle)", to_string(ranking.front().frameRateMode.fps).c_str());
+        return {ranking, GlobalSignals{.idle = true}};
     }
 
     if (layers.empty() || noVoteLayers == layers.size()) {
         ALOGV("No layers with votes");
-        return {rankFrameRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
+        const auto ranking = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
+        ATRACE_FORMAT_INSTANT("%s (No layers with votes)",
+                              to_string(ranking.front().frameRateMode.fps).c_str());
+        return {ranking, kNoSignals};
     }
 
     // Only if all layers want Min we should return Min
     if (noVoteLayers + minVoteLayers == layers.size()) {
         ALOGV("All layers Min");
-        return {rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending), kNoSignals};
+        const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending);
+        ATRACE_FORMAT_INSTANT("%s (All layers Min)",
+                              to_string(ranking.front().frameRateMode.fps).c_str());
+        return {ranking, kNoSignals};
     }
 
     // Find the best refresh rate based on score
@@ -670,8 +682,13 @@
         // range instead of picking a random score from the app range.
         if (noLayerScore) {
             ALOGV("Layers not scored");
-            return {rankFrameRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
+            const auto descending = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
+            ATRACE_FORMAT_INSTANT("%s (Layers not scored)",
+                                  to_string(descending.front().frameRateMode.fps).c_str());
+            return {descending, kNoSignals};
         } else {
+            ATRACE_FORMAT_INSTANT("%s (primaryRangeIsSingleRate)",
+                                  to_string(ranking.front().frameRateMode.fps).c_str());
             return {ranking, kNoSignals};
         }
     }
@@ -696,17 +713,22 @@
     if (signals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
         scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) {
         ALOGV("Touch Boost");
+        ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])",
+                              to_string(touchRefreshRates.front().frameRateMode.fps).c_str());
         return {touchRefreshRates, GlobalSignals{.touch = true}};
     }
 
     // If we never scored any layers, and we don't favor high refresh rates, prefer to stay with the
     // current config
     if (noLayerScore && refreshRateOrder == RefreshRateOrder::Ascending) {
-        const auto preferredDisplayMode = activeMode.getId();
-        return {rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, preferredDisplayMode),
-                kNoSignals};
+        const auto ascendingWithPreferred =
+                rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId());
+        ATRACE_FORMAT_INSTANT("%s (preferredDisplayMode)",
+                              to_string(ascendingWithPreferred.front().frameRateMode.fps).c_str());
+        return {ascendingWithPreferred, kNoSignals};
     }
 
+    ATRACE_FORMAT_INSTANT("%s (scored))", to_string(ranking.front().frameRateMode.fps).c_str());
     return {ranking, kNoSignals};
 }
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 2d8b9c1..40de4d6 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1152,6 +1152,10 @@
     switch (display->setDesiredActiveMode(DisplayDevice::ActiveModeInfo(std::move(request)),
                                           force)) {
         case DisplayDevice::DesiredActiveModeAction::InitiateDisplayModeSwitch:
+            // Set the render rate as setDesiredActiveMode updated it.
+            mScheduler->setRenderRate(display->refreshRateSelector().getActiveMode().fps);
+
+            // Schedule a new frame to initiate the display mode switch.
             scheduleComposite(FrameHint::kNone);
 
             // Start receiving vsync samples now, so that we can detect a period
@@ -1833,17 +1837,6 @@
     return NO_ERROR;
 }
 
-bool SurfaceFlinger::hasVisibleHdrLayer(const sp<DisplayDevice>& display) {
-    bool hasHdrLayers = false;
-    mDrawingState.traverse([&,
-                            compositionDisplay = display->getCompositionDisplay()](Layer* layer) {
-        hasHdrLayers |= (layer->isVisible() &&
-                         compositionDisplay->includesLayer(layer->getCompositionEngineLayerFE()) &&
-                         isHdrDataspace(layer->getDataSpace()));
-    });
-    return hasHdrLayers;
-}
-
 status_t SurfaceFlinger::setDisplayBrightness(const sp<IBinder>& displayToken,
                                               const gui::DisplayBrightness& brightness) {
     if (!displayToken) {
@@ -2638,7 +2631,8 @@
             int32_t maxArea = 0;
             mDrawingState.traverse([&, compositionDisplay = compositionDisplay](Layer* layer) {
                 const auto layerFe = layer->getCompositionEngineLayerFE();
-                if (layer->isVisible() && compositionDisplay->includesLayer(layerFe)) {
+                if (layer->isVisible() &&
+                    compositionDisplay->includesLayer(layer->getOutputFilter())) {
                     if (isHdrLayer(layer)) {
                         const auto* outputLayer =
                             compositionDisplay->getOutputLayerForLayer(layerFe);
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 33f0402..5457be8 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -673,9 +673,6 @@
     void setPowerModeInternal(const sp<DisplayDevice>& display, hal::PowerMode mode)
             REQUIRES(mStateLock, kMainThreadContext);
 
-    // Returns true if the display has a visible HDR layer in its layer stack.
-    bool hasVisibleHdrLayer(const sp<DisplayDevice>& display) REQUIRES(mStateLock);
-
     // Returns the preferred mode for PhysicalDisplayId if the Scheduler has selected one for that
     // display. Falls back to the display's defaultModeId otherwise.
     ftl::Optional<scheduler::FrameRateMode> getPreferredDisplayMode(