Merge "Update decodeImage to account for alignment"
diff --git a/cmds/cmd/cmd.cpp b/cmds/cmd/cmd.cpp
index 7b4aeb2..8dad475 100644
--- a/cmds/cmd/cmd.cpp
+++ b/cmds/cmd/cmd.cpp
@@ -223,7 +223,8 @@
     sp<MyResultReceiver> result = new MyResultReceiver();
 
 #if DEBUG
-    ALOGD("cmd: Invoking %s in=%d, out=%d, err=%d", cmd, in, out, err);
+    ALOGD("cmd: Invoking %.*s in=%d, out=%d, err=%d",
+          static_cast<int>(cmd.size()), cmd.data(), in, out, err);
 #endif
 
     // TODO: block until a result is returned to MyResultReceiver.
diff --git a/data/etc/android.hardware.context_hub.xml b/data/etc/android.hardware.context_hub.xml
new file mode 100644
index 0000000..8133e0b
--- /dev/null
+++ b/data/etc/android.hardware.context_hub.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<!-- Features for devices supporting a Context Hub. -->
+<permissions>
+    <feature name="android.hardware.context_hub" />
+</permissions>
diff --git a/data/etc/android.software.vulkan.deqp.level-2019-03-01.xml b/data/etc/android.software.vulkan.deqp.level-2019-03-01.xml
new file mode 100644
index 0000000..9c67d4a
--- /dev/null
+++ b/data/etc/android.software.vulkan.deqp.level-2019-03-01.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 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.
+-->
+
+<!-- This is the standard feature indicating that the device passes Vulkan deQP
+     tests associated with date 2019-03-01 (0x07E30301). -->
+<permissions>
+    <feature name="android.software.vulkan.deqp.level" version="132317953" />
+</permissions>
diff --git a/data/etc/android.software.vulkan.deqp.level-2020-03-01.xml b/data/etc/android.software.vulkan.deqp.level-2020-03-01.xml
new file mode 100644
index 0000000..19b269b
--- /dev/null
+++ b/data/etc/android.software.vulkan.deqp.level-2020-03-01.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 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.
+-->
+
+<!-- This is the standard feature indicating that the device passes Vulkan deQP
+     tests associated with date 2020-03-01 (0x07E40301). -->
+<permissions>
+    <feature name="android.software.vulkan.deqp.level" version="132383489" />
+</permissions>
diff --git a/docs/images/camera2/metadata/android.scaler.rotateAndCrop/crop-region-rotate-90-43-ratio.png b/docs/images/camera2/metadata/android.scaler.rotateAndCrop/crop-region-rotate-90-43-ratio.png
new file mode 100644
index 0000000..2633996
--- /dev/null
+++ b/docs/images/camera2/metadata/android.scaler.rotateAndCrop/crop-region-rotate-90-43-ratio.png
Binary files differ
diff --git a/include/android/bitmap.h b/include/android/bitmap.h
index 5e03c6c..571a5ca 100644
--- a/include/android/bitmap.h
+++ b/include/android/bitmap.h
@@ -189,7 +189,7 @@
  *  @param size Length in bytes of data to write.
  *  @return Whether the operation succeeded.
  */
-typedef bool (*AndroidBitmap_compress_write_fn)(void* userContext,
+typedef bool (*AndroidBitmap_CompressWriteFunc)(void* userContext,
                                                 const void* data,
                                                 size_t size) __INTRODUCED_IN(30);
 
@@ -205,7 +205,7 @@
  *                 differently depending on the
  *                 {@link AndroidBitmapCompressFormat}.
  *  @param userContext User-defined data which will be passed to the supplied
- *                     {@link AndroidBitmap_compress_write_fn} each time it is
+ *                     {@link AndroidBitmap_CompressWriteFunc} each time it is
  *                     called. May be null.
  *  @parm fn Function that writes the compressed data. Will be called each time
  *           the compressor has compressed more data that is ready to be
@@ -218,7 +218,7 @@
                            const void* pixels,
                            int32_t format, int32_t quality,
                            void* userContext,
-                           AndroidBitmap_compress_write_fn fn) __INTRODUCED_IN(30);
+                           AndroidBitmap_CompressWriteFunc fn) __INTRODUCED_IN(30);
 
 struct AHardwareBuffer;
 
diff --git a/include/android/imagedecoder.h b/include/android/imagedecoder.h
index c9a041e..da324b8 100644
--- a/include/android/imagedecoder.h
+++ b/include/android/imagedecoder.h
@@ -133,7 +133,7 @@
 /**
  * Choose the desired output format.
  *
- * @param format AndroidBitmapFormat to use
+ * @param format {@link AndroidBitmapFormat} to use for the output.
  * @return {@link ANDROID_IMAGE_DECODER_SUCCESS} if the format is compatible
  *         with the image and {@link ANDROID_IMAGE_DECODER_INVALID_CONVERSION}
  *         otherwise. In the latter case, the AImageDecoder uses the
@@ -196,7 +196,7 @@
  *           pointer is null, width or height is <= 0, or any existing crop is
  *           not contained by the image dimensions.
  */
-int AImageDecoder_setTargetSize(AImageDecoder*, int width, int height) __INTRODUCED_IN(30);
+int AImageDecoder_setTargetSize(AImageDecoder*, int32_t width, int32_t height) __INTRODUCED_IN(30);
 
 
 /**
@@ -219,7 +219,7 @@
  * @return ANDROID_IMAGE_DECODER result code.
  */
 int AImageDecoder_computeSampledSize(const AImageDecoder*, int sampleSize,
-                                     int* width, int* height) __INTRODUCED_IN(30);
+                                     int32_t* width, int32_t* height) __INTRODUCED_IN(30);
 /**
  * Specify how to crop the output after scaling (if any).
  *
@@ -276,18 +276,12 @@
         const AImageDecoderHeaderInfo*) __INTRODUCED_IN(30);
 
 /**
- * Report whether the encoded image represents an animation.
- */
-bool AImageDecoderHeaderInfo_isAnimated(
-        const AImageDecoderHeaderInfo*) __INTRODUCED_IN(30);
-
-/**
- * Report the AndroidBitmapFormat the AImageDecoder will decode to
+ * Report the {@link AndroidBitmapFormat} the AImageDecoder will decode to
  * by default. AImageDecoder will try to choose one that is sensible
  * for the image and the system. Note that this does not indicate the
  * encoded format of the image.
  */
-AndroidBitmapFormat AImageDecoderHeaderInfo_getAndroidBitmapFormat(
+int32_t AImageDecoderHeaderInfo_getAndroidBitmapFormat(
         const AImageDecoderHeaderInfo*) __INTRODUCED_IN(30);
 
 /**
diff --git a/include/android/sensor.h b/include/android/sensor.h
index 3ebe79f..12c00ad 100644
--- a/include/android/sensor.h
+++ b/include/android/sensor.h
@@ -245,6 +245,10 @@
      * {@link ASENSOR_TYPE_ACCELEROMETER_UNCALIBRATED}
      */
     ASENSOR_TYPE_ACCELEROMETER_UNCALIBRATED = 35,
+    /**
+     * {@link ASENSOR_TYPE_HINGE_ANGLE}
+     */
+    ASENSOR_TYPE_HINGE_ANGLE = 36,
 };
 
 /**
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index 31abb66..eeb8330 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -407,6 +407,33 @@
 
 #endif // __ANDROID_API__ >= 29
 
+#if __ANDROID_API__ >= 30
+
+/*
+ * Sets the intended frame rate for |surface_control|.
+ *
+ * On devices that are capable of running the display at different refresh rates, the system may
+ * choose a display refresh rate to better match this surface's frame rate. Usage of this API won't
+ * directly affect the application's frame production pipeline. However, because the system may
+ * change the display refresh rate, calls to this function may result in changes to Choreographer
+ * callback timings, and changes to the time interval at which the system releases buffers back to
+ * the application.
+ *
+ * |frameRate| is the intended frame rate of this surface, in frames per second. 0 is a special
+ * value that indicates the app will accept the system's choice for the display frame rate, which is
+ * the default behavior if this function isn't called. The frameRate param does *not* need to be a
+ * valid refresh rate for this device's display - e.g., it's fine to pass 30fps to a device that can
+ * only run the display at 60fps.
+ *
+ * Available since API level 30.
+ */
+void ASurfaceTransaction_setFrameRate(ASurfaceTransaction* transaction,
+                                      ASurfaceControl* surface_control,
+                                      float frameRate)
+                                      __INTRODUCED_IN(30);
+
+#endif // __ANDROID_API__ >= 30
+
 __END_DECLS
 
 #endif // ANDROID_SURFACE_CONTROL_H
diff --git a/libs/binder/BufferedTextOutput.cpp b/libs/binder/BufferedTextOutput.cpp
index fb424fd..824b56b 100644
--- a/libs/binder/BufferedTextOutput.cpp
+++ b/libs/binder/BufferedTextOutput.cpp
@@ -49,9 +49,10 @@
     }
     
     status_t append(const char* txt, size_t len) {
+        if (len > SIZE_MAX - bufferPos) return NO_MEMORY; // overflow
         if ((len+bufferPos) > bufferSize) {
+            if ((len + bufferPos) > SIZE_MAX / 3) return NO_MEMORY; // overflow
             size_t newSize = ((len+bufferPos)*3)/2;
-            if (newSize < (len+bufferPos)) return NO_MEMORY;    // overflow
             void* b = realloc(buffer, newSize);
             if (!b) return NO_MEMORY;
             buffer = (char*)b;
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index a9f8ae6..994e3b9 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -422,8 +422,10 @@
         const sp<ProcessState> proc(ProcessState::self());
         // grow objects
         if (mObjectsCapacity < mObjectsSize + numObjects) {
+            if ((size_t) numObjects > SIZE_MAX - mObjectsSize) return NO_MEMORY; // overflow
+            if (mObjectsSize + numObjects > SIZE_MAX / 3) return NO_MEMORY; // overflow
             size_t newSize = ((mObjectsSize + numObjects)*3)/2;
-            if (newSize*sizeof(binder_size_t) < mObjectsSize) return NO_MEMORY;   // overflow
+            if (newSize > SIZE_MAX / sizeof(binder_size_t)) return NO_MEMORY; // overflow
             binder_size_t *objects =
                 (binder_size_t*)realloc(mObjects, newSize*sizeof(binder_size_t));
             if (objects == (binder_size_t*)nullptr) {
@@ -1276,8 +1278,10 @@
         if (err != NO_ERROR) return err;
     }
     if (!enoughObjects) {
+        if (mObjectsSize > SIZE_MAX - 2) return NO_MEMORY; // overflow
+        if ((mObjectsSize + 2) > SIZE_MAX / 3) return NO_MEMORY; // overflow
         size_t newSize = ((mObjectsSize+2)*3)/2;
-        if (newSize*sizeof(binder_size_t) < mObjectsSize) return NO_MEMORY;   // overflow
+        if (newSize > SIZE_MAX / sizeof(binder_size_t)) return NO_MEMORY; // overflow
         binder_size_t* objects = (binder_size_t*)realloc(mObjects, newSize*sizeof(binder_size_t));
         if (objects == nullptr) return NO_MEMORY;
         mObjects = objects;
@@ -2405,6 +2409,8 @@
         return BAD_VALUE;
     }
 
+    if (len > SIZE_MAX - mDataSize) return NO_MEMORY; // overflow
+    if (mDataSize + len > SIZE_MAX / 3) return NO_MEMORY; // overflow
     size_t newSize = ((mDataSize+len)*3)/2;
     return (newSize <= mDataSize)
             ? (status_t) NO_MEMORY
diff --git a/libs/gralloc/types/Gralloc4.cpp b/libs/gralloc/types/Gralloc4.cpp
index 4851b44..4e8569f 100644
--- a/libs/gralloc/types/Gralloc4.cpp
+++ b/libs/gralloc/types/Gralloc4.cpp
@@ -42,6 +42,7 @@
 using aidl::android::hardware::graphics::common::StandardMetadataType;
 using aidl::android::hardware::graphics::common::XyColor;
 
+using BufferDescriptorInfo = android::hardware::graphics::mapper::V4_0::IMapper::BufferDescriptorInfo;
 using MetadataType = android::hardware::graphics::mapper::V4_0::IMapper::MetadataType;
 
 namespace android {
@@ -195,15 +196,32 @@
 status_t validateMetadataType(InputHidlVec* input, const MetadataType& expectedMetadataType);
 
 /**
- * encode is the main encoding function. It takes in T and uses the encodeHelper function to turn T
- * into the hidl_vec byte stream.
+ * encode/encodeMetadata are the main encoding functions. They take in T and uses the encodeHelper
+ * function to turn T into the hidl_vec byte stream.
  *
- * This function first calls the encodeHelper function to determine how large the hidl_vec
- * needs to be. It resizes the hidl_vec. Finally, it reruns the encodeHelper function which
+ * These functions first call the encodeHelper function to determine how large the hidl_vec
+ * needs to be. They resize the hidl_vec. Finally, it reruns the encodeHelper function which
  * encodes T into the hidl_vec byte stream.
  */
 template <class T>
-status_t encode(const MetadataType& metadataType, const T& input, hidl_vec<uint8_t>* output,
+status_t encode(const T& input, hidl_vec<uint8_t>* output, EncodeHelper<T> encodeHelper) {
+    OutputHidlVec outputHidlVec{output};
+
+    status_t err = encodeHelper(input, &outputHidlVec);
+    if (err) {
+        return err;
+    }
+
+    err = outputHidlVec.resize();
+    if (err) {
+        return err;
+    }
+
+    return encodeHelper(input, &outputHidlVec);
+}
+
+template <class T>
+status_t encodeMetadata(const MetadataType& metadataType, const T& input, hidl_vec<uint8_t>* output,
                 EncodeHelper<T> encodeHelper) {
     OutputHidlVec outputHidlVec{output};
 
@@ -231,21 +249,42 @@
 }
 
 template <class T>
-status_t encodeOptional(const MetadataType& metadataType, const std::optional<T>& input,
+status_t encodeOptionalMetadata(const MetadataType& metadataType, const std::optional<T>& input,
                         hidl_vec<uint8_t>* output, EncodeHelper<T> encodeHelper) {
     if (!input) {
         return NO_ERROR;
     }
-    return encode(metadataType, *input, output, encodeHelper);
+    return encodeMetadata(metadataType, *input, output, encodeHelper);
 }
 
 /**
- * decode is the main decode function. It takes in a hidl_vec and uses the decodeHelper function to
- * turn the hidl_vec byte stream into T. If an error occurs, the errorHandler function cleans up
- * T.
+ * decode/decodeMetadata are the main decoding functions. They take in a hidl_vec and use the
+ * decodeHelper function to turn the hidl_vec byte stream into T. If an error occurs, the
+ * errorHandler function cleans up T.
  */
 template <class T>
-status_t decode(const MetadataType& metadataType, const hidl_vec<uint8_t>& input, T* output,
+status_t decode(const hidl_vec<uint8_t>& input, T* output, DecodeHelper<T> decodeHelper,
+                ErrorHandler<T> errorHandler = nullptr) {
+    InputHidlVec inputHidlVec{&input};
+
+    status_t err = decodeHelper(&inputHidlVec, output);
+    if (err) {
+        return err;
+    }
+
+    err = inputHidlVec.hasRemainingData();
+    if (err) {
+        if (errorHandler) {
+            errorHandler(output);
+        }
+        return BAD_VALUE;
+    }
+
+    return NO_ERROR;
+}
+
+template <class T>
+status_t decodeMetadata(const MetadataType& metadataType, const hidl_vec<uint8_t>& input, T* output,
                 DecodeHelper<T> decodeHelper, ErrorHandler<T> errorHandler = nullptr) {
     InputHidlVec inputHidlVec{&input};
 
@@ -271,7 +310,7 @@
 }
 
 template <class T>
-status_t decodeOptional(const MetadataType& metadataType, const hidl_vec<uint8_t>& input,
+status_t decodeOptionalMetadata(const MetadataType& metadataType, const hidl_vec<uint8_t>& input,
                         std::optional<T>* output, DecodeHelper<T> decodeHelper) {
     if (!output) {
         return BAD_VALUE;
@@ -281,7 +320,7 @@
         return NO_ERROR;
     }
     T tmp;
-    status_t err = decode(metadataType, input, &tmp, decodeHelper);
+    status_t err = decodeMetadata(metadataType, input, &tmp, decodeHelper);
     if (!err) {
         *output = tmp;
     }
@@ -520,6 +559,66 @@
     return decodeInteger<int32_t>(input, &output->bottom);
 }
 
+status_t encodeBufferDescriptorInfoHelper(const BufferDescriptorInfo& input,
+        OutputHidlVec* output) {
+    status_t err = encodeString(input.name, output);
+    if (err) {
+        return err;
+    }
+    err = encodeInteger<uint32_t>(input.width, output);
+    if (err) {
+        return err;
+    }
+    err = encodeInteger<uint32_t>(input.height, output);
+    if (err) {
+        return err;
+    }
+    err = encodeInteger<uint32_t>(input.layerCount, output);
+    if (err) {
+        return err;
+    }
+    err = encodeInteger<int32_t>(static_cast<int32_t>(input.format), output);
+    if (err) {
+        return err;
+    }
+    err = encodeInteger<uint64_t>(input.usage, output);
+    if (err) {
+        return err;
+    }
+    return encodeInteger<uint64_t>(input.reservedSize, output);
+}
+
+status_t decodeBufferDescriptorInfoHelper(InputHidlVec* input, BufferDescriptorInfo* output) {
+    std::string name;
+    status_t err = decodeString(input, &name);
+    if (err) {
+        return err;
+    }
+    output->name = name;
+
+    err = decodeInteger<uint32_t>(input, &output->width);
+    if (err) {
+        return err;
+    }
+    err = decodeInteger<uint32_t>(input, &output->height);
+    if (err) {
+        return err;
+    }
+    err = decodeInteger<uint32_t>(input, &output->layerCount);
+    if (err) {
+        return err;
+    }
+    err = decodeInteger<int32_t>(input, reinterpret_cast<int32_t*>(&output->format));
+    if (err) {
+        return err;
+    }
+    err = decodeInteger<uint64_t>(input, &output->usage);
+    if (err) {
+        return err;
+    }
+    return decodeInteger<uint64_t>(input, &output->reservedSize);
+}
+
 status_t encodePlaneLayoutComponent(const PlaneLayoutComponent& input, OutputHidlVec* output) {
     if (!output) {
         return BAD_VALUE;
@@ -799,260 +898,269 @@
 /**
  * Public API functions
  */
+status_t encodeBufferDescriptorInfo(const BufferDescriptorInfo& bufferDescriptorInfo,
+        hidl_vec<uint8_t>* outBufferDescriptorInfo) {
+    return encode(bufferDescriptorInfo, outBufferDescriptorInfo, encodeBufferDescriptorInfoHelper);
+}
+
+status_t decodeBufferDescriptorInfo(const hidl_vec<uint8_t>& bufferDescriptorInfo,
+        BufferDescriptorInfo* outBufferDescriptorInfo) {
+    return decode(bufferDescriptorInfo, outBufferDescriptorInfo, decodeBufferDescriptorInfoHelper);
+}
 
 status_t encodeBufferId(uint64_t bufferId, hidl_vec<uint8_t>* outBufferId) {
-    return encode(MetadataType_BufferId, bufferId, outBufferId, encodeInteger);
+    return encodeMetadata(MetadataType_BufferId, bufferId, outBufferId, encodeInteger);
 }
 
 status_t decodeBufferId(const hidl_vec<uint8_t>& bufferId, uint64_t* outBufferId) {
-    return decode(MetadataType_BufferId, bufferId, outBufferId, decodeInteger);
+    return decodeMetadata(MetadataType_BufferId, bufferId, outBufferId, decodeInteger);
 }
 
 status_t encodeName(const std::string& name, hidl_vec<uint8_t>* outName) {
-    return encode(MetadataType_Name, name, outName, encodeString);
+    return encodeMetadata(MetadataType_Name, name, outName, encodeString);
 }
 
 status_t decodeName(const hidl_vec<uint8_t>& name, std::string* outName) {
-    return decode(MetadataType_Name, name, outName, decodeString);
+    return decodeMetadata(MetadataType_Name, name, outName, decodeString);
 }
 
 status_t encodeWidth(uint64_t width, hidl_vec<uint8_t>* outWidth) {
-    return encode(MetadataType_Width, width, outWidth, encodeInteger);
+    return encodeMetadata(MetadataType_Width, width, outWidth, encodeInteger);
 }
 
 status_t decodeWidth(const hidl_vec<uint8_t>& width, uint64_t* outWidth) {
-    return decode(MetadataType_Width, width, outWidth, decodeInteger);
+    return decodeMetadata(MetadataType_Width, width, outWidth, decodeInteger);
 }
 
 status_t encodeHeight(uint64_t height, hidl_vec<uint8_t>* outHeight) {
-    return encode(MetadataType_Height, height, outHeight, encodeInteger);
+    return encodeMetadata(MetadataType_Height, height, outHeight, encodeInteger);
 }
 
 status_t decodeHeight(const hidl_vec<uint8_t>& height, uint64_t* outHeight) {
-    return decode(MetadataType_Height, height, outHeight, decodeInteger);
+    return decodeMetadata(MetadataType_Height, height, outHeight, decodeInteger);
 }
 
 status_t encodeLayerCount(uint64_t layerCount, hidl_vec<uint8_t>* outLayerCount) {
-    return encode(MetadataType_LayerCount, layerCount, outLayerCount, encodeInteger);
+    return encodeMetadata(MetadataType_LayerCount, layerCount, outLayerCount, encodeInteger);
 }
 
 status_t decodeLayerCount(const hidl_vec<uint8_t>& layerCount, uint64_t* outLayerCount) {
-    return decode(MetadataType_LayerCount, layerCount, outLayerCount, decodeInteger);
+    return decodeMetadata(MetadataType_LayerCount, layerCount, outLayerCount, decodeInteger);
 }
 
 status_t encodePixelFormatRequested(const hardware::graphics::common::V1_2::PixelFormat& pixelFormatRequested,
         hidl_vec<uint8_t>* outPixelFormatRequested) {
-    return encode(MetadataType_PixelFormatRequested, static_cast<int32_t>(pixelFormatRequested),
+    return encodeMetadata(MetadataType_PixelFormatRequested, static_cast<int32_t>(pixelFormatRequested),
                   outPixelFormatRequested, encodeInteger);
 }
 
 status_t decodePixelFormatRequested(const hidl_vec<uint8_t>& pixelFormatRequested,
         hardware::graphics::common::V1_2::PixelFormat* outPixelFormatRequested) {
-    return decode(MetadataType_PixelFormatRequested, pixelFormatRequested,
+    return decodeMetadata(MetadataType_PixelFormatRequested, pixelFormatRequested,
                   reinterpret_cast<int32_t*>(outPixelFormatRequested), decodeInteger);
 }
 
 status_t encodePixelFormatFourCC(uint32_t pixelFormatFourCC, hidl_vec<uint8_t>* outPixelFormatFourCC) {
-    return encode(MetadataType_PixelFormatFourCC, pixelFormatFourCC, outPixelFormatFourCC,
+    return encodeMetadata(MetadataType_PixelFormatFourCC, pixelFormatFourCC, outPixelFormatFourCC,
                   encodeInteger);
 }
 
 status_t decodePixelFormatFourCC(const hidl_vec<uint8_t>& pixelFormatFourCC, uint32_t* outPixelFormatFourCC) {
-    return decode(MetadataType_PixelFormatFourCC, pixelFormatFourCC, outPixelFormatFourCC,
+    return decodeMetadata(MetadataType_PixelFormatFourCC, pixelFormatFourCC, outPixelFormatFourCC,
                   decodeInteger);
 }
 
 status_t encodePixelFormatModifier(uint64_t pixelFormatModifier, hidl_vec<uint8_t>* outPixelFormatModifier) {
-    return encode(MetadataType_PixelFormatModifier, pixelFormatModifier, outPixelFormatModifier,
+    return encodeMetadata(MetadataType_PixelFormatModifier, pixelFormatModifier, outPixelFormatModifier,
                   encodeInteger);
 }
 
 status_t decodePixelFormatModifier(const hidl_vec<uint8_t>& pixelFormatModifier, uint64_t* outPixelFormatModifier) {
-    return decode(MetadataType_PixelFormatModifier, pixelFormatModifier, outPixelFormatModifier,
+    return decodeMetadata(MetadataType_PixelFormatModifier, pixelFormatModifier, outPixelFormatModifier,
                   decodeInteger);
 }
 
 status_t encodeUsage(uint64_t usage, hidl_vec<uint8_t>* outUsage) {
-    return encode(MetadataType_Usage, usage, outUsage, encodeInteger);
+    return encodeMetadata(MetadataType_Usage, usage, outUsage, encodeInteger);
 }
 
 status_t decodeUsage(const hidl_vec<uint8_t>& usage, uint64_t* outUsage) {
-    return decode(MetadataType_Usage, usage, outUsage, decodeInteger);
+    return decodeMetadata(MetadataType_Usage, usage, outUsage, decodeInteger);
 }
 
 status_t encodeAllocationSize(uint64_t allocationSize, hidl_vec<uint8_t>* outAllocationSize) {
-    return encode(MetadataType_AllocationSize, allocationSize, outAllocationSize, encodeInteger);
+    return encodeMetadata(MetadataType_AllocationSize, allocationSize, outAllocationSize, encodeInteger);
 }
 
 status_t decodeAllocationSize(const hidl_vec<uint8_t>& allocationSize, uint64_t* outAllocationSize) {
-    return decode(MetadataType_AllocationSize, allocationSize, outAllocationSize, decodeInteger);
+    return decodeMetadata(MetadataType_AllocationSize, allocationSize, outAllocationSize, decodeInteger);
 }
 
 status_t encodeProtectedContent(uint64_t protectedContent, hidl_vec<uint8_t>* outProtectedContent) {
-    return encode(MetadataType_ProtectedContent, protectedContent, outProtectedContent,
+    return encodeMetadata(MetadataType_ProtectedContent, protectedContent, outProtectedContent,
                   encodeInteger);
 }
 
 status_t decodeProtectedContent(const hidl_vec<uint8_t>& protectedContent, uint64_t* outProtectedContent) {
-    return decode(MetadataType_ProtectedContent, protectedContent, outProtectedContent,
+    return decodeMetadata(MetadataType_ProtectedContent, protectedContent, outProtectedContent,
                   decodeInteger);
 }
 
 status_t encodeCompression(const ExtendableType& compression, hidl_vec<uint8_t>* outCompression) {
-    return encode(MetadataType_Compression, compression, outCompression, encodeExtendableType);
+    return encodeMetadata(MetadataType_Compression, compression, outCompression, encodeExtendableType);
 }
 
 status_t decodeCompression(const hidl_vec<uint8_t>& compression, ExtendableType* outCompression) {
-    return decode(MetadataType_Compression, compression, outCompression, decodeExtendableType,
+    return decodeMetadata(MetadataType_Compression, compression, outCompression, decodeExtendableType,
                   clearExtendableType);
 }
 
 status_t encodeInterlaced(const ExtendableType& interlaced, hidl_vec<uint8_t>* outInterlaced) {
-    return encode(MetadataType_Interlaced, interlaced, outInterlaced, encodeExtendableType);
+    return encodeMetadata(MetadataType_Interlaced, interlaced, outInterlaced, encodeExtendableType);
 }
 
 status_t decodeInterlaced(const hidl_vec<uint8_t>& interlaced, ExtendableType* outInterlaced) {
-    return decode(MetadataType_Interlaced, interlaced, outInterlaced, decodeExtendableType,
+    return decodeMetadata(MetadataType_Interlaced, interlaced, outInterlaced, decodeExtendableType,
                   clearExtendableType);
 }
 
 status_t encodeChromaSiting(const ExtendableType& chromaSiting, hidl_vec<uint8_t>* outChromaSiting) {
-    return encode(MetadataType_ChromaSiting, chromaSiting, outChromaSiting, encodeExtendableType);
+    return encodeMetadata(MetadataType_ChromaSiting, chromaSiting, outChromaSiting, encodeExtendableType);
 }
 
 status_t decodeChromaSiting(const hidl_vec<uint8_t>& chromaSiting, ExtendableType* outChromaSiting) {
-    return decode(MetadataType_ChromaSiting, chromaSiting, outChromaSiting, decodeExtendableType,
+    return decodeMetadata(MetadataType_ChromaSiting, chromaSiting, outChromaSiting, decodeExtendableType,
                   clearExtendableType);
 }
 
 status_t encodePlaneLayouts(const std::vector<PlaneLayout>& planeLayouts, hidl_vec<uint8_t>* outPlaneLayouts) {
-    return encode(MetadataType_PlaneLayouts, planeLayouts, outPlaneLayouts,
+    return encodeMetadata(MetadataType_PlaneLayouts, planeLayouts, outPlaneLayouts,
                   encodePlaneLayoutsHelper);
 }
 
 status_t decodePlaneLayouts(const hidl_vec<uint8_t>& planeLayouts, std::vector<PlaneLayout>* outPlaneLayouts) {
-    return decode(MetadataType_PlaneLayouts, planeLayouts, outPlaneLayouts,
+    return decodeMetadata(MetadataType_PlaneLayouts, planeLayouts, outPlaneLayouts,
                   decodePlaneLayoutsHelper, clearPlaneLayouts);
 }
 
 status_t encodeDataspace(const Dataspace& dataspace, hidl_vec<uint8_t>* outDataspace) {
-    return encode(MetadataType_Dataspace, static_cast<int32_t>(dataspace), outDataspace,
+    return encodeMetadata(MetadataType_Dataspace, static_cast<int32_t>(dataspace), outDataspace,
                   encodeInteger);
 }
 
 status_t decodeDataspace(const hidl_vec<uint8_t>& dataspace, Dataspace* outDataspace) {
-    return decode(MetadataType_Dataspace, dataspace, reinterpret_cast<int32_t*>(outDataspace),
+    return decodeMetadata(MetadataType_Dataspace, dataspace, reinterpret_cast<int32_t*>(outDataspace),
                   decodeInteger);
 }
 
 status_t encodeBlendMode(const BlendMode& blendMode, hidl_vec<uint8_t>* outBlendMode) {
-    return encode(MetadataType_BlendMode, static_cast<int32_t>(blendMode), outBlendMode,
+    return encodeMetadata(MetadataType_BlendMode, static_cast<int32_t>(blendMode), outBlendMode,
                   encodeInteger);
 }
 
 status_t decodeBlendMode(const hidl_vec<uint8_t>& blendMode, BlendMode* outBlendMode) {
-    return decode(MetadataType_BlendMode, blendMode, reinterpret_cast<int32_t*>(outBlendMode),
+    return decodeMetadata(MetadataType_BlendMode, blendMode, reinterpret_cast<int32_t*>(outBlendMode),
                   decodeInteger);
 }
 
 status_t encodeSmpte2086(const std::optional<Smpte2086>& smpte2086,
                          hidl_vec<uint8_t>* outSmpte2086) {
-    return encodeOptional(MetadataType_Smpte2086, smpte2086, outSmpte2086, encodeSmpte2086Helper);
+    return encodeOptionalMetadata(MetadataType_Smpte2086, smpte2086, outSmpte2086, encodeSmpte2086Helper);
 }
 
 status_t decodeSmpte2086(const hidl_vec<uint8_t>& smpte2086,
                          std::optional<Smpte2086>* outSmpte2086) {
-    return decodeOptional(MetadataType_Smpte2086, smpte2086, outSmpte2086, decodeSmpte2086Helper);
+    return decodeOptionalMetadata(MetadataType_Smpte2086, smpte2086, outSmpte2086, decodeSmpte2086Helper);
 }
 
 status_t encodeCta861_3(const std::optional<Cta861_3>& cta861_3, hidl_vec<uint8_t>* outCta861_3) {
-    return encodeOptional(MetadataType_Cta861_3, cta861_3, outCta861_3, encodeCta861_3Helper);
+    return encodeOptionalMetadata(MetadataType_Cta861_3, cta861_3, outCta861_3, encodeCta861_3Helper);
 }
 
 status_t decodeCta861_3(const hidl_vec<uint8_t>& cta861_3, std::optional<Cta861_3>* outCta861_3) {
-    return decodeOptional(MetadataType_Cta861_3, cta861_3, outCta861_3, decodeCta861_3Helper);
+    return decodeOptionalMetadata(MetadataType_Cta861_3, cta861_3, outCta861_3, decodeCta861_3Helper);
 }
 
 status_t encodeSmpte2094_40(const std::optional<std::vector<uint8_t>>& smpte2094_40,
                             hidl_vec<uint8_t>* outSmpte2094_40) {
-    return encodeOptional(MetadataType_Smpte2094_40, smpte2094_40, outSmpte2094_40,
+    return encodeOptionalMetadata(MetadataType_Smpte2094_40, smpte2094_40, outSmpte2094_40,
                           encodeByteVector);
 }
 
 status_t decodeSmpte2094_40(const hidl_vec<uint8_t>& smpte2094_40,
                             std::optional<std::vector<uint8_t>>* outSmpte2094_40) {
-    return decodeOptional(MetadataType_Smpte2094_40, smpte2094_40, outSmpte2094_40,
+    return decodeOptionalMetadata(MetadataType_Smpte2094_40, smpte2094_40, outSmpte2094_40,
                           decodeByteVector);
 }
 
 status_t encodeUint32(const MetadataType& metadataType, uint32_t input,
                       hidl_vec<uint8_t>* output) {
-    return encode(metadataType, input, output, encodeInteger);
+    return encodeMetadata(metadataType, input, output, encodeInteger);
 }
 
 status_t decodeUint32(const MetadataType& metadataType, const hidl_vec<uint8_t>& input,
                       uint32_t* output) {
-    return decode(metadataType, input, output, decodeInteger);
+    return decodeMetadata(metadataType, input, output, decodeInteger);
 }
 
 status_t encodeInt32(const MetadataType& metadataType, int32_t input,
                      hidl_vec<uint8_t>* output) {
-    return encode(metadataType, input, output, encodeInteger);
+    return encodeMetadata(metadataType, input, output, encodeInteger);
 }
 
 status_t decodeInt32(const MetadataType& metadataType, const hidl_vec<uint8_t>& input,
                      int32_t* output) {
-    return decode(metadataType, input, output, decodeInteger);
+    return decodeMetadata(metadataType, input, output, decodeInteger);
 }
 
 status_t encodeUint64(const MetadataType& metadataType, uint64_t input,
                       hidl_vec<uint8_t>* output) {
-    return encode(metadataType, input, output, encodeInteger);
+    return encodeMetadata(metadataType, input, output, encodeInteger);
 }
 
 status_t decodeUint64(const MetadataType& metadataType, const hidl_vec<uint8_t>& input,
                       uint64_t* output) {
-    return decode(metadataType, input, output, decodeInteger);
+    return decodeMetadata(metadataType, input, output, decodeInteger);
 }
 
 status_t encodeInt64(const MetadataType& metadataType, int64_t input,
                      hidl_vec<uint8_t>* output) {
-    return encode(metadataType, input, output, encodeInteger);
+    return encodeMetadata(metadataType, input, output, encodeInteger);
 }
 
 status_t decodeInt64(const MetadataType& metadataType, const hidl_vec<uint8_t>& input,
                      int64_t* output) {
-    return decode(metadataType, input, output, decodeInteger);
+    return decodeMetadata(metadataType, input, output, decodeInteger);
 }
 
 status_t encodeFloat(const MetadataType& metadataType, float input,
                      hidl_vec<uint8_t>* output) {
-    return encode(metadataType, input, output, encodeInteger);
+    return encodeMetadata(metadataType, input, output, encodeInteger);
 }
 
 status_t decodeFloat(const MetadataType& metadataType, const hidl_vec<uint8_t>& input,
                      float* output) {
-    return decode(metadataType, input, output, decodeInteger);
+    return decodeMetadata(metadataType, input, output, decodeInteger);
 }
 
 status_t encodeDouble(const MetadataType& metadataType, double input,
                       hidl_vec<uint8_t>* output) {
-    return encode(metadataType, input, output, encodeInteger);
+    return encodeMetadata(metadataType, input, output, encodeInteger);
 }
 
 status_t decodeDouble(const MetadataType& metadataType, const hidl_vec<uint8_t>& input,
                       double* output) {
-    return decode(metadataType, input, output, decodeInteger);
+    return decodeMetadata(metadataType, input, output, decodeInteger);
 }
 
 status_t encodeString(const MetadataType& metadataType, const std::string& input,
                       hidl_vec<uint8_t>* output) {
-    return encode(metadataType, input, output, encodeString);
+    return encodeMetadata(metadataType, input, output, encodeString);
 }
 
 status_t decodeString(const MetadataType& metadataType, const hidl_vec<uint8_t>& input,
                       std::string* output) {
-    return decode(metadataType, input, output, decodeString);
+    return decodeMetadata(metadataType, input, output, decodeString);
 }
 
 bool isStandardMetadataType(const MetadataType& metadataType) {
diff --git a/libs/gralloc/types/fuzzer/gralloctypes.cpp b/libs/gralloc/types/fuzzer/gralloctypes.cpp
index b18f407..b5644be 100644
--- a/libs/gralloc/types/fuzzer/gralloctypes.cpp
+++ b/libs/gralloc/types/fuzzer/gralloctypes.cpp
@@ -31,6 +31,7 @@
 
 using ::android::status_t;
 using MetadataType = android::hardware::graphics::mapper::V4_0::IMapper::MetadataType;
+using BufferDescriptorInfo = android::hardware::graphics::mapper::V4_0::IMapper::BufferDescriptorInfo;
 
 #define GRALLOCTYPES_DECODE(T, FUNC) \
     [] (const ::android::hardware::hidl_vec<uint8_t>& vec) {\
@@ -53,6 +54,7 @@
 
 // clang-format off
 std::vector<GrallocTypesDecode> GRALLOCTYPES_DECODE_FUNCTIONS {
+    GRALLOCTYPES_DECODE(BufferDescriptorInfo, ::android::gralloc4::decodeBufferDescriptorInfo),
     GRALLOCTYPES_DECODE(uint64_t, ::android::gralloc4::decodeBufferId),
     GRALLOCTYPES_DECODE(std::string, ::android::gralloc4::decodeName),
     GRALLOCTYPES_DECODE(uint64_t, ::android::gralloc4::decodeWidth),
diff --git a/libs/gralloc/types/include/gralloctypes/Gralloc4.h b/libs/gralloc/types/include/gralloctypes/Gralloc4.h
index 42c6e15..d855439 100644
--- a/libs/gralloc/types/include/gralloctypes/Gralloc4.h
+++ b/libs/gralloc/types/include/gralloctypes/Gralloc4.h
@@ -414,6 +414,12 @@
 /*---------------------------------------------------------------------------------------------*/
 
 /**
+ * The functions below encode and decode BufferDescriptorInfo into a byte stream.
+ */
+status_t encodeBufferDescriptorInfo(const android::hardware::graphics::mapper::V4_0::IMapper::BufferDescriptorInfo& bufferDescriptorInfo, android::hardware::hidl_vec<uint8_t>* outBufferDescriptorInfo);
+status_t decodeBufferDescriptorInfo(const android::hardware::hidl_vec<uint8_t>& bufferDescriptorInfo, android::hardware::graphics::mapper::V4_0::IMapper::BufferDescriptorInfo* outBufferDescriptorInfo);
+
+/**
  * The functions below encode and decode standard metadata into a byte stream. It is STRONGLY
  * recommended that both the vendor and system partitions use these functions when getting
  * and setting metadata through gralloc 4 (IMapper 4.0).
diff --git a/libs/gralloc/types/tests/Gralloc4_test.cpp b/libs/gralloc/types/tests/Gralloc4_test.cpp
index 3542ed9..dbe41f1 100644
--- a/libs/gralloc/types/tests/Gralloc4_test.cpp
+++ b/libs/gralloc/types/tests/Gralloc4_test.cpp
@@ -25,6 +25,7 @@
 using android::hardware::hidl_vec;
 
 using android::hardware::graphics::common::V1_2::PixelFormat;
+using android::hardware::graphics::common::V1_2::BufferUsage;
 
 using aidl::android::hardware::graphics::common::BlendMode;
 using aidl::android::hardware::graphics::common::ChromaSiting;
@@ -41,6 +42,7 @@
 using aidl::android::hardware::graphics::common::StandardMetadataType;
 using aidl::android::hardware::graphics::common::XyColor;
 
+using BufferDescriptorInfo = android::hardware::graphics::mapper::V4_0::IMapper::BufferDescriptorInfo;
 using MetadataType = android::hardware::graphics::mapper::V4_0::IMapper::MetadataType;
 
 namespace android {
@@ -435,6 +437,19 @@
     ASSERT_NO_FATAL_FAILURE(testHelperStableAidlTypeOptional(GetParam(), gralloc4::encodeSmpte2094_40, gralloc4::decodeSmpte2094_40));
 }
 
+class Gralloc4TestBufferDescriptorInfo : public testing::TestWithParam<BufferDescriptorInfo> { };
+
+INSTANTIATE_TEST_CASE_P(
+        Gralloc4TestBufferDescriptorInfoParams, Gralloc4TestBufferDescriptorInfo,
+        ::testing::Values(BufferDescriptorInfo{"BufferName", 64, 64, 1,
+                PixelFormat::RGBA_8888,
+                static_cast<uint64_t>(BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN),
+                1024}));
+
+TEST_P(Gralloc4TestBufferDescriptorInfo, BufferDescriptorInfo) {
+    ASSERT_NO_FATAL_FAILURE(testHelperConst(GetParam(), gralloc4::encodeBufferDescriptorInfo, gralloc4::decodeBufferDescriptorInfo));
+}
+
 class Gralloc4TestErrors : public testing::Test { };
 
 TEST_F(Gralloc4TestErrors, Gralloc4TestEncodeNull) {
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 9674e54..d2b25a2 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -48,7 +48,7 @@
     mBufferItemConsumer->setBufferFreedListener(this);
     mBufferItemConsumer->setDefaultBufferSize(mWidth, mHeight);
     mBufferItemConsumer->setDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888);
-    mBufferItemConsumer->setTransformHint(mSurfaceControl->getTransformHint());
+    mTransformHint = mSurfaceControl->getTransformHint();
 
     mNumAcquired = 0;
     mNumFrameAvailable = 0;
@@ -62,7 +62,6 @@
     mWidth = width;
     mHeight = height;
     mBufferItemConsumer->setDefaultBufferSize(mWidth, mHeight);
-    mBufferItemConsumer->setTransformHint(mSurfaceControl->getTransformHint());
 }
 
 static void transactionCallbackThunk(void* context, nsecs_t latchTime,
@@ -155,6 +154,7 @@
     t->setFrame(mSurfaceControl, {0, 0, (int32_t)buffer->getWidth(), (int32_t)buffer->getHeight()});
     t->setCrop(mSurfaceControl, computeCrop(bufferItem));
     t->setTransform(mSurfaceControl, bufferItem.mTransform);
+    t->setTransformToDisplayInverse(mSurfaceControl, bufferItem.mTransformToDisplayInverse);
 
     if (applyTransaction) {
         t->apply();
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index 842af18..a60bc4d 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -158,6 +158,13 @@
     return query(window, NATIVE_WINDOW_DATASPACE);
 }
 
+int32_t ANativeWindow_setFrameRate(ANativeWindow* window, float frameRate) {
+    if (!window || !query(window, NATIVE_WINDOW_IS_VALID) || frameRate < 0) {
+        return -EINVAL;
+    }
+    return native_window_set_frame_rate(window, frameRate);
+}
+
 /**************************************************************************************************
  * vndk-stable
  **************************************************************************************************/
diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h
index 3e436e3..262aee3 100644
--- a/libs/nativewindow/include/android/native_window.h
+++ b/libs/nativewindow/include/android/native_window.h
@@ -230,6 +230,39 @@
 
 #endif // __ANDROID_API__ >= 28
 
+#if __ANDROID_API__ >= 30
+
+/**
+ * Sets the intended frame rate for this window.
+ *
+ * On devices that are capable of running the display at different refresh
+ * rates, the system may choose a display refresh rate to better match this
+ * window's frame rate. Usage of this API won't introduce frame rate throttling,
+ * or affect other aspects of the application's frame production
+ * pipeline. However, because the system may change the display refresh rate,
+ * calls to this function may result in changes to Choreographer callback
+ * timings, and changes to the time interval at which the system releases
+ * buffers back to the application.
+ *
+ * Note that this only has an effect for windows presented on the display. If
+ * this ANativeWindow is consumed by something other than the system compositor,
+ * e.g. a media codec, this call has no effect.
+ *
+ * Available since API level 30.
+ *
+ * \param frameRate The intended frame rate of this window, in frames per
+ * second. 0 is a special value that indicates the app will accept the system's
+ * choice for the display frame rate, which is the default behavior if this
+ * function isn't called. The frameRate param does *not* need to be a valid
+ * refresh rate for this device's display - e.g., it's fine to pass 30fps to a
+ * device that can only run the display at 60fps.
+ *
+ * \return 0 for success, -EINVAL if the window or frame rate are invalid.
+ */
+int32_t ANativeWindow_setFrameRate(ANativeWindow* window, float frameRate) __INTRODUCED_IN(30);
+
+#endif // __ANDROID_API__ >= 30
+
 #ifdef __cplusplus
 };
 #endif
diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt
index f59e8f0..3002da2 100644
--- a/libs/nativewindow/libnativewindow.map.txt
+++ b/libs/nativewindow/libnativewindow.map.txt
@@ -43,6 +43,7 @@
     ANativeWindow_setDequeueTimeout; # apex # introduced=30
     ANativeWindow_setSharedBufferMode; # llndk
     ANativeWindow_setSwapInterval; # llndk
+    ANativeWindow_setFrameRate; # introduced=30
     ANativeWindow_setUsage; # llndk
     ANativeWindow_unlockAndPost;
   local:
diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp
index 09659fe..e257704 100644
--- a/libs/renderengine/gl/GLESRenderEngine.cpp
+++ b/libs/renderengine/gl/GLESRenderEngine.cpp
@@ -960,7 +960,7 @@
 }
 
 status_t GLESRenderEngine::drawLayers(const DisplaySettings& display,
-                                      const std::vector<LayerSettings>& layers,
+                                      const std::vector<const LayerSettings*>& layers,
                                       ANativeWindowBuffer* const buffer,
                                       const bool useFramebufferCache, base::unique_fd&& bufferFence,
                                       base::unique_fd* drawFence) {
@@ -985,9 +985,9 @@
     // Blurs in multiple layers are not supported, given the cost of the shader.
     const LayerSettings* blurLayer = nullptr;
     if (CC_LIKELY(mBlurFilter != nullptr)) {
-        for (auto const& layer : layers) {
-            if (layer.backgroundBlurRadius > 0) {
-                blurLayer = &layer;
+        for (auto const layer : layers) {
+            if (layer->backgroundBlurRadius > 0) {
+                blurLayer = layer;
             }
         }
     }
@@ -1035,9 +1035,9 @@
                         .setTexCoords(2 /* size */)
                         .setCropCoords(2 /* size */)
                         .build();
-    for (auto const& layer : layers) {
-        if (blurLayer == &layer) {
-            auto status = mBlurFilter->prepare(layer.backgroundBlurRadius);
+    for (auto const layer : layers) {
+        if (blurLayer == layer) {
+            auto status = mBlurFilter->prepare(layer->backgroundBlurRadius);
             if (status != NO_ERROR) {
                 ALOGE("Failed to render blur effect! Aborting GPU composition for buffer (%p).",
                       buffer->handle);
@@ -1065,40 +1065,40 @@
             }
         }
 
-        mState.maxMasteringLuminance = layer.source.buffer.maxMasteringLuminance;
-        mState.maxContentLuminance = layer.source.buffer.maxContentLuminance;
-        mState.projectionMatrix = projectionMatrix * layer.geometry.positionTransform;
+        mState.maxMasteringLuminance = layer->source.buffer.maxMasteringLuminance;
+        mState.maxContentLuminance = layer->source.buffer.maxContentLuminance;
+        mState.projectionMatrix = projectionMatrix * layer->geometry.positionTransform;
 
-        const FloatRect bounds = layer.geometry.boundaries;
+        const FloatRect bounds = layer->geometry.boundaries;
         Mesh::VertexArray<vec2> position(mesh.getPositionArray<vec2>());
         position[0] = vec2(bounds.left, bounds.top);
         position[1] = vec2(bounds.left, bounds.bottom);
         position[2] = vec2(bounds.right, bounds.bottom);
         position[3] = vec2(bounds.right, bounds.top);
 
-        setupLayerCropping(layer, mesh);
-        setColorTransform(display.colorTransform * layer.colorTransform);
+        setupLayerCropping(*layer, mesh);
+        setColorTransform(display.colorTransform * layer->colorTransform);
 
         bool usePremultipliedAlpha = true;
         bool disableTexture = true;
         bool isOpaque = false;
-        if (layer.source.buffer.buffer != nullptr) {
+        if (layer->source.buffer.buffer != nullptr) {
             disableTexture = false;
-            isOpaque = layer.source.buffer.isOpaque;
+            isOpaque = layer->source.buffer.isOpaque;
 
-            sp<GraphicBuffer> gBuf = layer.source.buffer.buffer;
-            bindExternalTextureBuffer(layer.source.buffer.textureName, gBuf,
-                                      layer.source.buffer.fence);
+            sp<GraphicBuffer> gBuf = layer->source.buffer.buffer;
+            bindExternalTextureBuffer(layer->source.buffer.textureName, gBuf,
+                                      layer->source.buffer.fence);
 
-            usePremultipliedAlpha = layer.source.buffer.usePremultipliedAlpha;
-            Texture texture(Texture::TEXTURE_EXTERNAL, layer.source.buffer.textureName);
-            mat4 texMatrix = layer.source.buffer.textureTransform;
+            usePremultipliedAlpha = layer->source.buffer.usePremultipliedAlpha;
+            Texture texture(Texture::TEXTURE_EXTERNAL, layer->source.buffer.textureName);
+            mat4 texMatrix = layer->source.buffer.textureTransform;
 
             texture.setMatrix(texMatrix.asArray());
-            texture.setFiltering(layer.source.buffer.useTextureFiltering);
+            texture.setFiltering(layer->source.buffer.useTextureFiltering);
 
             texture.setDimensions(gBuf->getWidth(), gBuf->getHeight());
-            setSourceY410BT2020(layer.source.buffer.isY410BT2020);
+            setSourceY410BT2020(layer->source.buffer.isY410BT2020);
 
             renderengine::Mesh::VertexArray<vec2> texCoords(mesh.getTexCoordArray<vec2>());
             texCoords[0] = vec2(0.0, 0.0);
@@ -1108,32 +1108,32 @@
             setupLayerTexturing(texture);
         }
 
-        const half3 solidColor = layer.source.solidColor;
-        const half4 color = half4(solidColor.r, solidColor.g, solidColor.b, layer.alpha);
+        const half3 solidColor = layer->source.solidColor;
+        const half4 color = half4(solidColor.r, solidColor.g, solidColor.b, layer->alpha);
         // Buffer sources will have a black solid color ignored in the shader,
         // so in that scenario the solid color passed here is arbitrary.
         setupLayerBlending(usePremultipliedAlpha, isOpaque, disableTexture, color,
-                           layer.geometry.roundedCornersRadius);
-        if (layer.disableBlending) {
+                           layer->geometry.roundedCornersRadius);
+        if (layer->disableBlending) {
             glDisable(GL_BLEND);
         }
-        setSourceDataSpace(layer.sourceDataspace);
+        setSourceDataSpace(layer->sourceDataspace);
 
-        if (layer.shadow.length > 0.0f) {
-            handleShadow(layer.geometry.boundaries, layer.geometry.roundedCornersRadius,
-                         layer.shadow);
+        if (layer->shadow.length > 0.0f) {
+            handleShadow(layer->geometry.boundaries, layer->geometry.roundedCornersRadius,
+                         layer->shadow);
         }
         // We only want to do a special handling for rounded corners when having rounded corners
         // is the only reason it needs to turn on blending, otherwise, we handle it like the
         // usual way since it needs to turn on blending anyway.
-        else if (layer.geometry.roundedCornersRadius > 0.0 && color.a >= 1.0f && isOpaque) {
-            handleRoundedCorners(display, layer, mesh);
+        else if (layer->geometry.roundedCornersRadius > 0.0 && color.a >= 1.0f && isOpaque) {
+            handleRoundedCorners(display, *layer, mesh);
         } else {
             drawMesh(mesh);
         }
 
         // Cleanup if there's a buffer source
-        if (layer.source.buffer.buffer != nullptr) {
+        if (layer->source.buffer.buffer != nullptr) {
             disableBlending();
             setSourceY410BT2020(false);
             disableTexturing();
diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h
index 547235a..45c85de 100644
--- a/libs/renderengine/gl/GLESRenderEngine.h
+++ b/libs/renderengine/gl/GLESRenderEngine.h
@@ -71,7 +71,8 @@
     bool isProtected() const override { return mInProtectedContext; }
     bool supportsProtectedContent() const override;
     bool useProtectedContext(bool useProtectedContext) override;
-    status_t drawLayers(const DisplaySettings& display, const std::vector<LayerSettings>& layers,
+    status_t drawLayers(const DisplaySettings& display,
+                        const std::vector<const LayerSettings*>& layers,
                         ANativeWindowBuffer* buffer, const bool useFramebufferCache,
                         base::unique_fd&& bufferFence, base::unique_fd* drawFence) override;
 
diff --git a/libs/renderengine/gl/filters/BlurFilter.cpp b/libs/renderengine/gl/filters/BlurFilter.cpp
index a554687..a18a999 100644
--- a/libs/renderengine/gl/filters/BlurFilter.cpp
+++ b/libs/renderengine/gl/filters/BlurFilter.cpp
@@ -42,9 +42,20 @@
     ATRACE_NAME("BlurFilter::setAsDrawTarget");
 
     if (!mTexturesAllocated) {
-        const uint32_t fboWidth = floorf(display.physicalDisplay.width() * kFboScale);
-        const uint32_t fboHeight = floorf(display.physicalDisplay.height() * kFboScale);
-        mCompositionFbo.allocateBuffers(fboWidth, fboHeight);
+        mDisplayWidth = display.physicalDisplay.width();
+        mDisplayHeight = display.physicalDisplay.height();
+        mCompositionFbo.allocateBuffers(mDisplayWidth, mDisplayHeight);
+
+        // Let's use mimap filtering on the offscreen composition texture,
+        // this will drastically improve overall shader quality.
+        glBindTexture(GL_TEXTURE_2D, mCompositionFbo.getTextureName());
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 3);
+        glBindTexture(GL_TEXTURE_2D, 0);
+
+        const uint32_t fboWidth = floorf(mDisplayWidth * kFboScale);
+        const uint32_t fboHeight = floorf(mDisplayHeight * kFboScale);
         mBlurredFbo.allocateBuffers(fboWidth, fboHeight);
         allocateTextures();
         mTexturesAllocated = true;
diff --git a/libs/renderengine/gl/filters/BlurFilter.h b/libs/renderengine/gl/filters/BlurFilter.h
index 2b5ea58..e265b51 100644
--- a/libs/renderengine/gl/filters/BlurFilter.h
+++ b/libs/renderengine/gl/filters/BlurFilter.h
@@ -30,7 +30,7 @@
 class BlurFilter {
 public:
     // Downsample FBO to improve performance
-    static constexpr float kFboScale = 0.35f;
+    static constexpr float kFboScale = 0.25f;
 
     explicit BlurFilter(GLESRenderEngine& engine);
     virtual ~BlurFilter(){};
@@ -54,6 +54,8 @@
     GLFramebuffer mCompositionFbo;
     // Frame buffer holding the blur result.
     GLFramebuffer mBlurredFbo;
+    uint32_t mDisplayWidth;
+    uint32_t mDisplayHeight;
 
 private:
     bool mTexturesAllocated = false;
diff --git a/libs/renderengine/gl/filters/GaussianBlurFilter.cpp b/libs/renderengine/gl/filters/GaussianBlurFilter.cpp
index b1ad72c..f5ba02a 100644
--- a/libs/renderengine/gl/filters/GaussianBlurFilter.cpp
+++ b/libs/renderengine/gl/filters/GaussianBlurFilter.cpp
@@ -77,12 +77,14 @@
     // set uniforms
     auto width = mVerticalPassFbo.getBufferWidth();
     auto height = mVerticalPassFbo.getBufferHeight();
+    auto radiusF = fmax(1.0f, radius * kFboScale);
     glViewport(0, 0, width, height);
     glActiveTexture(GL_TEXTURE0);
     glBindTexture(GL_TEXTURE_2D, mCompositionFbo.getTextureName());
+    glGenerateMipmap(GL_TEXTURE_2D);
     glUniform1i(mVTextureLoc, 0);
     glUniform2f(mVSizeLoc, width, height);
-    glUniform1f(mVRadiusLoc, radius * kFboScale);
+    glUniform1f(mVRadiusLoc, radiusF);
     mEngine.checkErrors("Setting vertical-diagonal pass uniforms");
 
     drawMesh(mVUvLoc, mVPosLoc);
@@ -96,7 +98,7 @@
     glBindTexture(GL_TEXTURE_2D, mVerticalPassFbo.getTextureName());
     glUniform1i(mHTextureLoc, 0);
     glUniform2f(mHSizeLoc, width, height);
-    glUniform1f(mHRadiusLoc, radius * kFboScale);
+    glUniform1f(mHRadiusLoc, radiusF);
     mEngine.checkErrors("Setting vertical pass uniforms");
 
     drawMesh(mHUvLoc, mHPosLoc);
@@ -122,8 +124,7 @@
         uniform vec2 uSize;
         uniform float uRadius;
 
-        in mediump vec2 vUV;
-
+        mediump in vec2 vUV;
         out vec4 fragColor;
 
         #define PI 3.14159265359
@@ -131,7 +132,7 @@
         #define MU 0.0
         #define A 1.0 / (THETA * sqrt(2.0 * PI))
         #define K 1.0 / (2.0 * THETA * THETA)
-        #define MAX_SAMPLES 12
+        #define MAX_SAMPLES 10
 
         float gaussianBellCurve(float x) {
             float tmp = (x - MU);
@@ -139,14 +140,14 @@
         }
 
         vec3 gaussianBlur(sampler2D texture, mediump vec2 uv, float size,
-                          vec2 direction, float radius) {
+                          mediump vec2 direction, float radius) {
             float totalWeight = 0.0;
             vec3 blurred = vec3(0.);
-            int samples = min(int(floor(radius / 2.0)), MAX_SAMPLES);
+            int samples = min(int(ceil(radius / 2.0)), MAX_SAMPLES);
             float inc = radius / (size * 2.0);
 
             for (int i = -samples; i <= samples; i++) {
-                float normalized = (float(i) / float(samples));
+                float normalized = float(i) / float(samples);
                 float weight = gaussianBellCurve(normalized);
                 float radInc = inc * normalized;
                 blurred += weight * (texture(texture, uv + radInc * direction)).rgb;;
@@ -162,7 +163,7 @@
             #else
             vec3 color = gaussianBlur(uTexture, vUV, uSize.y, vec2(0.0, 1.0), uRadius);
             #endif
-            fragColor = vec4(color.r, color.g, color.b, texture(uTexture, vUV).a);
+            fragColor = vec4(color, 1.0);
         }
 
     )SHADER";
diff --git a/libs/renderengine/gl/filters/LensBlurFilter.cpp b/libs/renderengine/gl/filters/LensBlurFilter.cpp
index 386bd91..799deac 100644
--- a/libs/renderengine/gl/filters/LensBlurFilter.cpp
+++ b/libs/renderengine/gl/filters/LensBlurFilter.cpp
@@ -86,12 +86,14 @@
     // set uniforms
     auto width = mVerticalDiagonalPassFbo.getBufferWidth();
     auto height = mVerticalDiagonalPassFbo.getBufferHeight();
+    auto radiusF = fmax(1.0f, radius * kFboScale);
     glViewport(0, 0, width, height);
     glActiveTexture(GL_TEXTURE0);
     glBindTexture(GL_TEXTURE_2D, mCompositionFbo.getTextureName());
+    glGenerateMipmap(GL_TEXTURE_2D);
     glUniform1i(mVDTexture0Loc, 0);
-    glUniform2f(mVDSizeLoc, width, height);
-    glUniform1f(mVDRadiusLoc, radius * kFboScale);
+    glUniform2f(mVDSizeLoc, mDisplayWidth, mDisplayHeight);
+    glUniform1f(mVDRadiusLoc, radiusF);
     glUniform1i(mVDNumSamplesLoc, kNumSamples);
     mEngine.checkErrors("Setting vertical-diagonal pass uniforms");
 
@@ -108,8 +110,8 @@
     glActiveTexture(GL_TEXTURE1);
     glBindTexture(GL_TEXTURE_2D, mVerticalDiagonalPassFbo.getSecondaryTextureName());
     glUniform1i(mCTexture1Loc, 1);
-    glUniform2f(mCSizeLoc, width, height);
-    glUniform1f(mCRadiusLoc, radius * kFboScale);
+    glUniform2f(mCSizeLoc, mDisplayWidth, mDisplayHeight);
+    glUniform1f(mCRadiusLoc, radiusF);
     glUniform1i(mCNumSamplesLoc, kNumSamples);
     mEngine.checkErrors("Setting vertical pass uniforms");
 
@@ -134,7 +136,6 @@
     shader += R"SHADER(
         precision lowp float;
 
-        #define BOKEH_ANGLE 0.0
         #define PI 3.14159265359
 
         uniform sampler2D uTexture0;
@@ -142,7 +143,7 @@
         uniform float uRadius;
         uniform int uNumSamples;
 
-        in mediump vec2 vUV;
+        mediump in vec2 vUV;
 
         #if DIRECTION == 0
         layout(location = 0) out vec4 fragColor0;
@@ -152,61 +153,55 @@
         out vec4 fragColor;
         #endif
 
-        vec4 blur(const sampler2D tex, in vec2 uv, const vec2 direction, float radius,
-                  in int samples, float intensity) {
-            vec4 finalColor = vec4(vec3(0.0), 1.0);
-            float blurAmount = 0.0;
+        const vec2 verticalMult = vec2(cos(PI / 2.0), sin(PI / 2.0));
+        const vec2 diagonalMult = vec2(cos(-PI / 6.0), sin(-PI / 6.0));
+        const vec2 diagonal2Mult = vec2(cos(-5.0 * PI / 6.0), sin(-5.0 * PI / 6.0));
+
+        vec3 blur(const sampler2D tex, vec2 uv, const vec2 direction, float radius,
+                  int samples, float intensity) {
+            vec3 finalColor = vec3(0.0);
             uv += direction * 0.5;
 
             for (int i = 0; i < samples; i++){
                 float delta = radius * float(i) / float(samples);
-                vec4 color = texture(tex, uv + direction * delta);
+                vec3 color = texture(tex, uv + direction * delta).rgb;
                 color.rgb *= intensity;
-                color *= color.a;
-                blurAmount += color.a;
                 finalColor += color;
             }
 
-            return finalColor / blurAmount;
+            return finalColor / float(samples);
         }
 
-        vec4 blur(const sampler2D tex, in vec2 uv, const vec2 direction, float radius,
-                  in int samples) {
+        vec3 blur(const sampler2D tex, vec2 uv, const vec2 direction, float radius,
+                  int samples) {
             return blur(tex, uv, direction, radius, samples, 1.0);
         }
 
         vec4[2] verticalDiagonalLensBlur (vec2 uv, sampler2D texture, vec2 resolution,
                                           float radius, int samples) {
-            float coc = texture(texture, uv).a;
-
             // Vertical Blur
-            vec2 blurDirV = (coc / resolution.xy) * vec2(cos(BOKEH_ANGLE + PI / 2.0),
-                sin(BOKEH_ANGLE + PI / 2.0));
-            vec3 colorV = blur(texture, uv, blurDirV, radius, samples).rgb * coc;
+            vec2 blurDirV = 1.0 / resolution.xy * verticalMult;
+            vec3 colorV = blur(texture, uv, blurDirV, radius, samples);
 
             // Diagonal Blur
-            vec2 blurDirD = (coc / resolution.xy) * vec2(cos(BOKEH_ANGLE - PI / 6.0),
-                sin(BOKEH_ANGLE - PI / 6.0));
-            vec3 colorD = blur(texture, uv, blurDirD, radius, samples).rgb * coc;
+            vec2 blurDirD = 1.0 / resolution.xy * diagonalMult;
+            vec3 colorD = blur(texture, uv, blurDirD, radius, samples);
 
             vec4 composed[2];
-            composed[0] = vec4(colorV, coc);
+            composed[0] = vec4(colorV, 1.0);
             // added * 0.5, to remap
-            composed[1] = vec4((colorD + colorV) * 0.5, coc);
+            composed[1] = vec4((colorD + colorV) * 0.5, 1.0);
 
             return composed;
         }
 
         vec4 rhombiLensBlur (vec2 uv, sampler2D texture0, sampler2D texture1, vec2 resolution,
                              float radius, int samples) {
-            float coc1 = texture(texture0, uv).a;
-            float coc2 = texture(texture1, uv).a;
+            vec2 blurDirection1 = 1.0 / resolution.xy * diagonalMult;
+            vec3 color1 = blur(texture0, uv, blurDirection1, radius, samples);
 
-            vec2 blurDirection1 = coc1 / resolution.xy * vec2(cos(BOKEH_ANGLE - PI / 6.0), sin(BOKEH_ANGLE - PI / 6.0));
-            vec3 color1 = blur(texture0, uv, blurDirection1, radius, samples).rgb * coc1;
-
-            vec2 blurDirection2 = coc2 / resolution.xy * vec2(cos(BOKEH_ANGLE - 5.0 * PI / 6.0), sin(BOKEH_ANGLE - 5.0 * PI / 6.0));
-            vec3 color2 = blur(texture1, uv, blurDirection2, radius, samples, 2.0).rgb * coc2;
+            vec2 blurDirection2 = 1.0 / resolution.xy * diagonal2Mult;
+            vec3 color2 = blur(texture1, uv, blurDirection2, radius, samples, 2.0);
 
             return vec4((color1 + color2) * 0.33, 1.0);
         }
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index 3dc198f..95e9367 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -52,7 +52,7 @@
     // Transform matrix to apply to texture coordinates.
     mat4 textureTransform = mat4();
 
-    // Wheteher to use pre-multiplied alpha
+    // Whether to use pre-multiplied alpha.
     bool usePremultipliedAlpha = true;
 
     // Override flag that alpha for each pixel in the buffer *must* be 1.0.
@@ -153,6 +153,8 @@
     int backgroundBlurRadius = 0;
 };
 
+// Keep in sync with custom comparison function in
+// compositionengine/impl/ClientCompositionRequestCache.cpp
 static inline bool operator==(const Buffer& lhs, const Buffer& rhs) {
     return lhs.buffer == rhs.buffer && lhs.fence == rhs.fence &&
             lhs.textureName == rhs.textureName &&
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index e3c2d84..46f3fc6 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -154,7 +154,7 @@
     // @return An error code indicating whether drawing was successful. For
     // now, this always returns NO_ERROR.
     virtual status_t drawLayers(const DisplaySettings& display,
-                                const std::vector<LayerSettings>& layers,
+                                const std::vector<const LayerSettings*>& layers,
                                 ANativeWindowBuffer* buffer, const bool useFramebufferCache,
                                 base::unique_fd&& bufferFence, base::unique_fd* drawFence) = 0;
 
diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h
index 0750e86..3358c69 100644
--- a/libs/renderengine/include/renderengine/mock/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h
@@ -56,7 +56,7 @@
     MOCK_CONST_METHOD0(supportsProtectedContent, bool());
     MOCK_METHOD1(useProtectedContext, bool(bool));
     MOCK_METHOD6(drawLayers,
-                 status_t(const DisplaySettings&, const std::vector<LayerSettings>&,
+                 status_t(const DisplaySettings&, const std::vector<const LayerSettings*>&,
                           ANativeWindowBuffer*, const bool, base::unique_fd&&, base::unique_fd*));
 };
 
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index e676740..afcbc50 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -249,7 +249,8 @@
     }
 
     void invokeDraw(renderengine::DisplaySettings settings,
-                    std::vector<renderengine::LayerSettings> layers, sp<GraphicBuffer> buffer) {
+                    std::vector<const renderengine::LayerSettings*> layers,
+                    sp<GraphicBuffer> buffer) {
         base::unique_fd fence;
         status_t status = sRE->drawLayers(settings, layers, buffer->getNativeBuffer(), true,
                                           base::unique_fd(), &fence);
@@ -269,7 +270,7 @@
 
     void drawEmptyLayers() {
         renderengine::DisplaySettings settings;
-        std::vector<renderengine::LayerSettings> layers;
+        std::vector<const renderengine::LayerSettings*> layers;
         // Meaningless buffer since we don't do any drawing
         sp<GraphicBuffer> buffer = new GraphicBuffer();
         invokeDraw(settings, layers, buffer);
@@ -440,14 +441,14 @@
     settings.physicalDisplay = fullscreenRect();
     settings.clip = fullscreenRect();
 
-    std::vector<renderengine::LayerSettings> layers;
+    std::vector<const renderengine::LayerSettings*> layers;
 
     renderengine::LayerSettings layer;
     layer.geometry.boundaries = fullscreenRect().toFloatRect();
     SourceVariant::fillColor(layer, r, g, b, this);
     layer.alpha = a;
 
-    layers.push_back(layer);
+    layers.push_back(&layer);
 
     invokeDraw(settings, layers, mBuffer);
 }
@@ -482,14 +483,14 @@
     settings.physicalDisplay = offsetRect();
     settings.clip = offsetRectAtZero();
 
-    std::vector<renderengine::LayerSettings> layers;
+    std::vector<const renderengine::LayerSettings*> layers;
 
     renderengine::LayerSettings layer;
     layer.geometry.boundaries = offsetRectAtZero().toFloatRect();
     SourceVariant::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
     layer.alpha = 1.0f;
 
-    layers.push_back(layer);
+    layers.push_back(&layer);
     invokeDraw(settings, layers, mBuffer);
 }
 
@@ -515,7 +516,7 @@
     settings.clip = Rect(2, 2);
     settings.globalTransform = transform;
 
-    std::vector<renderengine::LayerSettings> layers;
+    std::vector<const renderengine::LayerSettings*> layers;
 
     renderengine::LayerSettings layerOne;
     Rect rectOne(0, 0, 1, 1);
@@ -535,9 +536,9 @@
     SourceVariant::fillColor(layerThree, 0.0f, 0.0f, 1.0f, this);
     layerThree.alpha = 1.0f;
 
-    layers.push_back(layerOne);
-    layers.push_back(layerTwo);
-    layers.push_back(layerThree);
+    layers.push_back(&layerOne);
+    layers.push_back(&layerTwo);
+    layers.push_back(&layerThree);
 
     invokeDraw(settings, layers, mBuffer);
 }
@@ -616,7 +617,7 @@
     // Here logical space is 2x2
     settings.clip = Rect(2, 2);
 
-    std::vector<renderengine::LayerSettings> layers;
+    std::vector<const renderengine::LayerSettings*> layers;
 
     renderengine::LayerSettings layer;
     layer.geometry.boundaries = Rect(1, 1).toFloatRect();
@@ -626,7 +627,7 @@
     layer.source.solidColor = half3(1.0f, 0.0f, 0.0f);
     layer.alpha = 1.0f;
 
-    layers.push_back(layer);
+    layers.push_back(&layer);
 
     invokeDraw(settings, layers, mBuffer);
 }
@@ -647,7 +648,7 @@
     settings.physicalDisplay = fullscreenRect();
     settings.clip = Rect(1, 1);
 
-    std::vector<renderengine::LayerSettings> layers;
+    std::vector<const renderengine::LayerSettings*> layers;
 
     renderengine::LayerSettings layer;
     layer.geometry.boundaries = Rect(1, 1).toFloatRect();
@@ -663,7 +664,7 @@
     layer.alpha = 1.0f;
     layer.geometry.boundaries = Rect(1, 1).toFloatRect();
 
-    layers.push_back(layer);
+    layers.push_back(&layer);
 
     invokeDraw(settings, layers, mBuffer);
 }
@@ -680,7 +681,7 @@
     settings.physicalDisplay = fullscreenRect();
     settings.clip = fullscreenRect();
 
-    std::vector<renderengine::LayerSettings> layers;
+    std::vector<const renderengine::LayerSettings*> layers;
 
     renderengine::LayerSettings layer;
     layer.geometry.boundaries = fullscreenRect().toFloatRect();
@@ -689,7 +690,7 @@
     SourceVariant::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
     layer.alpha = 1.0f;
 
-    layers.push_back(layer);
+    layers.push_back(&layer);
 
     invokeDraw(settings, layers, mBuffer);
 }
@@ -726,26 +727,26 @@
     settings.physicalDisplay = fullscreenRect();
     settings.clip = fullscreenRect();
 
-    std::vector<renderengine::LayerSettings> layers;
+    std::vector<const renderengine::LayerSettings*> layers;
 
     renderengine::LayerSettings backgroundLayer;
     backgroundLayer.geometry.boundaries = fullscreenRect().toFloatRect();
     SourceVariant::fillColor(backgroundLayer, 0.0f, 1.0f, 0.0f, this);
     backgroundLayer.alpha = 1.0f;
-    layers.push_back(backgroundLayer);
+    layers.push_back(&backgroundLayer);
 
     renderengine::LayerSettings leftLayer;
     leftLayer.geometry.boundaries =
             Rect(DEFAULT_DISPLAY_WIDTH / 2, DEFAULT_DISPLAY_HEIGHT).toFloatRect();
     SourceVariant::fillColor(leftLayer, 1.0f, 0.0f, 0.0f, this);
     leftLayer.alpha = 1.0f;
-    layers.push_back(leftLayer);
+    layers.push_back(&leftLayer);
 
     renderengine::LayerSettings blurLayer;
     blurLayer.geometry.boundaries = fullscreenRect().toFloatRect();
     blurLayer.backgroundBlurRadius = blurRadius;
     blurLayer.alpha = 0;
-    layers.push_back(blurLayer);
+    layers.push_back(&blurLayer);
 
     invokeDraw(settings, layers, mBuffer);
 
@@ -761,7 +762,7 @@
     settings.physicalDisplay = fullscreenRect();
     settings.clip = fullscreenRect();
 
-    std::vector<renderengine::LayerSettings> layersFirst;
+    std::vector<const renderengine::LayerSettings*> layersFirst;
 
     renderengine::LayerSettings layerOne;
     layerOne.geometry.boundaries =
@@ -769,14 +770,14 @@
     SourceVariant::fillColor(layerOne, 1.0f, 0.0f, 0.0f, this);
     layerOne.alpha = 0.2;
 
-    layersFirst.push_back(layerOne);
+    layersFirst.push_back(&layerOne);
     invokeDraw(settings, layersFirst, mBuffer);
     expectBufferColor(Rect(DEFAULT_DISPLAY_WIDTH / 3, DEFAULT_DISPLAY_HEIGHT / 3), 51, 0, 0, 51);
     expectBufferColor(Rect(DEFAULT_DISPLAY_WIDTH / 3 + 1, DEFAULT_DISPLAY_HEIGHT / 3 + 1,
                            DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                       0, 0, 0, 0);
 
-    std::vector<renderengine::LayerSettings> layersSecond;
+    std::vector<const renderengine::LayerSettings*> layersSecond;
     renderengine::LayerSettings layerTwo;
     layerTwo.geometry.boundaries =
             FloatRect(DEFAULT_DISPLAY_WIDTH / 3.0, DEFAULT_DISPLAY_HEIGHT / 3.0,
@@ -784,7 +785,7 @@
     SourceVariant::fillColor(layerTwo, 0.0f, 1.0f, 0.0f, this);
     layerTwo.alpha = 1.0f;
 
-    layersSecond.push_back(layerTwo);
+    layersSecond.push_back(&layerTwo);
     invokeDraw(settings, layersSecond, mBuffer);
 
     expectBufferColor(Rect(DEFAULT_DISPLAY_WIDTH / 3, DEFAULT_DISPLAY_HEIGHT / 3), 0, 0, 0, 0);
@@ -798,7 +799,7 @@
     settings.physicalDisplay = fullscreenRect();
     settings.clip = Rect(1, 1);
 
-    std::vector<renderengine::LayerSettings> layers;
+    std::vector<const renderengine::LayerSettings*> layers;
 
     renderengine::LayerSettings layer;
     // Here will allocate a checker board texture, but transform texture
@@ -833,7 +834,7 @@
     layer.alpha = 1.0f;
     layer.geometry.boundaries = Rect(1, 1).toFloatRect();
 
-    layers.push_back(layer);
+    layers.push_back(&layer);
 
     invokeDraw(settings, layers, mBuffer);
 }
@@ -849,7 +850,7 @@
     // Here logical space is 1x1
     settings.clip = Rect(1, 1);
 
-    std::vector<renderengine::LayerSettings> layers;
+    std::vector<const renderengine::LayerSettings*> layers;
 
     renderengine::LayerSettings layer;
     sp<GraphicBuffer> buf = allocateSourceBuffer(1, 1);
@@ -872,7 +873,7 @@
     layer.alpha = 0.5f;
     layer.geometry.boundaries = Rect(1, 1).toFloatRect();
 
-    layers.push_back(layer);
+    layers.push_back(&layer);
 
     invokeDraw(settings, layers, mBuffer);
 }
@@ -888,7 +889,7 @@
     // Here logical space is 1x1
     settings.clip = Rect(1, 1);
 
-    std::vector<renderengine::LayerSettings> layers;
+    std::vector<const renderengine::LayerSettings*> layers;
 
     renderengine::LayerSettings layer;
     sp<GraphicBuffer> buf = allocateSourceBuffer(1, 1);
@@ -911,7 +912,7 @@
     layer.alpha = 0.5f;
     layer.geometry.boundaries = Rect(1, 1).toFloatRect();
 
-    layers.push_back(layer);
+    layers.push_back(&layer);
 
     invokeDraw(settings, layers, mBuffer);
 }
@@ -928,10 +929,10 @@
     settings.clip = Rect(4, 4);
     settings.globalTransform = mat4::scale(vec4(2, 4, 0, 1));
     settings.clearRegion = Region(Rect(1, 1));
-    std::vector<renderengine::LayerSettings> layers;
+    std::vector<const renderengine::LayerSettings*> layers;
     // dummy layer, without bounds should not render anything
     renderengine::LayerSettings layer;
-    layers.push_back(layer);
+    layers.push_back(&layer);
     invokeDraw(settings, layers, mBuffer);
 }
 
@@ -952,7 +953,7 @@
     settings.physicalDisplay = fullscreenRect();
     settings.clip = fullscreenRect();
 
-    std::vector<renderengine::LayerSettings> layers;
+    std::vector<const renderengine::LayerSettings*> layers;
 
     // add background layer
     renderengine::LayerSettings bgLayer;
@@ -960,20 +961,20 @@
     ColorSourceVariant::fillColor(bgLayer, backgroundColor.r / 255.0f, backgroundColor.g / 255.0f,
                                   backgroundColor.b / 255.0f, this);
     bgLayer.alpha = backgroundColor.a / 255.0f;
-    layers.push_back(bgLayer);
+    layers.push_back(&bgLayer);
 
     // add shadow layer
     renderengine::LayerSettings shadowLayer;
     shadowLayer.geometry.boundaries = castingLayer.geometry.boundaries;
     shadowLayer.alpha = castingLayer.alpha;
     shadowLayer.shadow = shadow;
-    layers.push_back(shadowLayer);
+    layers.push_back(&shadowLayer);
 
     // add layer casting the shadow
     renderengine::LayerSettings layer = castingLayer;
     SourceVariant::fillColor(layer, casterColor.r / 255.0f, casterColor.g / 255.0f,
                              casterColor.b / 255.0f, this);
-    layers.push_back(layer);
+    layers.push_back(&layer);
 
     invokeDraw(settings, layers, mBuffer);
 }
@@ -984,11 +985,11 @@
 
 TEST_F(RenderEngineTest, drawLayers_nullOutputBuffer) {
     renderengine::DisplaySettings settings;
-    std::vector<renderengine::LayerSettings> layers;
+    std::vector<const renderengine::LayerSettings*> layers;
     renderengine::LayerSettings layer;
     layer.geometry.boundaries = fullscreenRect().toFloatRect();
     BufferSourceVariant<ForceOpaqueBufferVariant>::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
-    layers.push_back(layer);
+    layers.push_back(&layer);
     base::unique_fd fence;
     status_t status = sRE->drawLayers(settings, layers, nullptr, true, base::unique_fd(), &fence);
 
@@ -1000,12 +1001,12 @@
     settings.physicalDisplay = fullscreenRect();
     settings.clip = fullscreenRect();
 
-    std::vector<renderengine::LayerSettings> layers;
+    std::vector<const renderengine::LayerSettings*> layers;
     renderengine::LayerSettings layer;
     layer.geometry.boundaries = fullscreenRect().toFloatRect();
     BufferSourceVariant<ForceOpaqueBufferVariant>::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
     layer.alpha = 1.0;
-    layers.push_back(layer);
+    layers.push_back(&layer);
 
     status_t status = sRE->drawLayers(settings, layers, mBuffer->getNativeBuffer(), true,
                                       base::unique_fd(), nullptr);
@@ -1019,12 +1020,12 @@
     settings.physicalDisplay = fullscreenRect();
     settings.clip = fullscreenRect();
 
-    std::vector<renderengine::LayerSettings> layers;
+    std::vector<const renderengine::LayerSettings*> layers;
     renderengine::LayerSettings layer;
     layer.geometry.boundaries = fullscreenRect().toFloatRect();
     BufferSourceVariant<ForceOpaqueBufferVariant>::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
     layer.alpha = 1.0;
-    layers.push_back(layer);
+    layers.push_back(&layer);
 
     status_t status = sRE->drawLayers(settings, layers, mBuffer->getNativeBuffer(), false,
                                       base::unique_fd(), nullptr);
@@ -1223,13 +1224,13 @@
     settings.physicalDisplay = fullscreenRect();
     settings.clip = fullscreenRect();
 
-    std::vector<renderengine::LayerSettings> layers;
+    std::vector<const renderengine::LayerSettings*> layers;
 
     renderengine::LayerSettings layer;
     layer.geometry.boundaries = fullscreenRect().toFloatRect();
     BufferSourceVariant<ForceOpaqueBufferVariant>::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
 
-    layers.push_back(layer);
+    layers.push_back(&layer);
     invokeDraw(settings, layers, mBuffer);
     uint64_t bufferId = layer.source.buffer.buffer->getId();
     EXPECT_TRUE(sRE->isImageCachedForTesting(bufferId));
diff --git a/libs/sensor/Sensor.cpp b/libs/sensor/Sensor.cpp
index abc9103..9d817ae 100644
--- a/libs/sensor/Sensor.cpp
+++ b/libs/sensor/Sensor.cpp
@@ -268,6 +268,10 @@
         mStringType = SENSOR_STRING_TYPE_ACCELEROMETER_UNCALIBRATED;
         mFlags |= SENSOR_FLAG_CONTINUOUS_MODE;
         break;
+    case SENSOR_TYPE_HINGE_ANGLE:
+        mStringType = SENSOR_STRING_TYPE_HINGE_ANGLE;
+        mFlags |= SENSOR_FLAG_ON_CHANGE_MODE;
+        break;
     default:
         // Only pipe the stringType, requiredPermission and flags for custom sensors.
         if (halVersion > SENSORS_DEVICE_API_VERSION_1_0 && hwSensor.stringType) {
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index bf8b9f7..a4a5d13 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -209,7 +209,7 @@
             type == SENSOR_TYPE_TILT_DETECTOR || type == SENSOR_TYPE_WAKE_GESTURE ||
             type == SENSOR_TYPE_GLANCE_GESTURE || type == SENSOR_TYPE_PICK_UP_GESTURE ||
             type == SENSOR_TYPE_WRIST_TILT_GESTURE ||
-            type == SENSOR_TYPE_LOW_LATENCY_OFFBODY_DETECT) {
+            type == SENSOR_TYPE_LOW_LATENCY_OFFBODY_DETECT || type == SENSOR_TYPE_HINGE_ANGLE) {
             wakeUpSensor = true;
         }
         // For now we just return the first sensor of that type we find.
diff --git a/services/gpuservice/GpuService.cpp b/services/gpuservice/GpuService.cpp
index 55a0c0a..be4a462 100644
--- a/services/gpuservice/GpuService.cpp
+++ b/services/gpuservice/GpuService.cpp
@@ -28,12 +28,7 @@
 #include <utils/String8.h>
 #include <utils/Trace.h>
 
-#include <array>
-#include <fstream>
-#include <sstream>
-#include <sys/types.h>
 #include <vkjson.h>
-#include <unistd.h>
 
 #include "gpustats/GpuStats.h"
 
@@ -45,7 +40,6 @@
 status_t cmdHelp(int out);
 status_t cmdVkjson(int out, int err);
 void dumpGameDriverInfo(std::string* result);
-void dumpMemoryInfo(std::string* result, const GpuMemoryMap& memories, uint32_t pid);
 } // namespace
 
 const String16 sDump("android.permission.DUMP");
@@ -78,147 +72,6 @@
     mGpuStats->insertTargetStats(appPackageName, driverVersionCode, stats, value);
 }
 
-bool isExpectedFormat(const char* str) {
-    // Should match in order:
-    // gpuaddr useraddr size id flags type usage sglen mapsize eglsrf eglimg
-    std::istringstream iss;
-    iss.str(str);
-
-    std::string word;
-    iss >> word;
-    if (word != "gpuaddr") { return false; }
-    iss >> word;
-    if (word != "useraddr") { return false; }
-    iss >> word;
-    if (word != "size") { return false; }
-    iss >> word;
-    if (word != "id") { return false; }
-    iss >> word;
-    if (word != "flags") { return false; }
-    iss >> word;
-    if (word != "type") { return false; }
-    iss >> word;
-    if (word != "usage") { return false; }
-    iss >> word;
-    if (word != "sglen") { return false; }
-    iss >> word;
-    if (word != "mapsize") { return false; }
-    iss >> word;
-    if (word != "eglsrf") { return false; }
-    iss >> word;
-    if (word != "eglimg") { return false; }
-    return true;
-}
-
-
-// Queries gpu memory via Qualcomm's /d/kgsl/proc/*/mem interface.
-status_t GpuService::getQCommGpuMemoryInfo(GpuMemoryMap* memories, std::string* result, int32_t dumpPid) const {
-    const std::string kDirectoryPath = "/d/kgsl/proc";
-    DIR* directory = opendir(kDirectoryPath.c_str());
-    if (!directory) { return PERMISSION_DENIED; }
-
-    // File Format:
-    //          gpuaddr         useraddr     size id     flags   type          usage sglen mapsize eglsrf eglimg
-    // 0000000000000000 0000000000000000  8359936 23 --w--pY-- gpumem VK/others( 38)     0       0      0      0
-    // 0000000000000000 0000000000000000 16293888 24 --wL--N--    ion        surface    41       0      0      1
-
-    const bool dumpAll = dumpPid == 0;
-    static constexpr size_t kMaxLineLength = 1024;
-    static char line[kMaxLineLength];
-    while(dirent* subdir = readdir(directory)) {
-        // Skip "." and ".." in directory.
-        if (strcmp(subdir->d_name, ".") == 0 || strcmp(subdir->d_name, "..") == 0 ) { continue; }
-
-        std::string pid_str(subdir->d_name);
-        const uint32_t pid(stoi(pid_str));
-
-        if (!dumpAll && dumpPid != pid) {
-            continue;
-        }
-
-        std::string filepath(kDirectoryPath + "/" + pid_str + "/mem");
-        std::ifstream file(filepath);
-
-        // Check first line
-        file.getline(line, kMaxLineLength);
-        if (!isExpectedFormat(line)) {
-            continue;
-        }
-
-        if (result) {
-            StringAppendF(result, "%d:\n%s\n", pid, line);
-        }
-
-        while( file.getline(line, kMaxLineLength) ) {
-            if (result) {
-                StringAppendF(result, "%s\n", line);
-            }
-
-            std::istringstream iss;
-            iss.str(line);
-
-            // Skip gpuaddr, useraddr.
-            const char delimiter = ' ';
-            iss >> std::ws;
-            iss.ignore(kMaxLineLength, delimiter);
-            iss >> std::ws;
-            iss.ignore(kMaxLineLength, delimiter);
-
-            // Get size.
-            int64_t memsize;
-            iss >> memsize;
-
-            // Skip id, flags.
-            iss >> std::ws;
-            iss.ignore(kMaxLineLength, delimiter);
-            iss >> std::ws;
-            iss.ignore(kMaxLineLength, delimiter);
-
-            // Get type, usage.
-            std::string memtype;
-            std::string usage;
-            iss >> memtype >> usage;
-
-            // Adjust for the space in VK/others( #)
-            if (usage == "VK/others(") {
-              std::string vkTypeEnd;
-              iss >> vkTypeEnd;
-              usage.append(vkTypeEnd);
-            }
-
-            // Skip sglen.
-            iss >> std::ws;
-            iss.ignore(kMaxLineLength, delimiter);
-
-            // Get mapsize.
-            int64_t mapsize;
-            iss >> mapsize;
-
-            if (memsize == 0 && mapsize == 0) {
-                continue;
-            }
-
-            if (memtype == "gpumem") {
-                (*memories)[pid][usage].gpuMemory += memsize;
-            } else {
-                (*memories)[pid][usage].ionMemory += memsize;
-            }
-
-            if (mapsize > 0) {
-                (*memories)[pid][usage].mappedMemory += mapsize;
-            }
-        }
-
-        if (result) {
-            StringAppendF(result, "\n");
-        }
-    }
-
-    closedir(directory);
-
-    return OK;
-}
-
 status_t GpuService::shellCommand(int /*in*/, int out, int err, std::vector<String16>& args) {
     ATRACE_CALL();
 
@@ -248,9 +101,7 @@
         bool dumpAll = true;
         bool dumpDriverInfo = false;
         bool dumpStats = false;
-        bool dumpMemory = false;
         size_t numArgs = args.size();
-        int32_t pid = 0;
 
         if (numArgs) {
             dumpAll = false;
@@ -259,11 +110,6 @@
                     dumpStats = true;
                 } else if (args[index] == String16("--gpudriverinfo")) {
                     dumpDriverInfo = true;
-                } else if (args[index] == String16("--gpumem")) {
-                    dumpMemory = true;
-                } else if (args[index].startsWith(String16("--gpumem="))) {
-                    dumpMemory = true;
-                    pid = atoi(String8(&args[index][9]));
                 }
             }
         }
@@ -276,14 +122,6 @@
             mGpuStats->dump(args, &result);
             result.append("\n");
         }
-        if (dumpAll || dumpMemory) {
-            GpuMemoryMap memories;
-            // Currently only queries Qualcomm gpu memory. More will be added later.
-            if (getQCommGpuMemoryInfo(&memories, &result, pid) == OK) {
-                dumpMemoryInfo(&result, memories, pid);
-                result.append("\n");
-            }
-        }
     }
 
     write(fd, result.c_str(), result.size());
@@ -335,34 +173,6 @@
     StringAppendF(result, "Pre-release Game Driver: %s\n", preReleaseGameDriver);
 }
 
-// Read and print all memory info for each process from /d/kgsl/proc/<pid>/mem.
-void dumpMemoryInfo(std::string* result, const GpuMemoryMap& memories, uint32_t pid) {
-    if (!result) return;
-
-    // Write results.
-    StringAppendF(result, "GPU Memory Summary:\n");
-    for(auto& mem : memories) {
-        uint32_t process = mem.first;
-        if (pid != 0 && pid != process) {
-            continue;
-        }
-
-        StringAppendF(result, "%d:\n", process);
-        for(auto& memStruct : mem.second) {
-            StringAppendF(result, "  %s", memStruct.first.c_str());
-
-            if(memStruct.second.gpuMemory > 0)
-                StringAppendF(result, ", GPU memory = %" PRId64, memStruct.second.gpuMemory);
-            if(memStruct.second.mappedMemory > 0)
-                StringAppendF(result, ", Mapped memory = %" PRId64, memStruct.second.mappedMemory);
-            if(memStruct.second.ionMemory > 0)
-                StringAppendF(result, ", Ion memory = %" PRId64, memStruct.second.ionMemory);
-
-            StringAppendF(result, "\n");
-        }
-    }
-}
-
 } // anonymous namespace
 
 } // namespace android
diff --git a/services/inputflinger/InputReaderBase.cpp b/services/inputflinger/InputReaderBase.cpp
index 2d6f2c1..b2dadf8 100644
--- a/services/inputflinger/InputReaderBase.cpp
+++ b/services/inputflinger/InputReaderBase.cpp
@@ -125,6 +125,16 @@
     return std::nullopt;
 }
 
+std::optional<DisplayViewport> InputReaderConfiguration::getDisplayViewportById(
+        int32_t displayId) const {
+    for (const DisplayViewport& currentViewport : mDisplays) {
+        if (currentViewport.displayId == displayId) {
+            return std::make_optional(currentViewport);
+        }
+    }
+    return std::nullopt;
+}
+
 void InputReaderConfiguration::setDisplayViewports(const std::vector<DisplayViewport>& viewports) {
     mDisplays = viewports;
 }
@@ -151,4 +161,4 @@
     y = newY;
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 56c0a73..f8d5351 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -169,6 +169,9 @@
     // Used to determine which DisplayViewport should be tied to which InputDevice.
     std::unordered_map<std::string, uint8_t> portAssociations;
 
+    // The suggested display ID to show the cursor.
+    int32_t defaultPointerDisplayId;
+
     // Velocity control parameters for mouse pointer movements.
     VelocityControlParameters pointerVelocityControlParameters;
 
@@ -273,6 +276,7 @@
     std::optional<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueDisplayId)
             const;
     std::optional<DisplayViewport> getDisplayViewportByPort(uint8_t physicalPort) const;
+    std::optional<DisplayViewport> getDisplayViewportById(int32_t displayId) const;
     void setDisplayViewports(const std::vector<DisplayViewport>& viewports);
 
 
@@ -352,4 +356,4 @@
 
 } // namespace android
 
-#endif // _UI_INPUT_READER_COMMON_H
\ No newline at end of file
+#endif // _UI_INPUT_READER_COMMON_H
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index 0ff28e4..194c665 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -17,6 +17,7 @@
 #ifndef _INPUTFLINGER_POINTER_CONTROLLER_INTERFACE_H
 #define _INPUTFLINGER_POINTER_CONTROLLER_INTERFACE_H
 
+#include <input/DisplayViewport.h>
 #include <input/Input.h>
 #include <utils/BitSet.h>
 #include <utils/RefBase.h>
@@ -101,6 +102,9 @@
 
     /* Gets the id of the display where the pointer should be shown. */
     virtual int32_t getDisplayId() const = 0;
+
+    /* Sets the associated display of this pointer. Pointer should show on that display. */
+    virtual void setDisplayViewport(const DisplayViewport& displayViewport) = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index f69138e..69a75ba 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -189,12 +189,32 @@
 
         // Update the PointerController if viewports changed.
         if (mParameters.mode == Parameters::MODE_POINTER) {
-            getPolicy()->obtainPointerController(getDeviceId());
+            updatePointerControllerDisplayViewport(*config);
         }
         bumpGeneration();
     }
 }
 
+void CursorInputMapper::updatePointerControllerDisplayViewport(
+        const InputReaderConfiguration& config) {
+    std::optional<DisplayViewport> viewport =
+            config.getDisplayViewportById(config.defaultPointerDisplayId);
+    if (!viewport) {
+        ALOGW("Can't find the designated viewport with ID %" PRId32 " to update cursor input "
+              "mapper. Fall back to default display",
+              config.defaultPointerDisplayId);
+        viewport = config.getDisplayViewportById(ADISPLAY_ID_DEFAULT);
+    }
+
+    if (!viewport) {
+        ALOGE("Still can't find a viable viewport to update cursor input mapper. Skip setting it to"
+              " PointerController.");
+        return;
+    }
+
+    mPointerController->setDisplayViewport(*viewport);
+}
+
 void CursorInputMapper::configureParameters() {
     mParameters.mode = Parameters::MODE_POINTER;
     String8 cursorModeString;
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index 77d122a..d56f9be 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -117,8 +117,9 @@
     void dumpParameters(std::string& dump);
 
     void sync(nsecs_t when);
+    void updatePointerControllerDisplayViewport(const InputReaderConfiguration& config);
 };
 
 } // namespace android
 
-#endif // _UI_INPUTREADER_CURSOR_INPUT_MAPPER_H
\ No newline at end of file
+#endif // _UI_INPUTREADER_CURSOR_INPUT_MAPPER_H
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index c80a2dc..b66caca 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -552,9 +552,10 @@
  * Determine which DisplayViewport to use.
  * 1. If display port is specified, return the matching viewport. If matching viewport not
  * found, then return.
- * 2. If a device has associated display, get the matching viewport by either unique id or by
+ * 2. Always use the suggested viewport from WindowManagerService for pointers.
+ * 3. If a device has associated display, get the matching viewport by either unique id or by
  * the display type (internal or external).
- * 3. Otherwise, use a non-display viewport.
+ * 4. Otherwise, use a non-display viewport.
  */
 std::optional<DisplayViewport> TouchInputMapper::findViewport() {
     if (mParameters.hasAssociatedDisplay) {
@@ -564,6 +565,17 @@
             return mDevice->getAssociatedViewport();
         }
 
+        if (mDeviceMode == DEVICE_MODE_POINTER) {
+            std::optional<DisplayViewport> viewport =
+                    mConfig.getDisplayViewportById(mConfig.defaultPointerDisplayId);
+            if (viewport) {
+                return viewport;
+            } else {
+                ALOGW("Can't find designated display viewport with ID %" PRId32 " for pointers.",
+                      mConfig.defaultPointerDisplayId);
+            }
+        }
+
         // Check if uniqueDisplayId is specified in idc file.
         if (!mParameters.uniqueDisplayId.empty()) {
             return mConfig.getDisplayViewportByUniqueId(mParameters.uniqueDisplayId);
@@ -749,6 +761,7 @@
         (mDeviceMode == DEVICE_MODE_DIRECT && mConfig.showTouches)) {
         if (mPointerController == nullptr || viewportChanged) {
             mPointerController = getPolicy()->obtainPointerController(getDeviceId());
+            mPointerController->setDisplayViewport(mViewport);
         }
     } else {
         mPointerController.clear();
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index a9d7793..8ca7e4a 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -90,10 +90,6 @@
         mMaxY = maxY;
     }
 
-    void setDisplayId(int32_t displayId) {
-        mDisplayId = displayId;
-    }
-
     virtual void setPosition(float x, float y) {
         mX = x;
         mY = y;
@@ -116,6 +112,10 @@
         return mDisplayId;
     }
 
+    virtual void setDisplayViewport(const DisplayViewport& viewport) {
+        mDisplayId = viewport.displayId;
+    }
+
     const std::map<int32_t, std::vector<int32_t>>& getSpots() {
         return mSpotsByDisplay;
     }
@@ -280,6 +280,10 @@
         mConfig.showTouches = enabled;
     }
 
+    void setDefaultPointerDisplayId(int32_t pointerDisplayId) {
+        mConfig.defaultPointerDisplayId = pointerDisplayId;
+    }
+
 private:
     DisplayViewport createDisplayViewport(int32_t displayId, int32_t width, int32_t height,
             int32_t orientation, const std::string& uniqueId, std::optional<uint8_t> physicalPort,
@@ -3432,12 +3436,18 @@
     CursorInputMapper* mapper = new CursorInputMapper(mDevice);
     addMapperAndConfigure(mapper);
 
-    // Setup PointerController for second display.
+    // Setup for second display.
     constexpr int32_t SECOND_DISPLAY_ID = 1;
+    const std::string SECOND_DISPLAY_UNIQUE_ID = "local:1";
+    mFakePolicy->addDisplayViewport(SECOND_DISPLAY_ID, 800, 480, DISPLAY_ORIENTATION_0,
+                                    SECOND_DISPLAY_UNIQUE_ID, NO_PORT,
+                                    ViewportType::VIEWPORT_EXTERNAL);
+    mFakePolicy->setDefaultPointerDisplayId(SECOND_DISPLAY_ID);
+    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+
     mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
     mFakePointerController->setPosition(100, 200);
     mFakePointerController->setButtonState(0);
-    mFakePointerController->setDisplayId(SECOND_DISPLAY_ID);
 
     NotifyMotionArgs args;
     process(mapper, ARBITRARY_TIME, EV_REL, REL_X, 10);
@@ -6539,14 +6549,16 @@
 }
 
 TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) {
-    // Setup PointerController for second display.
+    // Setup for second display.
     sp<FakePointerController> fakePointerController = new FakePointerController();
-    fakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+    fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
     fakePointerController->setPosition(100, 200);
     fakePointerController->setButtonState(0);
-    fakePointerController->setDisplayId(SECONDARY_DISPLAY_ID);
     mFakePolicy->setPointerController(mDevice->getId(), fakePointerController);
 
+    mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
+    prepareSecondaryDisplay(ViewportType::VIEWPORT_EXTERNAL);
+
     MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
     prepareDisplay(DISPLAY_ORIENTATION_0);
     prepareAxes(POSITION);
diff --git a/services/sensorservice/SensorServiceUtils.cpp b/services/sensorservice/SensorServiceUtils.cpp
index 34cd8dd..fdd56b3 100644
--- a/services/sensorservice/SensorServiceUtils.cpp
+++ b/services/sensorservice/SensorServiceUtils.cpp
@@ -55,6 +55,7 @@
         case SENSOR_TYPE_MOTION_DETECT:
         case SENSOR_TYPE_HEART_BEAT:
         case SENSOR_TYPE_LOW_LATENCY_OFFBODY_DETECT:
+        case SENSOR_TYPE_HINGE_ANGLE:
             return 1;
 
         default:
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 9a7eabd..1b1e889 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -165,7 +165,9 @@
         "Scheduler/EventThread.cpp",
         "Scheduler/OneShotTimer.cpp",
         "Scheduler/LayerHistory.cpp",
+        "Scheduler/LayerHistoryV2.cpp",
         "Scheduler/LayerInfo.cpp",
+        "Scheduler/LayerInfoV2.cpp",
         "Scheduler/MessageQueue.cpp",
         "Scheduler/PhaseOffsets.cpp",
         "Scheduler/RefreshRateConfigs.cpp",
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index 7845cab..35d0215 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -144,11 +144,12 @@
     return inverse(tr);
 }
 
-std::optional<renderengine::LayerSettings> BufferLayer::prepareClientComposition(
+std::optional<compositionengine::LayerFE::LayerSettings> BufferLayer::prepareClientComposition(
         compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) {
     ATRACE_CALL();
 
-    auto result = Layer::prepareClientComposition(targetSettings);
+    std::optional<compositionengine::LayerFE::LayerSettings> result =
+            Layer::prepareClientComposition(targetSettings);
     if (!result) {
         return result;
     }
@@ -183,7 +184,7 @@
     bool blackOutLayer = (isProtected() && !targetSettings.supportsProtectedContent) ||
             (isSecure() && !targetSettings.isSecure);
     const State& s(getDrawingState());
-    auto& layer = *result;
+    LayerFE::LayerSettings& layer = *result;
     if (!blackOutLayer) {
         layer.source.buffer.buffer = mBufferInfo.mBuffer;
         layer.source.buffer.isOpaque = isOpaque(s);
@@ -199,6 +200,9 @@
         layer.source.buffer.maxContentLuminance = hasCta861_3
                 ? mBufferInfo.mHdrMetadata.cta8613.maxContentLightLevel
                 : defaultMaxContentLuminance;
+        layer.frameNumber = mCurrentFrameNumber;
+        layer.bufferId = mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getId() : 0;
+
         // TODO: we could be more subtle with isFixedSize()
         const bool useFiltering = targetSettings.needsFiltering || mNeedsFiltering || isFixedSize();
 
@@ -264,9 +268,11 @@
         // layer.
         layer.source.buffer.buffer = nullptr;
         layer.alpha = 1.0;
+        layer.frameNumber = 0;
+        layer.bufferId = 0;
     }
 
-    return result;
+    return layer;
 }
 
 bool BufferLayer::isHdrY410() const {
diff --git a/services/surfaceflinger/BufferLayer.h b/services/surfaceflinger/BufferLayer.h
index 5f5532e..b2398a8 100644
--- a/services/surfaceflinger/BufferLayer.h
+++ b/services/surfaceflinger/BufferLayer.h
@@ -177,7 +177,7 @@
      */
     bool onPreComposition(nsecs_t) override;
     void latchPerFrameState(compositionengine::LayerFECompositionState&) const override;
-    std::optional<renderengine::LayerSettings> prepareClientComposition(
+    std::optional<compositionengine::LayerFE::LayerSettings> prepareClientComposition(
             compositionengine::LayerFE::ClientCompositionTargetSettings&) override;
 
     // Loads the corresponding system property once per process
diff --git a/services/surfaceflinger/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp
index b55e62b..e85281d 100644
--- a/services/surfaceflinger/BufferQueueLayer.cpp
+++ b/services/surfaceflinger/BufferQueueLayer.cpp
@@ -129,8 +129,11 @@
     return frameRateChanged;
 }
 
-float BufferQueueLayer::getFrameRate() const {
-    return mLatchedFrameRate;
+std::optional<float> BufferQueueLayer::getFrameRate() const {
+    if (mLatchedFrameRate > 0.f || mLatchedFrameRate == FRAME_RATE_NO_VOTE)
+        return mLatchedFrameRate;
+
+    return {};
 }
 
 // -----------------------------------------------------------------------
diff --git a/services/surfaceflinger/BufferQueueLayer.h b/services/surfaceflinger/BufferQueueLayer.h
index 0777953..2bd1e3d 100644
--- a/services/surfaceflinger/BufferQueueLayer.h
+++ b/services/surfaceflinger/BufferQueueLayer.h
@@ -57,7 +57,7 @@
     bool shouldPresentNow(nsecs_t expectedPresentTime) const override;
 
     bool setFrameRate(float frameRate) override;
-    float getFrameRate() const override;
+    std::optional<float> getFrameRate() const override;
 
     // -----------------------------------------------------------------------
 
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index 287fe89..cd4227c 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -253,7 +253,8 @@
                                            FrameTracer::FrameEvent::POST);
     mCurrentState.desiredPresentTime = desiredPresentTime;
 
-    mFlinger->mScheduler->recordLayerHistory(this, desiredPresentTime);
+    mFlinger->mScheduler->recordLayerHistory(this,
+                                             desiredPresentTime <= 0 ? 0 : desiredPresentTime);
 
     return true;
 }
diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h
index 73bf9d0..9427283 100644
--- a/services/surfaceflinger/BufferStateLayer.h
+++ b/services/surfaceflinger/BufferStateLayer.h
@@ -141,7 +141,7 @@
 
     std::atomic<bool> mSidebandStreamChanged{false};
 
-    mutable uint32_t mFrameNumber{0};
+    mutable uint64_t mFrameNumber{0};
     uint64_t mFrameCounter{0};
 
     sp<Fence> mPreviousReleaseFence;
diff --git a/services/surfaceflinger/ColorLayer.cpp b/services/surfaceflinger/ColorLayer.cpp
index 6aea88a..04854d0 100644
--- a/services/surfaceflinger/ColorLayer.cpp
+++ b/services/surfaceflinger/ColorLayer.cpp
@@ -50,7 +50,7 @@
 
 ColorLayer::~ColorLayer() = default;
 
-std::optional<renderengine::LayerSettings> ColorLayer::prepareClientComposition(
+std::optional<compositionengine::LayerFE::LayerSettings> ColorLayer::prepareClientComposition(
         compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) {
     auto result = Layer::prepareClientComposition(targetSettings);
     if (!result) {
diff --git a/services/surfaceflinger/ColorLayer.h b/services/surfaceflinger/ColorLayer.h
index 634a800..9246eb2 100644
--- a/services/surfaceflinger/ColorLayer.h
+++ b/services/surfaceflinger/ColorLayer.h
@@ -46,7 +46,7 @@
      * compositionengine::LayerFE overrides
      */
     void latchPerFrameState(compositionengine::LayerFECompositionState&) const override;
-    std::optional<renderengine::LayerSettings> prepareClientComposition(
+    std::optional<compositionengine::LayerFE::LayerSettings> prepareClientComposition(
             compositionengine::LayerFE::ClientCompositionTargetSettings&) override;
 
     std::shared_ptr<compositionengine::Layer> mCompositionLayer;
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 42d9875..a634f2f 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -44,6 +44,7 @@
     name: "libcompositionengine",
     defaults: ["libcompositionengine_defaults"],
     srcs: [
+        "src/ClientCompositionRequestCache.cpp",
         "src/CompositionEngine.cpp",
         "src/Display.cpp",
         "src/DisplayColorProfile.cpp",
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
index 9193dc0..a38d1f3 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
@@ -52,6 +52,10 @@
     // Creates a render surface for the display
     virtual void createRenderSurface(const RenderSurfaceCreationArgs&) = 0;
 
+    // Creates a cache to cache duplicate client composition requests and skip
+    // similar requests if needed.
+    virtual void createClientCompositionCache(uint32_t cacheSize) = 0;
+
 protected:
     ~Display() = default;
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h
index d93bfa3..67e6deb 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h
@@ -19,8 +19,15 @@
 #include <cstdint>
 #include <string>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
 #include <ui/GraphicTypes.h>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 namespace android {
 
 class HdrCapabilities;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfileCreationArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfileCreationArgs.h
index ef0f925..7eb8eb1 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfileCreationArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfileCreationArgs.h
@@ -20,7 +20,15 @@
 #include <unordered_map>
 #include <vector>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
 #include <ui/GraphicTypes.h>
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 #include <ui/HdrCapabilities.h>
 
 namespace android::compositionengine {
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index e432b1c..26442d9 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -20,7 +20,15 @@
 #include <ostream>
 #include <unordered_set>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
 #include <renderengine/LayerSettings.h>
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
 
@@ -89,16 +97,26 @@
         Region& clearRegion;
     };
 
+    // A superset of LayerSettings required by RenderEngine to compose a layer
+    // and buffer info to determine duplicate client composition requests.
+    struct LayerSettings : renderengine::LayerSettings {
+        // Currently latched buffer if, 0 if invalid.
+        uint64_t bufferId = 0;
+
+        // Currently latched frame number, 0 if invalid.
+        uint64_t frameNumber = 0;
+    };
+
     // Returns the LayerSettings to pass to RenderEngine::drawLayers, or
     // nullopt_t if the layer does not render
-    virtual std::optional<renderengine::LayerSettings> prepareClientComposition(
+    virtual std::optional<LayerSettings> prepareClientComposition(
             ClientCompositionTargetSettings&) = 0;
 
     // Returns the LayerSettings used to draw shadows around a layer. It is passed
     // to RenderEngine::drawLayers. Returns nullopt_t if the layer does not render
     // shadows.
-    virtual std::optional<renderengine::LayerSettings> prepareShadowClientComposition(
-            const renderengine::LayerSettings& layerSettings, const Rect& displayViewport,
+    virtual std::optional<LayerSettings> prepareShadowClientComposition(
+            const LayerSettings& layerSettings, const Rect& displayViewport,
             ui::Dataspace outputDataspace) = 0;
 
     // Called after the layer is displayed to update the presentation fence
@@ -125,6 +143,13 @@
             lhs.clearRegion.hasSameRects(rhs.clearRegion);
 }
 
+static inline bool operator==(const LayerFE::LayerSettings& lhs,
+                              const LayerFE::LayerSettings& rhs) {
+    return static_cast<const renderengine::LayerSettings&>(lhs) ==
+            static_cast<const renderengine::LayerSettings&>(rhs) &&
+            lhs.bufferId == rhs.bufferId && lhs.frameNumber == rhs.frameNumber;
+}
+
 // Defining PrintTo helps with Google Tests.
 static inline void PrintTo(const LayerFE::ClientCompositionTargetSettings& settings,
                            ::std::ostream* os) {
@@ -140,5 +165,13 @@
     *os << "\n}";
 }
 
+static inline void PrintTo(const LayerFE::LayerSettings& settings, ::std::ostream* os) {
+    *os << "LayerFE::LayerSettings{";
+    PrintTo(static_cast<const renderengine::LayerSettings&>(settings), os);
+    *os << "\n    .bufferId = " << settings.bufferId;
+    *os << "\n    .frameNumber = " << settings.frameNumber;
+    *os << "\n}";
+}
+
 } // namespace compositionengine
 } // namespace android
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index 3dbd25e..1af99c5 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -18,18 +18,26 @@
 
 #include <cstdint>
 
-#include <gui/BufferQueue.h>
 #include <gui/HdrMetadata.h>
 #include <math/mat4.h>
 #include <ui/FloatRect.h>
-#include <ui/GraphicBuffer.h>
-#include <ui/GraphicTypes.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
 #include <ui/Transform.h>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
+#include <gui/BufferQueue.h>
+#include <ui/GraphicBuffer.h>
+#include <ui/GraphicTypes.h>
+
 #include "DisplayHardware/ComposerHal.h"
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 namespace android::compositionengine {
 
 /*
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index a280c75..a5da0b1 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -271,12 +271,13 @@
     virtual void chooseCompositionStrategy() = 0;
     virtual bool getSkipColorTransform() const = 0;
     virtual FrameFences presentAndGetFrameFences() = 0;
-    virtual std::vector<renderengine::LayerSettings> generateClientCompositionRequests(
+    virtual std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
             bool supportsProtectedContent, Region& clearRegion, ui::Dataspace outputDataspace) = 0;
     virtual void appendRegionFlashRequests(
             const Region& flashRegion,
-            std::vector<renderengine::LayerSettings>& clientCompositionLayers) = 0;
+            std::vector<LayerFE::LayerSettings>& clientCompositionLayers) = 0;
     virtual void setExpensiveRenderingExpected(bool enabled) = 0;
+    virtual void cacheClientCompositionRequests(uint32_t cacheSize) = 0;
 };
 
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
index a9a95cd..a466561 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
@@ -21,9 +21,16 @@
 
 #include <utils/StrongPointer.h>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
 #include "DisplayHardware/ComposerHal.h"
 #include "DisplayHardware/DisplayIdentification.h"
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 namespace HWC2 {
 class Layer;
 } // namespace HWC2
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/ClientCompositionRequestCache.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/ClientCompositionRequestCache.h
new file mode 100644
index 0000000..c14a6e8
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/ClientCompositionRequestCache.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <deque>
+
+#include <compositionengine/LayerFE.h>
+#include <renderengine/DisplaySettings.h>
+#include <renderengine/LayerSettings.h>
+
+namespace android {
+
+namespace compositionengine::impl {
+
+// The cache is used to skip duplicate client composition requests. We do so by keeping track
+// of every composition request and the buffer that the request is rendered into. During the
+// next composition request, if the request matches what was rendered into the buffer, then
+// we can skip of the request, pass back an empty fence, and let HWC use the previous render
+// result.
+//
+// The cache is a mapping of the RenderSurface buffer id (unique per process) and a snapshot of
+// the composition request. We need to make sure the request, including the order of the
+// layers, do not change from call to call. The snapshot removes strong references to the
+// client buffer id so we don't extend the lifetime of the buffer by storing it in the cache.
+class ClientCompositionRequestCache {
+public:
+    explicit ClientCompositionRequestCache(uint32_t cacheSize) : mMaxCacheSize(cacheSize){};
+    ~ClientCompositionRequestCache() = default;
+    bool exists(uint64_t bufferId, const renderengine::DisplaySettings& display,
+                const std::vector<LayerFE::LayerSettings>& layerSettings) const;
+    void add(uint64_t bufferId, const renderengine::DisplaySettings& display,
+             const std::vector<LayerFE::LayerSettings>& layerSettings);
+    void remove(uint64_t bufferId);
+
+private:
+    uint32_t mMaxCacheSize;
+    struct ClientCompositionRequest {
+        renderengine::DisplaySettings display;
+        std::vector<LayerFE::LayerSettings> layerSettings;
+        ClientCompositionRequest(const renderengine::DisplaySettings& _display,
+                                 const std::vector<LayerFE::LayerSettings>& _layerSettings);
+        bool equals(const renderengine::DisplaySettings& _display,
+                    const std::vector<LayerFE::LayerSettings>& _layerSettings) const;
+    };
+
+    // Cache of requests, keyed by corresponding GraphicBuffer ID.
+    std::deque<std::pair<uint64_t /* bufferId */, ClientCompositionRequest>> mCache;
+};
+
+} // namespace compositionengine::impl
+} // namespace android
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index ace876c..39acb37 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -60,6 +60,7 @@
     void createDisplayColorProfile(
             const compositionengine::DisplayColorProfileCreationArgs&) override;
     void createRenderSurface(const compositionengine::RenderSurfaceCreationArgs&) override;
+    void createClientCompositionCache(uint32_t cacheSize) override;
 
     // Internal helpers used by chooseCompositionStrategy()
     using ChangedTypes = android::HWComposer::DeviceRequestedChanges::ChangedTypes;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
index 8eec035..2864c10 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
@@ -19,7 +19,15 @@
 #include <cstdint>
 #include <vector>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
 #include <gui/BufferQueue.h>
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 #include <utils/StrongPointer.h>
 
 namespace android {
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index e2d01ed..f469e62 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -18,8 +18,10 @@
 
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/Output.h>
+#include <compositionengine/impl/ClientCompositionRequestCache.h>
 #include <compositionengine/impl/OutputCompositionState.h>
-
+#include <renderengine/DisplaySettings.h>
+#include <renderengine/LayerSettings.h>
 #include <memory>
 #include <utility>
 #include <vector>
@@ -83,6 +85,7 @@
     void finishFrame(const CompositionRefreshArgs&) override;
     std::optional<base::unique_fd> composeSurfaces(const Region&) override;
     void postFramebuffer() override;
+    void cacheClientCompositionRequests(uint32_t) override;
 
     // Testing
     const ReleasedLayers& getReleasedLayersForTest() const;
@@ -96,11 +99,10 @@
     void chooseCompositionStrategy() override;
     bool getSkipColorTransform() const override;
     compositionengine::Output::FrameFences presentAndGetFrameFences() override;
-    std::vector<renderengine::LayerSettings> generateClientCompositionRequests(
+    std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
             bool supportsProtectedContent, Region& clearRegion,
             ui::Dataspace outputDataspace) override;
-    void appendRegionFlashRequests(const Region&,
-                                   std::vector<renderengine::LayerSettings>&) override;
+    void appendRegionFlashRequests(const Region&, std::vector<LayerFE::LayerSettings>&) override;
     void setExpensiveRenderingExpected(bool enabled) override;
     void dumpBase(std::string&) const;
 
@@ -128,6 +130,7 @@
 
     ReleasedLayers mReleasedLayers;
     OutputLayer* mLayerRequestingBackgroundBlur = nullptr;
+    std::unique_ptr<ClientCompositionRequestCache> mClientCompositionRequestCache;
 };
 
 // This template factory function standardizes the implementation details of the
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
index 17d3d3f..e700b76 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
@@ -19,7 +19,16 @@
 #include <cstdint>
 
 #include <math/mat4.h>
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
 #include <ui/GraphicTypes.h>
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 #include <ui/Rect.h>
 #include <ui/Region.h>
 #include <ui/Transform.h>
@@ -45,6 +54,9 @@
     // composition
     bool flipClientTarget{false};
 
+    // If true, the current frame reused the buffer from a previous client composition
+    bool reusedClientComposition{false};
+
     // If true, this output displays layers that are internal-only
     bool layerStackInternal{false};
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
index 00baa89..b0a9bc9 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
@@ -27,8 +27,15 @@
 #include <ui/Rect.h>
 #include <ui/Region.h>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
 #include "DisplayHardware/ComposerHal.h"
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 namespace HWC2 {
 class Layer;
 } // namespace HWC2
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
index 57f33ae..3a4c70f 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
@@ -40,6 +40,7 @@
 
     MOCK_METHOD1(createDisplayColorProfile, void(const DisplayColorProfileCreationArgs&));
     MOCK_METHOD1(createRenderSurface, void(const RenderSurfaceCreationArgs&));
+    MOCK_METHOD1(createClientCompositionCache, void(uint32_t));
 };
 
 } // namespace android::compositionengine::mock
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index 739490f..163e302 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -36,11 +36,10 @@
                        void(LayerFECompositionState&, compositionengine::LayerFE::StateSubset));
     MOCK_CONST_METHOD1(latchCursorCompositionState, void(LayerFECompositionState&));
     MOCK_METHOD1(prepareClientComposition,
-                 std::optional<renderengine::LayerSettings>(
+                 std::optional<LayerSettings>(
                          compositionengine::LayerFE::ClientCompositionTargetSettings&));
     MOCK_METHOD3(prepareShadowClientComposition,
-                 std::optional<renderengine::LayerSettings>(const renderengine::LayerSettings&,
-                                                            const Rect&, ui::Dataspace));
+                 std::optional<LayerSettings>(const LayerSettings&, const Rect&, ui::Dataspace));
 
     MOCK_METHOD1(onLayerDisplayed, void(const sp<Fence>&));
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index 367e8ca..c41302d 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -107,10 +107,11 @@
     MOCK_METHOD0(presentAndGetFrameFences, compositionengine::Output::FrameFences());
 
     MOCK_METHOD3(generateClientCompositionRequests,
-                 std::vector<renderengine::LayerSettings>(bool, Region&, ui::Dataspace));
+                 std::vector<LayerFE::LayerSettings>(bool, Region&, ui::Dataspace));
     MOCK_METHOD2(appendRegionFlashRequests,
-                 void(const Region&, std::vector<renderengine::LayerSettings>&));
+                 void(const Region&, std::vector<LayerFE::LayerSettings>&));
     MOCK_METHOD1(setExpensiveRenderingExpected, void(bool));
+    MOCK_METHOD1(cacheClientCompositionRequests, void(uint32_t));
 };
 
 } // namespace android::compositionengine::mock
diff --git a/services/surfaceflinger/CompositionEngine/mock/Display.cpp b/services/surfaceflinger/CompositionEngine/mock/Display.cpp
index dc303d7..01cf112 100644
--- a/services/surfaceflinger/CompositionEngine/mock/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/mock/Display.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <compositionengine/mock/Display.h>
 
 namespace android::compositionengine::mock {
@@ -28,6 +24,3 @@
 Display::~Display() = default;
 
 } // namespace android::compositionengine::mock
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/mock/Layer.cpp b/services/surfaceflinger/CompositionEngine/mock/Layer.cpp
index a733bac..08483cb 100644
--- a/services/surfaceflinger/CompositionEngine/mock/Layer.cpp
+++ b/services/surfaceflinger/CompositionEngine/mock/Layer.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <compositionengine/mock/Layer.h>
 
 namespace android::compositionengine::mock {
@@ -28,6 +24,3 @@
 Layer::~Layer() = default;
 
 } // namespace android::compositionengine::mock
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/mock/LayerFE.cpp b/services/surfaceflinger/CompositionEngine/mock/LayerFE.cpp
index 232756f..607eaad 100644
--- a/services/surfaceflinger/CompositionEngine/mock/LayerFE.cpp
+++ b/services/surfaceflinger/CompositionEngine/mock/LayerFE.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <compositionengine/mock/LayerFE.h>
 
 namespace android::compositionengine::mock {
@@ -28,6 +24,3 @@
 LayerFE::~LayerFE() = default;
 
 } // namespace android::compositionengine::mock
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/mock/Output.cpp b/services/surfaceflinger/CompositionEngine/mock/Output.cpp
index 2608ef0..44df4c3 100644
--- a/services/surfaceflinger/CompositionEngine/mock/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/mock/Output.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <compositionengine/mock/Output.h>
 
 namespace android::compositionengine::mock {
@@ -28,6 +24,3 @@
 Output::~Output() = default;
 
 } // namespace android::compositionengine::mock
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/mock/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/mock/OutputLayer.cpp
index c1153e3..4da9377 100644
--- a/services/surfaceflinger/CompositionEngine/mock/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/mock/OutputLayer.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <compositionengine/mock/OutputLayer.h>
 
 namespace android::compositionengine::mock {
@@ -28,6 +24,3 @@
 OutputLayer::~OutputLayer() = default;
 
 } // namespace android::compositionengine::mock
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp b/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
new file mode 100644
index 0000000..acaaf4e
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include <algorithm>
+
+#include <compositionengine/impl/ClientCompositionRequestCache.h>
+#include <renderengine/DisplaySettings.h>
+#include <renderengine/LayerSettings.h>
+
+namespace android::compositionengine::impl {
+
+namespace {
+LayerFE::LayerSettings getLayerSettingsSnapshot(const LayerFE::LayerSettings& settings) {
+    LayerFE::LayerSettings snapshot = settings;
+    snapshot.source.buffer.buffer = nullptr;
+    snapshot.source.buffer.fence = nullptr;
+    return snapshot;
+}
+
+inline bool equalIgnoringSource(const renderengine::LayerSettings& lhs,
+                                const renderengine::LayerSettings& rhs) {
+    return lhs.geometry == rhs.geometry && lhs.alpha == rhs.alpha &&
+            lhs.sourceDataspace == rhs.sourceDataspace &&
+            lhs.colorTransform == rhs.colorTransform &&
+            lhs.disableBlending == rhs.disableBlending && lhs.shadow == rhs.shadow;
+}
+
+inline bool equalIgnoringBuffer(const renderengine::Buffer& lhs, const renderengine::Buffer& rhs) {
+    return lhs.textureName == rhs.textureName &&
+            lhs.useTextureFiltering == rhs.useTextureFiltering &&
+            lhs.textureTransform == rhs.textureTransform &&
+            lhs.usePremultipliedAlpha == rhs.usePremultipliedAlpha &&
+            lhs.isOpaque == rhs.isOpaque && lhs.isY410BT2020 == rhs.isY410BT2020 &&
+            lhs.maxMasteringLuminance == rhs.maxMasteringLuminance &&
+            lhs.maxContentLuminance == rhs.maxContentLuminance;
+}
+
+inline bool equalIgnoringBuffer(const renderengine::LayerSettings& lhs,
+                                const renderengine::LayerSettings& rhs) {
+    // compare LayerSettings without LayerSettings.PixelSource
+    return equalIgnoringSource(lhs, rhs) &&
+
+            // compare LayerSettings.PixelSource without buffer
+            lhs.source.solidColor == rhs.source.solidColor &&
+
+            // compare LayerSettings.PixelSource.Buffer without buffer & fence
+            equalIgnoringBuffer(lhs.source.buffer, rhs.source.buffer);
+}
+
+bool layerSettingsAreEqual(const LayerFE::LayerSettings& lhs, const LayerFE::LayerSettings& rhs) {
+    return lhs.bufferId == rhs.bufferId && lhs.frameNumber == rhs.frameNumber &&
+            equalIgnoringBuffer(lhs, rhs);
+}
+
+} // namespace
+
+ClientCompositionRequestCache::ClientCompositionRequest::ClientCompositionRequest(
+        const renderengine::DisplaySettings& initDisplay,
+        const std::vector<LayerFE::LayerSettings>& initLayerSettings)
+      : display(initDisplay) {
+    layerSettings.reserve(initLayerSettings.size());
+    for (const LayerFE::LayerSettings& settings : initLayerSettings) {
+        layerSettings.push_back(getLayerSettingsSnapshot(settings));
+    }
+}
+
+bool ClientCompositionRequestCache::ClientCompositionRequest::equals(
+        const renderengine::DisplaySettings& newDisplay,
+        const std::vector<LayerFE::LayerSettings>& newLayerSettings) const {
+    return newDisplay == display &&
+            std::equal(layerSettings.begin(), layerSettings.end(), newLayerSettings.begin(),
+                       newLayerSettings.end(), layerSettingsAreEqual);
+}
+
+bool ClientCompositionRequestCache::exists(
+        uint64_t bufferId, const renderengine::DisplaySettings& display,
+        const std::vector<LayerFE::LayerSettings>& layerSettings) const {
+    for (const auto& [cachedBufferId, cachedRequest] : mCache) {
+        if (cachedBufferId == bufferId) {
+            return cachedRequest.equals(display, layerSettings);
+        }
+    }
+    return false;
+}
+
+void ClientCompositionRequestCache::add(uint64_t bufferId,
+                                        const renderengine::DisplaySettings& display,
+                                        const std::vector<LayerFE::LayerSettings>& layerSettings) {
+    const ClientCompositionRequest request(display, layerSettings);
+    for (auto& [cachedBufferId, cachedRequest] : mCache) {
+        if (cachedBufferId == bufferId) {
+            cachedRequest = std::move(request);
+            return;
+        }
+    }
+
+    if (mCache.size() >= mMaxCacheSize) {
+        mCache.pop_front();
+    }
+
+    mCache.emplace_back(bufferId, std::move(request));
+}
+
+void ClientCompositionRequestCache::remove(uint64_t bufferId) {
+    for (auto it = mCache.begin(); it != mCache.end(); it++) {
+        if (it->first == bufferId) {
+            mCache.erase(it);
+            return;
+        }
+    }
+}
+
+} // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
index 030c703..aeaa18a 100644
--- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
@@ -14,21 +14,25 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/LayerFE.h>
 #include <compositionengine/OutputLayer.h>
 #include <compositionengine/impl/CompositionEngine.h>
 #include <compositionengine/impl/Display.h>
 #include <compositionengine/impl/Layer.h>
+
 #include <renderengine/RenderEngine.h>
 #include <utils/Trace.h>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
 #include "DisplayHardware/HWComposer.h"
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 namespace android::compositionengine {
 
 CompositionEngine::~CompositionEngine() = default;
@@ -159,6 +163,3 @@
 
 } // namespace impl
 } // namespace android::compositionengine
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 20f765c..ccd6572 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <android-base/stringprintf.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/CompositionRefreshArgs.h>
@@ -29,9 +25,18 @@
 #include <compositionengine/impl/DumpHelpers.h>
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/RenderSurface.h>
+
 #include <utils/Trace.h>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
 #include "DisplayHardware/HWComposer.h"
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 #include "DisplayHardware/PowerAdvisor.h"
 
 namespace android::compositionengine::impl {
@@ -137,6 +142,10 @@
             compositionengine::impl::createRenderSurface(getCompositionEngine(), *this, args));
 }
 
+void Display::createClientCompositionCache(uint32_t cacheSize) {
+    cacheClientCompositionRequests(cacheSize);
+}
+
 std::unique_ptr<compositionengine::OutputLayer> Display::createOutputLayer(
         const std::shared_ptr<compositionengine::Layer>& layer,
         const sp<compositionengine::LayerFE>& layerFE) const {
@@ -341,6 +350,3 @@
 }
 
 } // namespace android::compositionengine::impl
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
index 1e7c97c..cedc333 100644
--- a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
@@ -14,14 +14,18 @@
  * limitations under the License.
  */
 
+#include <compositionengine/impl/HwcBufferCache.h>
+
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
-#include <compositionengine/impl/HwcBufferCache.h>
 #include <gui/BufferQueue.h>
 #include <ui/GraphicBuffer.h>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 namespace android::compositionengine::impl {
 
 HwcBufferCache::HwcBufferCache() {
@@ -52,6 +56,3 @@
 }
 
 } // namespace android::compositionengine::impl
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/src/Layer.cpp b/services/surfaceflinger/CompositionEngine/src/Layer.cpp
index 22ecd33..ecacaee 100644
--- a/services/surfaceflinger/CompositionEngine/src/Layer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Layer.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <android-base/stringprintf.h>
 #include <compositionengine/LayerFE.h>
 #include <compositionengine/LayerFECompositionState.h>
@@ -45,6 +41,3 @@
 
 } // namespace impl
 } // namespace android::compositionengine
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
index 8065e65..016084f 100644
--- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <android-base/stringprintf.h>
 #include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/impl/DumpHelpers.h>
@@ -91,6 +87,3 @@
 }
 
 } // namespace android::compositionengine
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index bffb74f..55371df 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <thread>
 
 #include <android-base/stringprintf.h>
@@ -32,8 +28,17 @@
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
 #include <renderengine/DisplaySettings.h>
 #include <renderengine/RenderEngine.h>
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 #include <ui/DebugUtils.h>
 #include <ui/HdrCapabilities.h>
 #include <utils/Trace.h>
@@ -231,6 +236,14 @@
     dirtyEntireOutput();
 }
 
+void Output::cacheClientCompositionRequests(uint32_t cacheSize) {
+    if (cacheSize == 0) {
+        mClientCompositionRequestCache.reset();
+    } else {
+        mClientCompositionRequestCache = std::make_unique<ClientCompositionRequestCache>(cacheSize);
+    }
+};
+
 void Output::setRenderSurfaceForTest(std::unique_ptr<compositionengine::RenderSurface> surface) {
     mRenderSurface = std::move(surface);
 }
@@ -806,6 +819,7 @@
     ALOGV(__FUNCTION__);
 
     const auto& outputState = getState();
+    OutputCompositionState& outputCompositionState = editState();
     const TracedOrdinal<bool> hasClientComposition = {"hasClientComposition",
                                                       outputState.usesClientComposition};
     base::unique_fd readyFence;
@@ -839,7 +853,7 @@
     clientCompositionDisplay.clearRegion = Region::INVALID_REGION;
 
     // Generate the client composition requests for the layers on this output.
-    std::vector<renderengine::LayerSettings> clientCompositionLayers =
+    std::vector<LayerFE::LayerSettings> clientCompositionLayers =
             generateClientCompositionRequests(supportsProtectedContent,
                                               clientCompositionDisplay.clearRegion,
                                               clientCompositionDisplay.outputDataspace);
@@ -871,6 +885,19 @@
         return std::nullopt;
     }
 
+    // Check if the client composition requests were rendered into the provided graphic buffer. If
+    // so, we can reuse the buffer and avoid client composition.
+    if (mClientCompositionRequestCache) {
+        if (mClientCompositionRequestCache->exists(buf->getId(), clientCompositionDisplay,
+                                                   clientCompositionLayers)) {
+            outputCompositionState.reusedClientComposition = true;
+            setExpensiveRenderingExpected(false);
+            return readyFence;
+        }
+        mClientCompositionRequestCache->add(buf->getId(), clientCompositionDisplay,
+                                            clientCompositionLayers);
+    }
+
     // We boost GPU frequency here because there will be color spaces conversion
     // or complex GPU shaders and it's expensive. We boost the GPU frequency so that
     // GPU composition can finish in time. We must reset GPU frequency afterwards,
@@ -882,10 +909,25 @@
         setExpensiveRenderingExpected(true);
     }
 
+    std::vector<const renderengine::LayerSettings*> clientCompositionLayerPointers;
+    clientCompositionLayerPointers.reserve(clientCompositionLayers.size());
+    std::transform(clientCompositionLayers.begin(), clientCompositionLayers.end(),
+                   std::back_inserter(clientCompositionLayerPointers),
+                   [](LayerFE::LayerSettings& settings) -> renderengine::LayerSettings* {
+                       return &settings;
+                   });
+
     const nsecs_t renderEngineStart = systemTime();
-    renderEngine.drawLayers(clientCompositionDisplay, clientCompositionLayers,
-                            buf->getNativeBuffer(), /*useFramebufferCache=*/true, std::move(fd),
-                            &readyFence);
+    status_t status =
+            renderEngine.drawLayers(clientCompositionDisplay, clientCompositionLayerPointers,
+                                    buf->getNativeBuffer(), /*useFramebufferCache=*/true,
+                                    std::move(fd), &readyFence);
+
+    if (status != NO_ERROR && mClientCompositionRequestCache) {
+        // If rendering was not successful, remove the request from the cache.
+        mClientCompositionRequestCache->remove(buf->getId());
+    }
+
     auto& timeStats = getCompositionEngine().getTimeStats();
     if (readyFence.get() < 0) {
         timeStats.recordRenderEngineDuration(renderEngineStart, systemTime());
@@ -898,9 +940,9 @@
     return readyFence;
 }
 
-std::vector<renderengine::LayerSettings> Output::generateClientCompositionRequests(
+std::vector<LayerFE::LayerSettings> Output::generateClientCompositionRequests(
         bool supportsProtectedContent, Region& clearRegion, ui::Dataspace outputDataspace) {
-    std::vector<renderengine::LayerSettings> clientCompositionLayers;
+    std::vector<LayerFE::LayerSettings> clientCompositionLayers;
     ALOGV("Rendering client layers");
 
     const auto& outputState = getState();
@@ -944,15 +986,17 @@
                     supportsProtectedContent,
                     clientComposition ? clearRegion : dummyRegion,
             };
-            if (auto result = layerFE.prepareClientComposition(targetSettings)) {
+            if (std::optional<LayerFE::LayerSettings> result =
+                        layerFE.prepareClientComposition(targetSettings)) {
                 if (!clientComposition) {
-                    auto& layerSettings = *result;
+                    LayerFE::LayerSettings& layerSettings = *result;
                     layerSettings.source.buffer.buffer = nullptr;
                     layerSettings.source.solidColor = half3(0.0, 0.0, 0.0);
                     layerSettings.alpha = half(0.0);
                     layerSettings.disableBlending = true;
+                    layerSettings.frameNumber = 0;
                 } else {
-                    std::optional<renderengine::LayerSettings> shadowLayer =
+                    std::optional<LayerFE::LayerSettings> shadowLayer =
                             layerFE.prepareShadowClientComposition(*result, outputState.viewport,
                                                                    outputDataspace);
                     if (shadowLayer) {
@@ -979,13 +1023,12 @@
 }
 
 void Output::appendRegionFlashRequests(
-        const Region& flashRegion,
-        std::vector<renderengine::LayerSettings>& clientCompositionLayers) {
+        const Region& flashRegion, std::vector<LayerFE::LayerSettings>& clientCompositionLayers) {
     if (flashRegion.isEmpty()) {
         return;
     }
 
-    renderengine::LayerSettings layerSettings;
+    LayerFE::LayerSettings layerSettings;
     layerSettings.source.buffer.buffer = nullptr;
     layerSettings.source.solidColor = half3(1.0, 0.0, 1.0);
     layerSettings.alpha = half(1.0);
@@ -1065,6 +1108,7 @@
     auto& outputState = editState();
     outputState.usesClientComposition = true;
     outputState.usesDeviceComposition = false;
+    outputState.reusedClientComposition = false;
 }
 
 bool Output::getSkipColorTransform() const {
@@ -1081,6 +1125,3 @@
 
 } // namespace impl
 } // namespace android::compositionengine
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
index 0fcc308..84d79f7 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
@@ -27,6 +27,7 @@
     dumpVal(out, "usesClientComposition", usesClientComposition);
     dumpVal(out, "usesDeviceComposition", usesDeviceComposition);
     dumpVal(out, "flipClientTarget", flipClientTarget);
+    dumpVal(out, "reusedClientComposition", reusedClientComposition);
 
     dumpVal(out, "layerStack", layerStackId);
     dumpVal(out, "layerStackInternal", layerStackInternal);
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 914a047..d92b7ef 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <android-base/stringprintf.h>
 #include <compositionengine/DisplayColorProfile.h>
 #include <compositionengine/Layer.h>
@@ -28,8 +24,15 @@
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
 #include "DisplayHardware/HWComposer.h"
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 namespace android::compositionengine {
 
 OutputLayer::~OutputLayer() = default;
@@ -651,5 +654,3 @@
 } // namespace impl
 } // namespace android::compositionengine
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayerCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayerCompositionState.cpp
index 20c8f9a..165e320 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayerCompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayerCompositionState.cpp
@@ -14,15 +14,18 @@
  * limitations under the License.
  */
 
+#include <compositionengine/impl/DumpHelpers.h>
+#include <compositionengine/impl/OutputLayerCompositionState.h>
+
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
-#include <compositionengine/impl/DumpHelpers.h>
-#include <compositionengine/impl/OutputLayerCompositionState.h>
-
 #include "DisplayHardware/HWC2.h"
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 namespace android::compositionengine::impl {
 
 namespace {
@@ -74,6 +77,3 @@
 }
 
 } // namespace android::compositionengine::impl
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
index e981172..660baff 100644
--- a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <android-base/stringprintf.h>
@@ -29,6 +25,7 @@
 #include <compositionengine/impl/DumpHelpers.h>
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <compositionengine/impl/RenderSurface.h>
+
 #include <log/log.h>
 #include <renderengine/RenderEngine.h>
 #include <sync/sync.h>
@@ -37,8 +34,15 @@
 #include <ui/Rect.h>
 #include <utils/Trace.h>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
 #include "DisplayHardware/HWComposer.h"
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 namespace android::compositionengine {
 
 RenderSurface::~RenderSurface() = default;
@@ -254,6 +258,3 @@
 
 } // namespace impl
 } // namespace android::compositionengine
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
index bcaa529..c1faa90 100644
--- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/impl/CompositionEngine.h>
@@ -302,6 +298,3 @@
 
 } // namespace
 } // namespace android::compositionengine
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index 0fe5843..ae93969 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <cmath>
 
 #include <compositionengine/DisplayColorProfileCreationArgs.h>
@@ -837,6 +833,3 @@
 
 } // namespace
 } // namespace android::compositionengine
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/tests/LayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/LayerTest.cpp
index 44df289..787f973 100644
--- a/services/surfaceflinger/CompositionEngine/tests/LayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/LayerTest.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <gtest/gtest.h>
 
 #include <compositionengine/LayerCreationArgs.h>
@@ -63,6 +59,3 @@
 
 } // namespace
 } // namespace android::compositionengine
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
index 7fd6541..a51cc67 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
@@ -20,13 +20,21 @@
 #include <ui/Fence.h>
 #include <ui/FloatRect.h>
 #include <ui/GraphicBuffer.h>
-#include <ui/GraphicTypes.h>
+
 #include <ui/Rect.h>
 #include <ui/Region.h>
 #include <ui/Transform.h>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
+#include <ui/GraphicTypes.h>
 #include "DisplayHardware/HWC2.h"
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 namespace HWC2 {
 namespace mock {
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 9971791..502a33f 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -19,8 +19,15 @@
 #include <compositionengine/Output.h>
 #include <gmock/gmock.h>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
 #include "DisplayHardware/HWComposer.h"
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 namespace android {
 namespace mock {
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index bd830be..0e579fa 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
 #include <compositionengine/mock/CompositionEngine.h>
@@ -1058,6 +1054,3 @@
 
 } // namespace
 } // namespace android::compositionengine
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 730cac8..6e8d3df 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <cmath>
 
 #include <android-base/stringprintf.h>
@@ -47,12 +43,14 @@
 using testing::ByMove;
 using testing::ByRef;
 using testing::DoAll;
+using testing::ElementsAre;
 using testing::ElementsAreArray;
 using testing::Eq;
 using testing::InSequence;
 using testing::Invoke;
 using testing::IsEmpty;
 using testing::Mock;
+using testing::Pointee;
 using testing::Property;
 using testing::Ref;
 using testing::Return;
@@ -62,6 +60,7 @@
 
 constexpr auto TR_IDENT = 0u;
 constexpr auto TR_ROT_90 = HAL_TRANSFORM_ROT_90;
+constexpr auto MAX_CLIENT_COMPOSITION_CACHE_SIZE = 3;
 
 const mat4 kIdentity;
 const mat4 kNonIdentityHalf = mat4() * 0.5f;
@@ -2765,9 +2764,9 @@
         // mock implementations.
         MOCK_CONST_METHOD0(getSkipColorTransform, bool());
         MOCK_METHOD3(generateClientCompositionRequests,
-                     std::vector<renderengine::LayerSettings>(bool, Region&, ui::Dataspace));
+                     std::vector<LayerFE::LayerSettings>(bool, Region&, ui::Dataspace));
         MOCK_METHOD2(appendRegionFlashRequests,
-                     void(const Region&, std::vector<renderengine::LayerSettings>&));
+                     void(const Region&, std::vector<LayerFE::LayerSettings>&));
         MOCK_METHOD1(setExpensiveRenderingExpected, void(bool));
     };
 
@@ -2775,6 +2774,7 @@
         mOutput.setDisplayColorProfileForTest(
                 std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
         mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
+        mOutput.cacheClientCompositionRequests(MAX_CLIENT_COMPOSITION_CACHE_SIZE);
 
         mOutput.mState.frame = kDefaultOutputFrame;
         mOutput.mState.viewport = kDefaultOutputViewport;
@@ -2787,6 +2787,7 @@
         mOutput.mState.needsFiltering = false;
         mOutput.mState.usesClientComposition = true;
         mOutput.mState.usesDeviceComposition = false;
+        mOutput.mState.reusedClientComposition = false;
 
         EXPECT_CALL(mOutput, getCompositionEngine()).WillRepeatedly(ReturnRef(mCompositionEngine));
         EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine));
@@ -2863,7 +2864,7 @@
     EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
     EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
-            .WillRepeatedly(Return(std::vector<renderengine::LayerSettings>{}));
+            .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{}));
     EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
             .WillRepeatedly(Return());
 
@@ -2877,7 +2878,7 @@
     EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
     EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
-            .WillRepeatedly(Return(std::vector<renderengine::LayerSettings>{}));
+            .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{}));
     EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
             .WillRepeatedly(Return());
 
@@ -2889,8 +2890,8 @@
 }
 
 TEST_F(OutputComposeSurfacesTest, buildsAndRendersRequestList) {
-    renderengine::LayerSettings r1;
-    renderengine::LayerSettings r2;
+    LayerFE::LayerSettings r1;
+    LayerFE::LayerSettings r2;
 
     r1.geometry.boundaries = FloatRect{1, 2, 3, 4};
     r2.geometry.boundaries = FloatRect{5, 6, 7, 8};
@@ -2899,26 +2900,143 @@
     EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
     EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
-            .WillRepeatedly(Return(std::vector<renderengine::LayerSettings>{r1}));
+            .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{r1}));
     EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
             .WillRepeatedly(
                     Invoke([&](const Region&,
-                               std::vector<renderengine::LayerSettings>& clientCompositionLayers) {
+                               std::vector<LayerFE::LayerSettings>& clientCompositionLayers) {
                         clientCompositionLayers.emplace_back(r2);
                     }));
 
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAreArray({r1, r2}), _, true, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(Pointee(r1), Pointee(r2)), _, true, _, _))
             .WillRepeatedly(Return(NO_ERROR));
 
     verify().execute().expectAFenceWasReturned();
 }
 
+TEST_F(OutputComposeSurfacesTest, renderDuplicateClientCompositionRequestsWithoutCache) {
+    mOutput.cacheClientCompositionRequests(0);
+    LayerFE::LayerSettings r1;
+    LayerFE::LayerSettings r2;
+
+    r1.geometry.boundaries = FloatRect{1, 2, 3, 4};
+    r2.geometry.boundaries = FloatRect{5, 6, 7, 8};
+
+    EXPECT_CALL(mOutput, getSkipColorTransform()).WillRepeatedly(Return(false));
+    EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
+    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
+            .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{r1, r2}));
+    EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
+            .WillRepeatedly(Return());
+
+    EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(Pointee(r1), Pointee(r2)), _, true, _, _))
+            .Times(2)
+            .WillOnce(Return(NO_ERROR));
+
+    verify().execute().expectAFenceWasReturned();
+    EXPECT_FALSE(mOutput.mState.reusedClientComposition);
+
+    verify().execute().expectAFenceWasReturned();
+    EXPECT_FALSE(mOutput.mState.reusedClientComposition);
+}
+
+TEST_F(OutputComposeSurfacesTest, skipDuplicateClientCompositionRequests) {
+    mOutput.cacheClientCompositionRequests(3);
+    LayerFE::LayerSettings r1;
+    LayerFE::LayerSettings r2;
+
+    r1.geometry.boundaries = FloatRect{1, 2, 3, 4};
+    r2.geometry.boundaries = FloatRect{5, 6, 7, 8};
+
+    EXPECT_CALL(mOutput, getSkipColorTransform()).WillRepeatedly(Return(false));
+    EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
+    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
+            .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{r1, r2}));
+    EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
+            .WillRepeatedly(Return());
+
+    EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(Pointee(r1), Pointee(r2)), _, true, _, _))
+            .WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mOutput, setExpensiveRenderingExpected(false));
+
+    verify().execute().expectAFenceWasReturned();
+    EXPECT_FALSE(mOutput.mState.reusedClientComposition);
+
+    // We do not expect another call to draw layers.
+    verify().execute().expectAFenceWasReturned();
+    EXPECT_TRUE(mOutput.mState.reusedClientComposition);
+}
+
+TEST_F(OutputComposeSurfacesTest, clientCompositionIfBufferChanges) {
+    LayerFE::LayerSettings r1;
+    LayerFE::LayerSettings r2;
+
+    r1.geometry.boundaries = FloatRect{1, 2, 3, 4};
+    r2.geometry.boundaries = FloatRect{5, 6, 7, 8};
+
+    EXPECT_CALL(mOutput, getSkipColorTransform()).WillRepeatedly(Return(false));
+    EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
+    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
+            .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{r1, r2}));
+    EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
+            .WillRepeatedly(Return());
+
+    sp<GraphicBuffer> otherOutputBuffer = new GraphicBuffer();
+    EXPECT_CALL(*mRenderSurface, dequeueBuffer(_))
+            .WillOnce(Return(mOutputBuffer))
+            .WillOnce(Return(otherOutputBuffer));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(Pointee(r1), Pointee(r2)), _, true, _, _))
+            .WillRepeatedly(Return(NO_ERROR));
+
+    verify().execute().expectAFenceWasReturned();
+    EXPECT_FALSE(mOutput.mState.reusedClientComposition);
+
+    verify().execute().expectAFenceWasReturned();
+    EXPECT_FALSE(mOutput.mState.reusedClientComposition);
+}
+
+TEST_F(OutputComposeSurfacesTest, clientCompositionIfRequestChanges) {
+    LayerFE::LayerSettings r1;
+    LayerFE::LayerSettings r2;
+    LayerFE::LayerSettings r3;
+
+    r1.geometry.boundaries = FloatRect{1, 2, 3, 4};
+    r2.geometry.boundaries = FloatRect{5, 6, 7, 8};
+    r3.geometry.boundaries = FloatRect{5, 6, 7, 9};
+
+    EXPECT_CALL(mOutput, getSkipColorTransform()).WillRepeatedly(Return(false));
+    EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
+    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>{r1, r2}))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>{r1, r3}));
+    EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
+            .WillRepeatedly(Return());
+
+    EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(Pointee(r1), Pointee(r2)), _, true, _, _))
+            .WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(Pointee(r1), Pointee(r3)), _, true, _, _))
+            .WillOnce(Return(NO_ERROR));
+
+    verify().execute().expectAFenceWasReturned();
+    EXPECT_FALSE(mOutput.mState.reusedClientComposition);
+
+    verify().execute().expectAFenceWasReturned();
+    EXPECT_FALSE(mOutput.mState.reusedClientComposition);
+}
+
 struct OutputComposeSurfacesTest_UsesExpectedDisplaySettings : public OutputComposeSurfacesTest {
     OutputComposeSurfacesTest_UsesExpectedDisplaySettings() {
         EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
         EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
-                .WillRepeatedly(Return(std::vector<renderengine::LayerSettings>{}));
+                .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{}));
         EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
                 .WillRepeatedly(Return());
         EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
@@ -3048,7 +3166,7 @@
         EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
 
         EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, _))
-                .WillRepeatedly(Return(std::vector<renderengine::LayerSettings>{}));
+                .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{}));
         EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
                 .WillRepeatedly(Return());
         EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
@@ -3166,7 +3284,7 @@
     mOutput.mState.dataspace = kExpensiveOutputDataspace;
 
     EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kExpensiveOutputDataspace))
-            .WillOnce(Return(std::vector<renderengine::LayerSettings>{}));
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>{}));
 
     // For this test, we also check the call order of key functions.
     InSequence seq;
@@ -3184,7 +3302,7 @@
 struct GenerateClientCompositionRequestsTest : public testing::Test {
     struct OutputPartialMock : public OutputPartialMockBase {
         // compositionengine::Output overrides
-        std::vector<renderengine::LayerSettings> generateClientCompositionRequests(
+        std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
                 bool supportsProtectedContent, Region& clearRegion,
                 ui::Dataspace dataspace) override {
             return impl::Output::generateClientCompositionRequests(supportsProtectedContent,
@@ -3206,7 +3324,7 @@
         StrictMock<mock::LayerFE> mLayerFE;
         LayerFECompositionState mLayerFEState;
         impl::OutputLayerCompositionState mOutputLayerState;
-        renderengine::LayerSettings mRELayerSettings;
+        LayerFE::LayerSettings mLayerSettings;
     };
 
     GenerateClientCompositionRequestsTest() {
@@ -3237,11 +3355,11 @@
             mLayers[i].mOutputLayerState.clearClientTarget = false;
             mLayers[i].mOutputLayerState.visibleRegion = Region(kDisplayFrame);
             mLayers[i].mLayerFEState.isOpaque = true;
-            mLayers[i].mRELayerSettings.geometry.boundaries =
+            mLayers[i].mLayerSettings.geometry.boundaries =
                     FloatRect{static_cast<float>(i + 1), 0.f, 0.f, 0.f};
-            mLayers[i].mRELayerSettings.source.solidColor = {1.0f, 1.0f, 1.0f};
-            mLayers[i].mRELayerSettings.alpha = 1.0f;
-            mLayers[i].mRELayerSettings.disableBlending = false;
+            mLayers[i].mLayerSettings.source.solidColor = {1.0f, 1.0f, 1.0f};
+            mLayers[i].mLayerSettings.alpha = 1.0f;
+            mLayers[i].mLayerSettings.disableBlending = false;
 
             EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(i))
                     .WillRepeatedly(Return(&mLayers[i].mOutputLayer));
@@ -3292,30 +3410,30 @@
 }
 
 TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, gathersClientCompositionRequests) {
-    renderengine::LayerSettings mREShadowSettings;
-    mREShadowSettings.source.solidColor = {0.1f, 0.1f, 0.1f};
+    LayerFE::LayerSettings mShadowSettings;
+    mShadowSettings.source.solidColor = {0.1f, 0.1f, 0.1f};
 
     EXPECT_CALL(mLayers[0].mLayerFE, prepareClientComposition(_)).WillOnce(Return(std::nullopt));
     EXPECT_CALL(mLayers[1].mLayerFE, prepareClientComposition(_))
-            .WillOnce(Return(mLayers[1].mRELayerSettings));
+            .WillOnce(Return(mLayers[1].mLayerSettings));
     EXPECT_CALL(mLayers[1].mLayerFE,
-                prepareShadowClientComposition(mLayers[1].mRELayerSettings, kDisplayViewport,
+                prepareShadowClientComposition(mLayers[1].mLayerSettings, kDisplayViewport,
                                                kDisplayDataspace))
             .WillOnce(Return(std::nullopt));
     EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(_))
-            .WillOnce(Return(mLayers[2].mRELayerSettings));
+            .WillOnce(Return(mLayers[2].mLayerSettings));
     EXPECT_CALL(mLayers[2].mLayerFE,
-                prepareShadowClientComposition(mLayers[2].mRELayerSettings, kDisplayViewport,
+                prepareShadowClientComposition(mLayers[2].mLayerSettings, kDisplayViewport,
                                                kDisplayDataspace))
-            .WillOnce(Return(mREShadowSettings));
+            .WillOnce(Return(mShadowSettings));
 
     Region accumClearRegion(Rect(10, 11, 12, 13));
     auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
                                                               accumClearRegion, kDisplayDataspace);
     ASSERT_EQ(3u, requests.size());
-    EXPECT_EQ(mLayers[1].mRELayerSettings, requests[0]);
-    EXPECT_EQ(mREShadowSettings, requests[1]);
-    EXPECT_EQ(mLayers[2].mRELayerSettings, requests[2]);
+    EXPECT_EQ(mLayers[1].mLayerSettings, requests[0]);
+    EXPECT_EQ(mShadowSettings, requests[1]);
+    EXPECT_EQ(mLayers[2].mLayerSettings, requests[2]);
 
     EXPECT_THAT(accumClearRegion, RegionEq(Region(Rect(10, 11, 12, 13))));
 
@@ -3340,7 +3458,7 @@
     mLayers[2].mLayerFEState.isOpaque = true;
 
     EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(_))
-            .WillOnce(Return(mLayers[2].mRELayerSettings));
+            .WillOnce(Return(mLayers[2].mLayerSettings));
     EXPECT_CALL(mLayers[2].mLayerFE, prepareShadowClientComposition(_, _, _))
             .WillOnce(Return(std::nullopt));
 
@@ -3348,7 +3466,7 @@
     auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
                                                               accumClearRegion, kDisplayDataspace);
     ASSERT_EQ(1u, requests.size());
-    EXPECT_EQ(mLayers[2].mRELayerSettings, requests[0]);
+    EXPECT_EQ(mLayers[2].mLayerSettings, requests[0]);
 
     EXPECT_THAT(accumClearRegion, RegionEq(Region(Rect(10, 11, 12, 13))));
 }
@@ -3368,7 +3486,7 @@
     mLayers[2].mLayerFEState.isOpaque = false;
 
     EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(_))
-            .WillOnce(Return(mLayers[2].mRELayerSettings));
+            .WillOnce(Return(mLayers[2].mLayerSettings));
     EXPECT_CALL(mLayers[2].mLayerFE, prepareShadowClientComposition(_, _, _))
             .WillOnce(Return(std::nullopt));
 
@@ -3376,7 +3494,7 @@
     auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
                                                               accumClearRegion, kDisplayDataspace);
     ASSERT_EQ(1u, requests.size());
-    EXPECT_EQ(mLayers[2].mRELayerSettings, requests[0]);
+    EXPECT_EQ(mLayers[2].mLayerSettings, requests[0]);
 
     EXPECT_THAT(accumClearRegion, RegionEq(Region(Rect(10, 11, 12, 13))));
 }
@@ -3401,9 +3519,9 @@
     mLayers[2].mLayerFEState.isOpaque = true;
 
     EXPECT_CALL(mLayers[1].mLayerFE, prepareClientComposition(_))
-            .WillOnce(Return(mLayers[1].mRELayerSettings));
+            .WillOnce(Return(mLayers[1].mLayerSettings));
     EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(_))
-            .WillOnce(Return(mLayers[2].mRELayerSettings));
+            .WillOnce(Return(mLayers[2].mLayerSettings));
     EXPECT_CALL(mLayers[2].mLayerFE, prepareShadowClientComposition(_, _, _))
             .WillOnce(Return(std::nullopt));
 
@@ -3413,13 +3531,13 @@
     ASSERT_EQ(2u, requests.size());
 
     // The second layer is expected to be rendered as alpha=0 black with no blending
-    EXPECT_EQ(mLayers[1].mRELayerSettings.geometry.boundaries, requests[0].geometry.boundaries);
+    EXPECT_EQ(mLayers[1].mLayerSettings.geometry.boundaries, requests[0].geometry.boundaries);
     EXPECT_FALSE(requests[0].source.buffer.buffer);
     EXPECT_EQ((half3{0.f, 0.f, 0.f}), requests[0].source.solidColor);
     EXPECT_EQ(0.f, static_cast<float>(requests[0].alpha));
     EXPECT_EQ(true, requests[0].disableBlending);
 
-    EXPECT_EQ(mLayers[2].mRELayerSettings, requests[1]);
+    EXPECT_EQ(mLayers[2].mLayerSettings, requests[1]);
 
     EXPECT_THAT(accumClearRegion, RegionEq(Region(Rect(10, 11, 12, 13))));
 }
@@ -3689,12 +3807,12 @@
     leftLayer.mOutputLayerState.clearClientTarget = false;
     leftLayer.mOutputLayerState.visibleRegion = Region(Rect(0, 0, 1000, 1000));
     leftLayer.mLayerFEState.isOpaque = true;
-    leftLayer.mRELayerSettings.source.solidColor = {1.f, 0.f, 0.f};
+    leftLayer.mLayerSettings.source.solidColor = {1.f, 0.f, 0.f};
 
     rightLayer.mOutputLayerState.clearClientTarget = false;
     rightLayer.mOutputLayerState.visibleRegion = Region(Rect(1000, 0, 2000, 1000));
     rightLayer.mLayerFEState.isOpaque = true;
-    rightLayer.mRELayerSettings.source.solidColor = {0.f, 1.f, 0.f};
+    rightLayer.mLayerSettings.source.solidColor = {0.f, 1.f, 0.f};
 
     EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(2u));
     EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0u))
@@ -3716,9 +3834,9 @@
     EXPECT_CALL(leftLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
     EXPECT_CALL(leftLayer.mOutputLayer, needsFiltering()).WillRepeatedly(Return(false));
     EXPECT_CALL(leftLayer.mLayerFE, prepareClientComposition(Eq(ByRef(leftLayerSettings))))
-            .WillOnce(Return(leftLayer.mRELayerSettings));
+            .WillOnce(Return(leftLayer.mLayerSettings));
     EXPECT_CALL(leftLayer.mLayerFE,
-                prepareShadowClientComposition(leftLayer.mRELayerSettings, kPortraitViewport,
+                prepareShadowClientComposition(leftLayer.mLayerSettings, kPortraitViewport,
                                                kOutputDataspace))
             .WillOnce(Return(std::nullopt));
 
@@ -3734,9 +3852,9 @@
     EXPECT_CALL(rightLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
     EXPECT_CALL(rightLayer.mOutputLayer, needsFiltering()).WillRepeatedly(Return(false));
     EXPECT_CALL(rightLayer.mLayerFE, prepareClientComposition(Eq(ByRef(rightLayerSettings))))
-            .WillOnce(Return(rightLayer.mRELayerSettings));
+            .WillOnce(Return(rightLayer.mLayerSettings));
     EXPECT_CALL(rightLayer.mLayerFE,
-                prepareShadowClientComposition(rightLayer.mRELayerSettings, kPortraitViewport,
+                prepareShadowClientComposition(rightLayer.mLayerSettings, kPortraitViewport,
                                                kOutputDataspace))
             .WillOnce(Return(std::nullopt));
 
@@ -3744,8 +3862,8 @@
     auto requests = mOutput.generateClientCompositionRequests(supportsProtectedContent,
                                                               accumClearRegion, kOutputDataspace);
     ASSERT_EQ(2u, requests.size());
-    EXPECT_EQ(leftLayer.mRELayerSettings, requests[0]);
-    EXPECT_EQ(rightLayer.mRELayerSettings, requests[1]);
+    EXPECT_EQ(leftLayer.mLayerSettings, requests[0]);
+    EXPECT_EQ(rightLayer.mLayerSettings, requests[1]);
 }
 
 TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
@@ -3755,8 +3873,8 @@
     const Region kShadowRegion = Region(kContentWithShadow).subtract(kContent);
     const Region kPartialShadowRegion = Region(kContentWithShadow).subtract(Rect(40, 40, 60, 80));
 
-    renderengine::LayerSettings mREShadowSettings;
-    mREShadowSettings.source.solidColor = {0.1f, 0.1f, 0.1f};
+    LayerFE::LayerSettings mShadowSettings;
+    mShadowSettings.source.solidColor = {0.1f, 0.1f, 0.1f};
 
     mLayers[2].mOutputLayerState.visibleRegion = kPartialShadowRegion;
     mLayers[2].mOutputLayerState.shadowRegion = kShadowRegion;
@@ -3764,18 +3882,18 @@
     EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
     EXPECT_CALL(mLayers[1].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
     EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(_))
-            .WillOnce(Return(mLayers[2].mRELayerSettings));
+            .WillOnce(Return(mLayers[2].mLayerSettings));
     EXPECT_CALL(mLayers[2].mLayerFE,
-                prepareShadowClientComposition(mLayers[2].mRELayerSettings, kDisplayViewport,
+                prepareShadowClientComposition(mLayers[2].mLayerSettings, kDisplayViewport,
                                                kDisplayDataspace))
-            .WillOnce(Return(mREShadowSettings));
+            .WillOnce(Return(mShadowSettings));
 
     Region accumClearRegion(Rect(10, 11, 12, 13));
     auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
                                                               accumClearRegion, kDisplayDataspace);
     ASSERT_EQ(1u, requests.size());
 
-    EXPECT_EQ(mREShadowSettings, requests[0]);
+    EXPECT_EQ(mShadowSettings, requests[0]);
 }
 
 TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
@@ -3786,8 +3904,8 @@
     const Region kPartialContentWithPartialShadowRegion =
             Region(kContentWithShadow).subtract(Rect(40, 40, 50, 80));
 
-    renderengine::LayerSettings mREShadowSettings;
-    mREShadowSettings.source.solidColor = {0.1f, 0.1f, 0.1f};
+    LayerFE::LayerSettings mShadowSettings;
+    mShadowSettings.source.solidColor = {0.1f, 0.1f, 0.1f};
 
     mLayers[2].mOutputLayerState.visibleRegion = kPartialContentWithPartialShadowRegion;
     mLayers[2].mOutputLayerState.shadowRegion = kShadowRegion;
@@ -3795,23 +3913,20 @@
     EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
     EXPECT_CALL(mLayers[1].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
     EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(_))
-            .WillOnce(Return(mLayers[2].mRELayerSettings));
+            .WillOnce(Return(mLayers[2].mLayerSettings));
     EXPECT_CALL(mLayers[2].mLayerFE,
-                prepareShadowClientComposition(mLayers[2].mRELayerSettings, kDisplayViewport,
+                prepareShadowClientComposition(mLayers[2].mLayerSettings, kDisplayViewport,
                                                kDisplayDataspace))
-            .WillOnce(Return(mREShadowSettings));
+            .WillOnce(Return(mShadowSettings));
 
     Region accumClearRegion(Rect(10, 11, 12, 13));
     auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
                                                               accumClearRegion, kDisplayDataspace);
     ASSERT_EQ(2u, requests.size());
 
-    EXPECT_EQ(mREShadowSettings, requests[0]);
-    EXPECT_EQ(mLayers[2].mRELayerSettings, requests[1]);
+    EXPECT_EQ(mShadowSettings, requests[0]);
+    EXPECT_EQ(mLayers[2].mLayerSettings, requests[1]);
 }
 
 } // namespace
 } // namespace android::compositionengine
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp b/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp
index 4fba10b..fd47e45 100644
--- a/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <cstdarg>
 #include <cstdint>
 
@@ -354,6 +350,3 @@
 
 } // namespace
 } // namespace android::compositionengine
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index e2122d1..4ae6dad 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -70,6 +70,12 @@
                                                                  args.nativeWindow.get()),
                                                          args.nativeWindow, args.displaySurface});
 
+    if (!mFlinger->mDisableClientCompositionCache &&
+        SurfaceFlinger::maxFrameBufferAcquiredBuffers > 0) {
+        mCompositionDisplay->createClientCompositionCache(
+                static_cast<uint32_t>(SurfaceFlinger::maxFrameBufferAcquiredBuffers));
+    }
+
     mCompositionDisplay->createDisplayColorProfile(
             compositionengine::DisplayColorProfileCreationArgs{args.hasWideColorGamut,
                                                                std::move(args.hdrCapabilities),
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 605e7c8..ff48ecd 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -227,8 +227,10 @@
 public:
     DisplayRenderArea(const sp<const DisplayDevice>& display,
                       RotationFlags rotation = ui::Transform::ROT_0)
-          : DisplayRenderArea(display, display->getBounds(), display->getWidth(),
-                              display->getHeight(), display->getCompositionDataSpace(), rotation) {}
+          : DisplayRenderArea(display, display->getBounds(),
+                              static_cast<uint32_t>(display->getWidth()),
+                              static_cast<uint32_t>(display->getHeight()),
+                              display->getCompositionDataSpace(), rotation) {}
 
     DisplayRenderArea(sp<const DisplayDevice> display, const Rect& sourceCrop, uint32_t reqWidth,
                       uint32_t reqHeight, ui::Dataspace reqDataSpace, RotationFlags rotation,
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.cpp b/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
index 9d8f31b..97eeea2 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
@@ -1324,6 +1324,36 @@
     return mClient_2_4->setContentType(display, contentType);
 }
 
+V2_4::Error Composer::setLayerGenericMetadata(Display display, Layer layer, const std::string& key,
+                                              bool mandatory, const std::vector<uint8_t>& value) {
+    using Error = V2_4::Error;
+    if (!mClient_2_4) {
+        return Error::UNSUPPORTED;
+    }
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerGenericMetadata(key, mandatory, value);
+    return Error::NONE;
+}
+
+V2_4::Error Composer::getLayerGenericMetadataKeys(
+        std::vector<IComposerClient::LayerGenericMetadataKey>* outKeys) {
+    using Error = V2_4::Error;
+    if (!mClient_2_4) {
+        return Error::UNSUPPORTED;
+    }
+    Error error = kDefaultError_2_4;
+    mClient_2_4->getLayerGenericMetadataKeys([&](const auto& tmpError, const auto& tmpKeys) {
+        error = tmpError;
+        if (error != Error::NONE) {
+            return;
+        }
+
+        *outKeys = tmpKeys;
+    });
+    return error;
+}
+
 CommandReader::~CommandReader()
 {
     resetData();
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index 6f0f38a..aa43f09 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -23,6 +23,10 @@
 #include <utility>
 #include <vector>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
 #if defined(USE_VR_COMPOSER) && USE_VR_COMPOSER
 #include <android/frameworks/vr/composer/2.0/IVrComposerClient.h>
 #endif // defined(USE_VR_COMPOSER) && USE_VR_COMPOSER
@@ -36,6 +40,9 @@
 #include <ui/GraphicBuffer.h>
 #include <utils/StrongPointer.h>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 namespace android {
 
 namespace Hwc2 {
@@ -228,6 +235,11 @@
             std::vector<IComposerClient::ContentType>* outSupportedContentTypes) = 0;
     virtual V2_4::Error setContentType(Display displayId,
                                        IComposerClient::ContentType contentType) = 0;
+    virtual V2_4::Error setLayerGenericMetadata(Display display, Layer layer,
+                                                const std::string& key, bool mandatory,
+                                                const std::vector<uint8_t>& value) = 0;
+    virtual V2_4::Error getLayerGenericMetadataKeys(
+            std::vector<IComposerClient::LayerGenericMetadataKey>* outKeys) = 0;
 };
 
 namespace impl {
@@ -463,6 +475,10 @@
             std::vector<IComposerClient::ContentType>* outSupportedContentTypes) override;
     V2_4::Error setContentType(Display displayId,
                                IComposerClient::ContentType contentType) override;
+    V2_4::Error setLayerGenericMetadata(Display display, Layer layer, const std::string& key,
+                                        bool mandatory, const std::vector<uint8_t>& value) override;
+    V2_4::Error getLayerGenericMetadataKeys(
+            std::vector<IComposerClient::LayerGenericMetadataKey>* outKeys) override;
 
 private:
 #if defined(USE_VR_COMPOSER) && USE_VR_COMPOSER
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index a0dabb4..effe43b 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -27,7 +27,13 @@
 
 #include <android-base/thread_annotations.h>
 #include <ui/Fence.h>
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
 #include <ui/GraphicTypes.h>
+#pragma clang diagnostic pop
+
 #include <utils/StrongPointer.h>
 #include <utils/Timers.h>
 
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index f4d4329..6f8df95 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -144,7 +144,7 @@
     mFlinger->onLayerDestroyed(this);
 }
 
-LayerCreationArgs::LayerCreationArgs(SurfaceFlinger* flinger, const sp<Client>& client,
+LayerCreationArgs::LayerCreationArgs(SurfaceFlinger* flinger, const sp<Client> client,
                                      std::string name, uint32_t w, uint32_t h, uint32_t flags,
                                      LayerMetadata metadata)
       : flinger(flinger),
@@ -551,7 +551,7 @@
 // drawing...
 // ---------------------------------------------------------------------------
 
-std::optional<renderengine::LayerSettings> Layer::prepareClientComposition(
+std::optional<compositionengine::LayerFE::LayerSettings> Layer::prepareClientComposition(
         compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) {
     if (!getCompositionLayer()) {
         return {};
@@ -559,7 +559,8 @@
 
     FloatRect bounds = getBounds();
     half alpha = getAlpha();
-    renderengine::LayerSettings layerSettings;
+
+    compositionengine::LayerFE::LayerSettings layerSettings;
     layerSettings.geometry.boundaries = bounds;
     if (targetSettings.useIdentityTransform) {
         layerSettings.geometry.positionTransform = mat4();
@@ -581,8 +582,8 @@
     return layerSettings;
 }
 
-std::optional<renderengine::LayerSettings> Layer::prepareShadowClientComposition(
-        const renderengine::LayerSettings& casterLayerSettings, const Rect& displayViewport,
+std::optional<compositionengine::LayerFE::LayerSettings> Layer::prepareShadowClientComposition(
+        const LayerFE::LayerSettings& casterLayerSettings, const Rect& displayViewport,
         ui::Dataspace outputDataspace) {
     renderengine::ShadowSettings shadow = getShadowSettings(displayViewport);
     if (shadow.length <= 0.f) {
@@ -593,7 +594,8 @@
     const bool casterIsOpaque = ((casterLayerSettings.source.buffer.buffer != nullptr) &&
                                  casterLayerSettings.source.buffer.isOpaque);
 
-    renderengine::LayerSettings shadowLayer = casterLayerSettings;
+    compositionengine::LayerFE::LayerSettings shadowLayer = casterLayerSettings;
+
     shadowLayer.shadow = shadow;
     shadowLayer.geometry.boundaries = mBounds; // ignore transparent region
 
@@ -604,13 +606,16 @@
     shadowLayer.shadow.spotColor *= casterAlpha;
     shadowLayer.sourceDataspace = outputDataspace;
     shadowLayer.source.buffer.buffer = nullptr;
+    shadowLayer.source.buffer.fence = nullptr;
+    shadowLayer.frameNumber = 0;
+    shadowLayer.bufferId = 0;
 
     if (shadowLayer.shadow.ambientColor.a <= 0.f && shadowLayer.shadow.spotColor.a <= 0.f) {
         return {};
     }
 
     float casterCornerRadius = shadowLayer.geometry.roundedCornersRadius;
-    const FloatRect& cornerRadiusCropRect = casterLayerSettings.geometry.roundedCornersCrop;
+    const FloatRect& cornerRadiusCropRect = shadowLayer.geometry.roundedCornersCrop;
     const FloatRect& casterRect = shadowLayer.geometry.boundaries;
 
     // crop used to set the corner radius may be larger than the content rect. Adjust the corner
@@ -1251,8 +1256,11 @@
     return true;
 }
 
-float Layer::getFrameRate() const {
-    return getDrawingState().frameRate;
+std::optional<float> Layer::getFrameRate() const {
+    const auto frameRate = getDrawingState().frameRate;
+    if (frameRate > 0.f || frameRate == FRAME_RATE_NO_VOTE) return frameRate;
+
+    return {};
 }
 
 void Layer::deferTransactionUntil_legacy(const sp<Layer>& barrierLayer, uint64_t frameNumber) {
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index ffe004f..c75a570 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -78,11 +78,11 @@
 // ---------------------------------------------------------------------------
 
 struct LayerCreationArgs {
-    LayerCreationArgs(SurfaceFlinger* flinger, const sp<Client>& client, std::string name,
+    LayerCreationArgs(SurfaceFlinger* flinger, const sp<Client> client, std::string name,
                       uint32_t w, uint32_t h, uint32_t flags, LayerMetadata metadata);
 
     SurfaceFlinger* flinger;
-    const sp<Client>& client;
+    const sp<Client> client;
     std::string name;
     uint32_t w;
     uint32_t h;
@@ -527,10 +527,10 @@
     void latchCompositionState(compositionengine::LayerFECompositionState&,
                                compositionengine::LayerFE::StateSubset subset) const override;
     void latchCursorCompositionState(compositionengine::LayerFECompositionState&) const override;
-    std::optional<renderengine::LayerSettings> prepareClientComposition(
+    std::optional<LayerSettings> prepareClientComposition(
             compositionengine::LayerFE::ClientCompositionTargetSettings&) override;
-    std::optional<renderengine::LayerSettings> prepareShadowClientComposition(
-            const renderengine::LayerSettings& layerSettings, const Rect& displayViewport,
+    std::optional<LayerSettings> prepareShadowClientComposition(
+            const LayerFE::LayerSettings& layerSettings, const Rect& displayViewport,
             ui::Dataspace outputDataspace) override;
     void onLayerDisplayed(const sp<Fence>& releaseFence) override;
     const char* getDebugName() const override;
@@ -744,8 +744,9 @@
      */
     Rect getCroppedBufferSize(const Layer::State& s) const;
 
+    constexpr static auto FRAME_RATE_NO_VOTE = -1.0f;
     virtual bool setFrameRate(float frameRate);
-    virtual float getFrameRate() const;
+    virtual std::optional<float> getFrameRate() const;
 
 protected:
     // constant
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index 33e5796..d3d9d3a 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -155,6 +155,7 @@
 
     Mutex::Autolock _l(mFlinger.mStateLock);
     mLayer = mClient->getLayerUser(mIBinder);
+    mLayer->setFrameRate(Layer::FRAME_RATE_NO_VOTE);
 
     // setting Layer's Z requires resorting layersSortedByZ
     ssize_t idx = mFlinger.mCurrentState.layersSortedByZ.indexOf(mLayer);
diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h
index a7a6dd5..9b3a9f4 100644
--- a/services/surfaceflinger/RenderArea.h
+++ b/services/surfaceflinger/RenderArea.h
@@ -71,8 +71,8 @@
     RotationFlags getRotationFlags() const { return mRotationFlags; }
 
     // Returns the size of the physical render area.
-    int getReqWidth() const { return mReqWidth; }
-    int getReqHeight() const { return mReqHeight; }
+    int getReqWidth() const { return static_cast<int>(mReqWidth); }
+    int getReqHeight() const { return static_cast<int>(mReqHeight); }
 
     // Returns the composition data space of the render area.
     ui::Dataspace getReqDataSpace() const { return mReqDataSpace; }
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index abf0cd6..b976523 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #undef LOG_TAG
 #define LOG_TAG "LayerHistory"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
@@ -43,7 +39,7 @@
 namespace {
 
 bool isLayerActive(const Layer& layer, const LayerInfo& info, nsecs_t threshold) {
-    if (layer.getFrameRate() > .0f) {
+    if (layer.getFrameRate().has_value()) {
         return layer.isVisible();
     }
     return layer.isVisible() && info.getLastUpdatedTime() >= threshold;
@@ -77,7 +73,8 @@
       : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {}
 LayerHistory::~LayerHistory() = default;
 
-void LayerHistory::registerLayer(Layer* layer, float lowRefreshRate, float highRefreshRate) {
+void LayerHistory::registerLayer(Layer* layer, float lowRefreshRate, float highRefreshRate,
+                                 LayerVoteType /*type*/) {
     auto info = std::make_unique<LayerInfo>(lowRefreshRate, highRefreshRate);
     std::lock_guard lock(mLock);
     mLayerInfos.emplace_back(layer, std::move(info));
@@ -101,8 +98,6 @@
 }
 
 LayerHistory::Summary LayerHistory::summarize(nsecs_t now) {
-    float maxRefreshRate = 0;
-
     std::lock_guard lock(mLock);
 
     partitionLayers(now);
@@ -113,7 +108,7 @@
 
         if (recent || CC_UNLIKELY(mTraceEnabled)) {
             const float refreshRate = info->getRefreshRate(now);
-            if (recent && refreshRate > maxRefreshRate) {
+            if (recent && refreshRate > 0.0f) {
                 if (const auto layer = activeLayer.promote(); layer) {
                     const int32_t priority = layer->getFrameRateSelectionPriority();
                     // TODO(b/142507166): This is where the scoring algorithm should live.
@@ -124,36 +119,30 @@
         }
     }
 
+    LayerHistory::Summary summary;
     for (const auto& [weakLayer, info] : activeLayers()) {
         const bool recent = info->isRecentlyActive(now);
         auto layer = weakLayer.promote();
         // Only use the layer if the reference still exists.
         if (layer || CC_UNLIKELY(mTraceEnabled)) {
-            float refreshRate = 0.f;
-            // Default content refresh rate is only used when dealing with recent layers.
-            if (recent) {
-                refreshRate = info->getRefreshRate(now);
-            }
             // Check if frame rate was set on layer.
-            float frameRate = layer->getFrameRate();
-            if (frameRate > 0.f) {
-                // Override content detection refresh rate, if it was set.
-                refreshRate = frameRate;
-            }
-            if (refreshRate > maxRefreshRate) {
-                maxRefreshRate = refreshRate;
+            auto frameRate = layer->getFrameRate();
+            if (frameRate.has_value() && frameRate.value() > 0.f) {
+                summary.push_back(
+                        {layer->getName(), LayerVoteType::Explicit, *frameRate, /* weight */ 1.0f});
+            } else if (recent) {
+                frameRate = info->getRefreshRate(now);
+                summary.push_back({layer->getName(), LayerVoteType::Heuristic, *frameRate,
+                                   /* weight */ 1.0f});
             }
 
             if (CC_UNLIKELY(mTraceEnabled)) {
-                trace(weakLayer, std::round(refreshRate));
+                trace(weakLayer, round<int>(*frameRate));
             }
         }
     }
-    if (CC_UNLIKELY(mTraceEnabled)) {
-        ALOGD("%s: maxRefreshRate=%.2f", __FUNCTION__, maxRefreshRate);
-    }
 
-    return {maxRefreshRate};
+    return summary;
 }
 
 void LayerHistory::partitionLayers(nsecs_t now) {
@@ -186,7 +175,7 @@
         }
     }
 
-    mLayerInfos.erase(mLayerInfos.begin() + end, mLayerInfos.end());
+    mLayerInfos.erase(mLayerInfos.begin() + static_cast<long>(end), mLayerInfos.end());
 }
 
 void LayerHistory::clear() {
@@ -199,23 +188,5 @@
     mActiveLayersEnd = 0;
 }
 
-bool LayerHistory::hasClientSpecifiedFrameRate() {
-    std::lock_guard lock(mLock);
-    for (const auto& [weakLayer, info] : activeLayers()) {
-        auto layer = weakLayer.promote();
-        if (layer) {
-            float frameRate = layer->getFrameRate();
-            // Found a layer that has a frame rate set on it.
-            if (fabs(frameRate) > 0.f) {
-                return true;
-            }
-        }
-    }
-    // Did not find any layers that have frame rate.
-    return false;
-}
-
 } // namespace android::scheduler::impl
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index f217134..a1ae35c 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -25,6 +25,8 @@
 #include <utility>
 #include <vector>
 
+#include "RefreshRateConfigs.h"
+
 namespace android {
 
 class Layer;
@@ -33,29 +35,32 @@
 namespace scheduler {
 
 class LayerHistoryTest;
+class LayerHistoryTestV2;
 class LayerInfo;
+class LayerInfoV2;
 
 class LayerHistory {
 public:
+    using LayerVoteType = RefreshRateConfigs::LayerVoteType;
+
     virtual ~LayerHistory() = default;
 
     // Layers are unregistered when the weak reference expires.
-    virtual void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate) = 0;
+    virtual void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate,
+                               LayerVoteType type) = 0;
+
+    // Sets the display size. Client is responsible for synchronization.
+    virtual void setDisplayArea(uint32_t displayArea) = 0;
 
     // Marks the layer as active, and records the given state to its history.
     virtual void record(Layer*, nsecs_t presentTime, nsecs_t now) = 0;
 
-    struct Summary {
-        float maxRefreshRate; // Maximum refresh rate among recently active layers.
-    };
+    using Summary = std::vector<RefreshRateConfigs::LayerRequirement>;
 
     // Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
     virtual Summary summarize(nsecs_t now) = 0;
 
     virtual void clear() = 0;
-
-    // Checks whether any of the active layers have a desired frame rate bit set on them.
-    virtual bool hasClientSpecifiedFrameRate() = 0;
 };
 
 namespace impl {
@@ -68,7 +73,10 @@
     virtual ~LayerHistory();
 
     // Layers are unregistered when the weak reference expires.
-    void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate) override;
+    void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate,
+                       LayerVoteType type) override;
+
+    void setDisplayArea(uint32_t /*displayArea*/) override {}
 
     // Marks the layer as active, and records the given state to its history.
     void record(Layer*, nsecs_t presentTime, nsecs_t now) override;
@@ -78,10 +86,6 @@
 
     void clear() override;
 
-    // Traverses all active layers and checks whether any of them have a desired frame
-    // rate bit set on them.
-    bool hasClientSpecifiedFrameRate() override;
-
 private:
     friend class android::scheduler::LayerHistoryTest;
     friend TestableScheduler;
@@ -94,7 +98,7 @@
         const size_t index;
 
         auto begin() { return infos.begin(); }
-        auto end() { return begin() + index; }
+        auto end() { return begin() + static_cast<long>(index); }
     };
 
     ActiveLayers activeLayers() REQUIRES(mLock) { return {mLayerInfos, mActiveLayersEnd}; }
@@ -118,6 +122,64 @@
     const bool mUseFrameRatePriority;
 };
 
+class LayerHistoryV2 : public android::scheduler::LayerHistory {
+public:
+    LayerHistoryV2();
+    virtual ~LayerHistoryV2();
+
+    // Layers are unregistered when the weak reference expires.
+    void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate,
+                       LayerVoteType type) override;
+
+    // Sets the display size. Client is responsible for synchronization.
+    void setDisplayArea(uint32_t displayArea) override { mDisplayArea = displayArea; }
+
+    // Marks the layer as active, and records the given state to its history.
+    void record(Layer*, nsecs_t presentTime, nsecs_t now) override;
+
+    // Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
+    android::scheduler::LayerHistory::Summary summarize(nsecs_t /*now*/) override;
+
+    void clear() override;
+
+private:
+    friend android::scheduler::LayerHistoryTestV2;
+    friend TestableScheduler;
+
+    using LayerPair = std::pair<wp<Layer>, std::unique_ptr<LayerInfoV2>>;
+    using LayerInfos = std::vector<LayerPair>;
+
+    struct ActiveLayers {
+        LayerInfos& infos;
+        const size_t index;
+
+        auto begin() { return infos.begin(); }
+        auto end() { return begin() + static_cast<long>(index); }
+    };
+
+    ActiveLayers activeLayers() REQUIRES(mLock) { return {mLayerInfos, mActiveLayersEnd}; }
+
+    // Iterates over layers in a single pass, swapping pairs such that active layers precede
+    // inactive layers, and inactive layers precede expired layers. Removes expired layers by
+    // truncating after inactive layers.
+    void partitionLayers(nsecs_t now) REQUIRES(mLock);
+
+    mutable std::mutex mLock;
+
+    // Partitioned such that active layers precede inactive layers. For fast lookup, the few active
+    // layers are at the front, and weak pointers are stored in contiguous memory to hit the cache.
+    LayerInfos mLayerInfos GUARDED_BY(mLock);
+    size_t mActiveLayersEnd GUARDED_BY(mLock) = 0;
+
+    uint32_t mDisplayArea = 0;
+
+    // Whether to emit systrace output and debug logs.
+    const bool mTraceEnabled;
+
+    // Whether to use priority sent from WindowManager to determine the relevancy of the layer.
+    const bool mUseFrameRatePriority;
+};
+
 } // namespace impl
 } // namespace scheduler
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
new file mode 100644
index 0000000..a6d2c74
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LayerHistoryV2"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "LayerHistory.h"
+
+#include <cutils/properties.h>
+#include <utils/Log.h>
+#include <utils/Timers.h>
+#include <utils/Trace.h>
+
+#include <algorithm>
+#include <cmath>
+#include <string>
+#include <utility>
+
+#include "../Layer.h"
+#include "SchedulerUtils.h"
+
+#include "LayerInfoV2.h"
+
+namespace android::scheduler::impl {
+
+namespace {
+
+bool isLayerActive(const Layer& layer, const LayerInfoV2& info, nsecs_t threshold) {
+    if (layer.getFrameRate().has_value()) {
+        return layer.isVisible();
+    }
+    return layer.isVisible() && info.getLastUpdatedTime() >= threshold;
+}
+
+bool traceEnabled() {
+    return property_get_bool("debug.sf.layer_history_trace", false);
+}
+
+bool useFrameRatePriority() {
+    char value[PROPERTY_VALUE_MAX];
+    property_get("debug.sf.use_frame_rate_priority", value, "1");
+    return atoi(value);
+}
+
+void trace(const wp<Layer>& weak, LayerHistory::LayerVoteType type, int fps) {
+    const auto layer = weak.promote();
+    if (!layer) return;
+
+    const auto& name = layer->getName();
+    const auto noVoteTag = "LFPS NoVote " + name;
+    const auto heuristicVoteTag = "LFPS Heuristic " + name;
+    const auto explicitVoteTag = "LFPS Explicit " + name;
+    const auto minVoteTag = "LFPS Min " + name;
+    const auto maxVoteTag = "LFPS Max " + name;
+
+    ATRACE_INT(noVoteTag.c_str(), type == LayerHistory::LayerVoteType::NoVote ? 1 : 0);
+    ATRACE_INT(heuristicVoteTag.c_str(), type == LayerHistory::LayerVoteType::Heuristic ? fps : 0);
+    ATRACE_INT(explicitVoteTag.c_str(), type == LayerHistory::LayerVoteType::Explicit ? fps : 0);
+    ATRACE_INT(minVoteTag.c_str(), type == LayerHistory::LayerVoteType::Min ? 1 : 0);
+    ATRACE_INT(maxVoteTag.c_str(), type == LayerHistory::LayerVoteType::Max ? 1 : 0);
+
+    ALOGD("%s: %s @ %d Hz", __FUNCTION__, name.c_str(), fps);
+}
+
+} // namespace
+
+LayerHistoryV2::LayerHistoryV2()
+      : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {}
+LayerHistoryV2::~LayerHistoryV2() = default;
+
+void LayerHistoryV2::registerLayer(Layer* layer, float /*lowRefreshRate*/, float highRefreshRate,
+                                   LayerVoteType type) {
+    const nsecs_t highRefreshRatePeriod = static_cast<nsecs_t>(1e9f / highRefreshRate);
+    auto info = std::make_unique<LayerInfoV2>(highRefreshRatePeriod, type);
+    std::lock_guard lock(mLock);
+    mLayerInfos.emplace_back(layer, std::move(info));
+}
+
+void LayerHistoryV2::record(Layer* layer, nsecs_t presentTime, nsecs_t now) {
+    std::lock_guard lock(mLock);
+
+    const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),
+                                 [layer](const auto& pair) { return pair.first == layer; });
+    LOG_FATAL_IF(it == mLayerInfos.end(), "%s: unknown layer %p", __FUNCTION__, layer);
+
+    const auto& info = it->second;
+    info->setLastPresentTime(presentTime, now);
+
+    // Activate layer if inactive.
+    if (const auto end = activeLayers().end(); it >= end) {
+        std::iter_swap(it, end);
+        mActiveLayersEnd++;
+    }
+}
+
+LayerHistoryV2::Summary LayerHistoryV2::summarize(nsecs_t now) {
+    LayerHistory::Summary summary;
+
+    std::lock_guard lock(mLock);
+
+    partitionLayers(now);
+
+    for (const auto& [layer, info] : activeLayers()) {
+        const auto strong = layer.promote();
+        if (!strong) {
+            continue;
+        }
+
+        const bool recent = info->isRecentlyActive(now);
+        if (recent) {
+            const auto [type, refreshRate] = info->getRefreshRate(now);
+            // Skip NoVote layer as those don't have any requirements
+            if (type == LayerHistory::LayerVoteType::NoVote) {
+                continue;
+            }
+
+            // Compute the layer's position on the screen
+            const Rect bounds = Rect(strong->getBounds());
+            const ui::Transform transform = strong->getTransform();
+            constexpr bool roundOutwards = true;
+            Rect transformed = transform.transform(bounds, roundOutwards);
+
+            const float layerArea = transformed.getWidth() * transformed.getHeight();
+            float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f;
+            summary.push_back({strong->getName(), type, refreshRate, weight});
+
+            if (CC_UNLIKELY(mTraceEnabled)) {
+                trace(layer, type, static_cast<int>(std::round(refreshRate)));
+            }
+        } else if (CC_UNLIKELY(mTraceEnabled)) {
+            trace(layer, LayerHistory::LayerVoteType::NoVote, 0);
+        }
+    }
+
+    return summary;
+}
+
+void LayerHistoryV2::partitionLayers(nsecs_t now) {
+    const nsecs_t threshold = getActiveLayerThreshold(now);
+
+    // Collect expired and inactive layers after active layers.
+    size_t i = 0;
+    while (i < mActiveLayersEnd) {
+        auto& [weak, info] = mLayerInfos[i];
+        if (const auto layer = weak.promote(); layer && isLayerActive(*layer, *info, threshold)) {
+            i++;
+            // Set layer vote if set
+            const auto frameRate = layer->getFrameRate();
+            if (frameRate.has_value()) {
+                if (*frameRate == Layer::FRAME_RATE_NO_VOTE) {
+                    info->setLayerVote(LayerVoteType::NoVote, 0.f);
+                } else {
+                    info->setLayerVote(LayerVoteType::Explicit, *frameRate);
+                }
+            } else {
+                info->resetLayerVote();
+            }
+            continue;
+        }
+
+        if (CC_UNLIKELY(mTraceEnabled)) {
+            trace(weak, LayerHistory::LayerVoteType::NoVote, 0);
+        }
+
+        info->clearHistory();
+        std::swap(mLayerInfos[i], mLayerInfos[--mActiveLayersEnd]);
+    }
+
+    // Collect expired layers after inactive layers.
+    size_t end = mLayerInfos.size();
+    while (i < end) {
+        if (mLayerInfos[i].first.promote()) {
+            i++;
+        } else {
+            std::swap(mLayerInfos[i], mLayerInfos[--end]);
+        }
+    }
+
+    mLayerInfos.erase(mLayerInfos.begin() + static_cast<long>(end), mLayerInfos.end());
+}
+
+void LayerHistoryV2::clear() {
+    std::lock_guard lock(mLock);
+
+    for (const auto& [layer, info] : activeLayers()) {
+        info->clearHistory();
+    }
+
+    mActiveLayersEnd = 0;
+}
+
+} // namespace android::scheduler::impl
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
new file mode 100644
index 0000000..d94d758
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2020 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.
+ */
+
+// #define LOG_NDEBUG 0
+
+#include "LayerInfoV2.h"
+
+#include <algorithm>
+#include <utility>
+
+#undef LOG_TAG
+#define LOG_TAG "LayerInfoV2"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+namespace android::scheduler {
+
+LayerInfoV2::LayerInfoV2(nsecs_t highRefreshRatePeriod, LayerHistory::LayerVoteType defaultVote)
+      : mHighRefreshRatePeriod(highRefreshRatePeriod),
+        mDefaultVote(defaultVote),
+        mLayerVote({defaultVote, 0.0f}) {}
+
+void LayerInfoV2::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now) {
+    lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
+
+    mLastUpdatedTime = std::max(lastPresentTime, now);
+
+    FrameTimeData frameTime = {.presetTime = lastPresentTime, .queueTime = mLastUpdatedTime};
+
+    mFrameTimes.push_back(frameTime);
+    if (mFrameTimes.size() > HISTORY_SIZE) {
+        mFrameTimes.pop_front();
+    }
+}
+
+// Returns whether the earliest present time is within the active threshold.
+bool LayerInfoV2::isRecentlyActive(nsecs_t now) const {
+    if (mFrameTimes.empty()) {
+        return false;
+    }
+
+    return mFrameTimes.back().queueTime >= getActiveLayerThreshold(now);
+}
+
+bool LayerInfoV2::isFrequent(nsecs_t now) const {
+    // Assume layer is infrequent if too few present times have been recorded.
+    if (mFrameTimes.size() < FREQUENT_LAYER_WINDOW_SIZE) {
+        return true;
+    }
+
+    // Layer is frequent if the earliest value in the window of most recent present times is
+    // within threshold.
+    const auto it = mFrameTimes.end() - FREQUENT_LAYER_WINDOW_SIZE;
+    const nsecs_t threshold = now - MAX_FREQUENT_LAYER_PERIOD_NS.count();
+    return it->queueTime >= threshold;
+}
+
+bool LayerInfoV2::hasEnoughDataForHeuristic() const {
+    // The layer had to publish at least HISTORY_SIZE or HISTORY_TIME of updates
+    if (mFrameTimes.size() < HISTORY_SIZE &&
+        mFrameTimes.back().queueTime - mFrameTimes.front().queueTime < HISTORY_TIME.count()) {
+        return false;
+    }
+
+    return true;
+}
+
+std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible() {
+    static constexpr float MARGIN = 1.0f; // 1Hz
+
+    if (!hasEnoughDataForHeuristic()) {
+        ALOGV("Not enough data");
+        return std::nullopt;
+    }
+
+    // Calculate the refresh rate by finding the average delta between frames
+    nsecs_t totalPresentTimeDeltas = 0;
+    for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
+        // If there are no presentation timestamp provided we can't calculate the refresh rate
+        if (it->presetTime == 0 || (it + 1)->presetTime == 0) {
+            return std::nullopt;
+        }
+
+        totalPresentTimeDeltas +=
+                std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod);
+    }
+    const float averageFrameTime =
+            static_cast<float>(totalPresentTimeDeltas) / (mFrameTimes.size() - 1);
+
+    // Now once we calculated the refresh rate we need to make sure that all the frames we captured
+    // are evenly distrubuted and we don't calculate the average across some burst of frames.
+
+    for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
+        const nsecs_t presentTimeDeltas =
+                std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod);
+        if (std::abs(presentTimeDeltas - averageFrameTime) > 2 * averageFrameTime) {
+            return std::nullopt;
+        }
+    }
+
+    const auto refreshRate = 1e9f / averageFrameTime;
+    if (std::abs(refreshRate - mLastReportedRefreshRate) > MARGIN) {
+        mLastReportedRefreshRate = refreshRate;
+    }
+
+    ALOGV("Refresh rate: %.2f", mLastReportedRefreshRate);
+    return mLastReportedRefreshRate;
+}
+
+std::pair<LayerHistory::LayerVoteType, float> LayerInfoV2::getRefreshRate(nsecs_t now) {
+    if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
+        return {mLayerVote.type, mLayerVote.fps};
+    }
+
+    if (!isFrequent(now)) {
+        return {LayerHistory::LayerVoteType::Min, 0};
+    }
+
+    auto refreshRate = calculateRefreshRateIfPossible();
+    if (refreshRate.has_value()) {
+        return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
+    }
+
+    return {LayerHistory::LayerVoteType::Max, 0};
+}
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.h b/services/surfaceflinger/Scheduler/LayerInfoV2.h
new file mode 100644
index 0000000..564f05e
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/LayerInfoV2.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#pragma once
+
+#include <utils/Timers.h>
+
+#include <chrono>
+#include <deque>
+
+#include "LayerHistory.h"
+#include "RefreshRateConfigs.h"
+#include "SchedulerUtils.h"
+
+namespace android {
+
+class Layer;
+
+namespace scheduler {
+
+using namespace std::chrono_literals;
+
+// Maximum period between presents for a layer to be considered active.
+constexpr std::chrono::nanoseconds MAX_ACTIVE_LAYER_PERIOD_NS = 1200ms;
+
+// Earliest present time for a layer to be considered active.
+constexpr nsecs_t getActiveLayerThreshold(nsecs_t now) {
+    return now - MAX_ACTIVE_LAYER_PERIOD_NS.count();
+}
+
+// Stores history of present times and refresh rates for a layer.
+class LayerInfoV2 {
+    // Layer is considered frequent if the earliest value in the window of most recent present times
+    // is within a threshold. If a layer is infrequent, its average refresh rate is disregarded in
+    // favor of a low refresh rate.
+    static constexpr size_t FREQUENT_LAYER_WINDOW_SIZE = 3;
+    static constexpr std::chrono::nanoseconds MAX_FREQUENT_LAYER_PERIOD_NS = 250ms;
+
+    friend class LayerHistoryTestV2;
+
+public:
+    LayerInfoV2(nsecs_t highRefreshRatePeriod, LayerHistory::LayerVoteType defaultVote);
+
+    LayerInfoV2(const LayerInfo&) = delete;
+    LayerInfoV2& operator=(const LayerInfoV2&) = delete;
+
+    // Records the last requested present time. It also stores information about when
+    // the layer was last updated. If the present time is farther in the future than the
+    // updated time, the updated time is the present time.
+    void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now);
+
+    bool isRecentlyActive(nsecs_t now) const;
+
+    // Sets an explicit layer vote. This usually comes directly from the application via
+    // ANativeWindow_setFrameRate API
+    void setLayerVote(LayerHistory::LayerVoteType type, float fps) { mLayerVote = {type, fps}; }
+
+    // Sets the default layer vote. This will be the layer vote after calling to resetLayerVote().
+    // This is used for layers that called to setLayerVote() and then removed the vote, so that the
+    // layer can go back to whatever vote it had before the app voted for it.
+    void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; }
+
+    // Resets the layer vote to its default.
+    void resetLayerVote() { mLayerVote = {mDefaultVote, 0.0f}; }
+
+    std::pair<LayerHistory::LayerVoteType, float> getRefreshRate(nsecs_t now);
+
+    // Return the last updated time. If the present time is farther in the future than the
+    // updated time, the updated time is the present time.
+    nsecs_t getLastUpdatedTime() const { return mLastUpdatedTime; }
+
+    void clearHistory() { mFrameTimes.clear(); }
+
+private:
+    bool isFrequent(nsecs_t now) const;
+    bool hasEnoughDataForHeuristic() const;
+    std::optional<float> calculateRefreshRateIfPossible();
+
+    // Used for sanitizing the heuristic data
+    const nsecs_t mHighRefreshRatePeriod;
+    LayerHistory::LayerVoteType mDefaultVote;
+
+    nsecs_t mLastUpdatedTime = 0;
+
+    float mLastReportedRefreshRate = 0.0f;
+
+    // Holds information about the layer vote
+    struct {
+        LayerHistory::LayerVoteType type;
+        float fps;
+    } mLayerVote;
+
+    // Used to store the layer timestamps
+    struct FrameTimeData {
+        nsecs_t presetTime; // desiredPresentTime, if provided
+        nsecs_t queueTime;  // buffer queue time
+    };
+    std::deque<FrameTimeData> mFrameTimes;
+    static constexpr size_t HISTORY_SIZE = 90;
+    static constexpr std::chrono::nanoseconds HISTORY_TIME = 1s;
+};
+
+} // namespace scheduler
+} // namespace android
diff --git a/services/surfaceflinger/Scheduler/PhaseOffsets.cpp b/services/surfaceflinger/Scheduler/PhaseOffsets.cpp
index 106aa9b..63d9c4b 100644
--- a/services/surfaceflinger/Scheduler/PhaseOffsets.cpp
+++ b/services/surfaceflinger/Scheduler/PhaseOffsets.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include "PhaseOffsets.h"
 
 #include <cutils/properties.h>
@@ -119,9 +115,9 @@
 }
 
 PhaseOffsets::Offsets PhaseOffsets::getHighFpsOffsets(nsecs_t vsyncDuration) const {
-    const int highFpsLateAppOffsetNs =
+    const auto highFpsLateAppOffsetNs =
             getProperty("debug.sf.high_fps_late_app_phase_offset_ns").value_or(2000000);
-    const int highFpsLateSfOffsetNs =
+    const auto highFpsLateSfOffsetNs =
             getProperty("debug.sf.high_fps_late_sf_phase_offset_ns").value_or(1000000);
 
     const auto highFpsEarlySfOffsetNs = getProperty("debug.sf.high_fps_early_phase_offset_ns");
@@ -209,6 +205,32 @@
                             : vsyncDuration - (appDuration + sfDuration) % vsyncDuration;
 }
 
+PhaseDurations::Offsets PhaseDurations::constructOffsets(nsecs_t vsyncDuration) const {
+    return Offsets{
+            {
+                    mSfEarlyDuration < vsyncDuration
+                            ? sfDurationToOffset(mSfEarlyDuration, vsyncDuration)
+                            : sfDurationToOffset(mSfEarlyDuration, vsyncDuration) - vsyncDuration,
+
+                    appDurationToOffset(mAppEarlyDuration, mSfEarlyDuration, vsyncDuration),
+            },
+            {
+                    mSfEarlyGlDuration < vsyncDuration
+                            ? sfDurationToOffset(mSfEarlyGlDuration, vsyncDuration)
+                            : sfDurationToOffset(mSfEarlyGlDuration, vsyncDuration) - vsyncDuration,
+
+                    appDurationToOffset(mAppEarlyGlDuration, mSfEarlyGlDuration, vsyncDuration),
+            },
+            {
+                    mSfDuration < vsyncDuration
+                            ? sfDurationToOffset(mSfDuration, vsyncDuration)
+                            : sfDurationToOffset(mSfDuration, vsyncDuration) - vsyncDuration,
+
+                    appDurationToOffset(mAppDuration, mSfDuration, vsyncDuration),
+            },
+    };
+}
+
 static std::vector<float> getRefreshRatesFromConfigs(
         const android::scheduler::RefreshRateConfigs& refreshRateConfigs) {
     const auto& allRefreshRates = refreshRateConfigs.getAllRefreshRates();
@@ -227,41 +249,7 @@
     std::unordered_map<float, Offsets> offsets;
 
     for (const auto fps : refreshRates) {
-        const nsecs_t vsyncDuration = static_cast<nsecs_t>(1e9f / fps);
-        offsets.emplace(fps,
-                        Offsets{
-                                {
-                                        mSfEarlyDuration < vsyncDuration
-                                                ? sfDurationToOffset(mSfEarlyDuration,
-                                                                     vsyncDuration)
-                                                : sfDurationToOffset(mSfEarlyDuration,
-                                                                     vsyncDuration) -
-                                                        vsyncDuration,
-
-                                        appDurationToOffset(mAppEarlyDuration, mSfEarlyDuration,
-                                                            vsyncDuration),
-                                },
-                                {
-                                        mSfEarlyGlDuration < vsyncDuration
-                                                ? sfDurationToOffset(mSfEarlyGlDuration,
-                                                                     vsyncDuration)
-                                                : sfDurationToOffset(mSfEarlyGlDuration,
-                                                                     vsyncDuration) -
-                                                        vsyncDuration,
-
-                                        appDurationToOffset(mAppEarlyGlDuration, mSfEarlyGlDuration,
-                                                            vsyncDuration),
-                                },
-                                {
-                                        mSfDuration < vsyncDuration
-                                                ? sfDurationToOffset(mSfDuration, vsyncDuration)
-                                                : sfDurationToOffset(mSfDuration, vsyncDuration) -
-                                                        vsyncDuration,
-
-                                        appDurationToOffset(mAppDuration, mSfDuration,
-                                                            vsyncDuration),
-                                },
-                        });
+        offsets.emplace(fps, constructOffsets(static_cast<nsecs_t>(1e9f / fps)));
     }
     return offsets;
 }
@@ -295,8 +283,16 @@
     const auto iter = std::find_if(mOffsets.begin(), mOffsets.end(), [=](const auto& candidateFps) {
         return fpsEqualsWithMargin(fps, candidateFps.first);
     });
-    LOG_ALWAYS_FATAL_IF(iter == mOffsets.end());
-    return iter->second;
+
+    if (iter != mOffsets.end()) {
+        return iter->second;
+    }
+
+    // Unknown refresh rate. This might happen if we get a hotplug event for the default display.
+    // This happens only during tests and not during regular device operation.
+    // In this case just construct the offset.
+    ALOGW("Can't find offset for %.2f fps", fps);
+    return constructOffsets(static_cast<nsecs_t>(1e9f / fps));
 }
 
 void PhaseDurations::dump(std::string& result) const {
@@ -334,6 +330,3 @@
 
 } // namespace impl
 } // namespace android::scheduler
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/Scheduler/PhaseOffsets.h b/services/surfaceflinger/Scheduler/PhaseOffsets.h
index 7b1bdfd..b7d4eae 100644
--- a/services/surfaceflinger/Scheduler/PhaseOffsets.h
+++ b/services/surfaceflinger/Scheduler/PhaseOffsets.h
@@ -108,6 +108,7 @@
 
 private:
     std::unordered_map<float, Offsets> initializeOffsets(const std::vector<float>&) const;
+    PhaseDurations::Offsets constructOffsets(nsecs_t vsyncDuration) const;
 
     const nsecs_t mSfDuration;
     const nsecs_t mAppDuration;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 45d1f23..c187049 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -14,24 +14,47 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 // #define LOG_NDEBUG 0
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
 #include "RefreshRateConfigs.h"
+#include <android-base/stringprintf.h>
+#include <utils/Trace.h>
+#include <chrono>
+#include <cmath>
+
+using namespace std::chrono_literals;
 
 namespace android::scheduler {
 
 using AllRefreshRatesMapType = RefreshRateConfigs::AllRefreshRatesMapType;
 using RefreshRate = RefreshRateConfigs::RefreshRate;
 
-// Returns the refresh rate map. This map won't be modified at runtime, so it's safe to access
-// from multiple threads. This can only be called if refreshRateSwitching() returns true.
-// TODO(b/122916473): Get this information from configs prepared by vendors, instead of
-// baking them in.
-const RefreshRate& RefreshRateConfigs::getRefreshRateForContent(float contentFramerate) const {
+const RefreshRate& RefreshRateConfigs::getRefreshRateForContent(
+        const std::vector<LayerRequirement>& layers) const {
     std::lock_guard lock(mLock);
+    int contentFramerate = 0;
+    int explicitContentFramerate = 0;
+    for (const auto& layer : layers) {
+        const auto desiredRefreshRateRound = round<int>(layer.desiredRefreshRate);
+        if (layer.vote == LayerVoteType::Explicit) {
+            if (desiredRefreshRateRound > explicitContentFramerate) {
+                explicitContentFramerate = desiredRefreshRateRound;
+            }
+        } else {
+            if (desiredRefreshRateRound > contentFramerate) {
+                contentFramerate = desiredRefreshRateRound;
+            }
+        }
+    }
+
+    if (explicitContentFramerate != 0) {
+        contentFramerate = explicitContentFramerate;
+    } else if (contentFramerate == 0) {
+        contentFramerate = round<int>(mMaxSupportedRefreshRate->fps);
+    }
+    ATRACE_INT("ContentFPS", contentFramerate);
+
     // Find the appropriate refresh rate with minimal error
     auto iter = min_element(mAvailableRefreshRates.cbegin(), mAvailableRefreshRates.cend(),
                             [contentFramerate](const auto& lhs, const auto& rhs) -> bool {
@@ -60,6 +83,113 @@
     return *bestSoFar;
 }
 
+const RefreshRate& RefreshRateConfigs::getRefreshRateForContentV2(
+        const std::vector<LayerRequirement>& layers) const {
+    constexpr nsecs_t MARGIN = std::chrono::nanoseconds(800us).count();
+    ATRACE_CALL();
+    ALOGV("getRefreshRateForContent %zu layers", layers.size());
+
+    std::lock_guard lock(mLock);
+
+    int noVoteLayers = 0;
+    int minVoteLayers = 0;
+    int maxVoteLayers = 0;
+    int explicitVoteLayers = 0;
+    for (const auto& layer : layers) {
+        if (layer.vote == LayerVoteType::NoVote)
+            noVoteLayers++;
+        else if (layer.vote == LayerVoteType::Min)
+            minVoteLayers++;
+        else if (layer.vote == LayerVoteType::Max)
+            maxVoteLayers++;
+        else if (layer.vote == LayerVoteType::Explicit)
+            explicitVoteLayers++;
+    }
+
+    // Only if all layers want Min we should return Min
+    if (noVoteLayers + minVoteLayers == layers.size()) {
+        return *mAvailableRefreshRates.front();
+    }
+
+    // If we have some Max layers and no Explicit we should return Max
+    if (maxVoteLayers > 0 && explicitVoteLayers == 0) {
+        return *mAvailableRefreshRates.back();
+    }
+
+    // Find the best refresh rate based on score
+    std::vector<std::pair<const RefreshRate*, float>> scores;
+    scores.reserve(mAvailableRefreshRates.size());
+
+    for (const auto refreshRate : mAvailableRefreshRates) {
+        scores.emplace_back(refreshRate, 0.0f);
+    }
+
+    for (const auto& layer : layers) {
+        if (layer.vote == LayerVoteType::NoVote || layer.vote == LayerVoteType::Min ||
+            layer.vote == LayerVoteType::Max) {
+            continue;
+        }
+
+        // If we have Explicit layers, ignore the Huristic ones
+        if (explicitVoteLayers > 0 && layer.vote == LayerVoteType::Heuristic) {
+            continue;
+        }
+
+        for (auto& [refreshRate, overallScore] : scores) {
+            const auto displayPeriod = refreshRate->vsyncPeriod;
+            const auto layerPeriod = round<nsecs_t>(1e9f / layer.desiredRefreshRate);
+
+            // Calculate how many display vsyncs we need to present a single frame for this layer
+            auto [displayFramesQuot, displayFramesRem] = std::div(layerPeriod, displayPeriod);
+            if (displayFramesRem <= MARGIN ||
+                std::abs(displayFramesRem - displayPeriod) <= MARGIN) {
+                displayFramesQuot++;
+                displayFramesRem = 0;
+            }
+
+            float layerScore;
+            if (displayFramesRem == 0) {
+                // Layer desired refresh rate matches the display rate.
+                layerScore = layer.weight * 1.0f;
+            } else if (displayFramesQuot == 0) {
+                // Layer desired refresh rate is higher the display rate.
+                layerScore = layer.weight * layerPeriod / displayPeriod;
+            } else {
+                // Layer desired refresh rate is lower the display rate. Check how well it fits the
+                // cadence
+                auto diff = std::abs(displayFramesRem - (displayPeriod - displayFramesRem));
+                int iter = 2;
+                static constexpr size_t MAX_ITERATOR = 10; // Stop calculating when score < 0.1
+                while (diff > MARGIN && iter < MAX_ITERATOR) {
+                    diff = diff - (displayPeriod - diff);
+                    iter++;
+                }
+
+                layerScore = layer.weight * 1.0f / iter;
+            }
+
+            ALOGV("%s (weight %.2f) %.2fHz gives %s score of %.2f", layer.name.c_str(),
+                  layer.weight, 1e9f / layerPeriod, refreshRate->name.c_str(), layerScore);
+            overallScore += layerScore;
+        }
+    }
+
+    float max = 0;
+    const RefreshRate* bestRefreshRate = nullptr;
+    for (const auto [refreshRate, score] : scores) {
+        ALOGV("%s scores %.2f", refreshRate->name.c_str(), score);
+
+        ATRACE_INT(refreshRate->name.c_str(), round<int>(score * 100));
+
+        if (score > max) {
+            max = score;
+            bestRefreshRate = refreshRate;
+        }
+    }
+
+    return bestRefreshRate == nullptr ? *mCurrentRefreshRate : *bestRefreshRate;
+}
+
 const AllRefreshRatesMapType& RefreshRateConfigs::getAllRefreshRates() const {
     return mRefreshRates;
 }
@@ -105,11 +235,10 @@
         HwcConfigIndexType currentConfigId)
       : mRefreshRateSwitching(refreshRateSwitching) {
     std::vector<InputConfig> inputConfigs;
-    for (auto configId = HwcConfigIndexType(0); configId < HwcConfigIndexType(configs.size());
-         ++configId) {
-        auto configGroup = HwcConfigGroupType(configs[configId.value()]->getConfigGroup());
-        inputConfigs.push_back(
-                {configId, configGroup, configs[configId.value()]->getVsyncPeriod()});
+    for (size_t configId = 0; configId < configs.size(); ++configId) {
+        auto configGroup = HwcConfigGroupType(configs[configId]->getConfigGroup());
+        inputConfigs.push_back({HwcConfigIndexType(static_cast<int>(configId)), configGroup,
+                                configs[configId]->getVsyncPeriod()});
     }
     init(inputConfigs, currentConfigId);
 }
@@ -180,14 +309,21 @@
 void RefreshRateConfigs::constructAvailableRefreshRates() {
     // Filter configs based on current policy and sort based on vsync period
     HwcConfigGroupType group = mRefreshRates.at(mDefaultConfig).configGroup;
-    ALOGV("constructRefreshRateMap: default %d group %d min %.2f max %.2f", mDefaultConfig.value(),
-          group.value(), mMinRefreshRateFps, mMaxRefreshRateFps);
+    ALOGV("constructAvailableRefreshRates: default %d group %d min %.2f max %.2f",
+          mDefaultConfig.value(), group.value(), mMinRefreshRateFps, mMaxRefreshRateFps);
     getSortedRefreshRateList(
             [&](const RefreshRate& refreshRate) REQUIRES(mLock) {
                 return refreshRate.configGroup == group &&
                         refreshRate.inPolicy(mMinRefreshRateFps, mMaxRefreshRateFps);
             },
             &mAvailableRefreshRates);
+
+    std::string availableRefreshRates;
+    for (const auto& refreshRate : mAvailableRefreshRates) {
+        base::StringAppendF(&availableRefreshRates, "%s ", refreshRate->name.c_str());
+    }
+
+    ALOGV("Available refresh rates: %s", availableRefreshRates.c_str());
     LOG_ALWAYS_FATAL_IF(mAvailableRefreshRates.empty(),
                         "No compatible display configs for default=%d min=%.0f max=%.0f",
                         mDefaultConfig.value(), mMinRefreshRateFps, mMaxRefreshRateFps);
@@ -221,5 +357,3 @@
 }
 
 } // namespace android::scheduler
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 0c3369a..80d42cc 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -97,8 +97,39 @@
     // Returns true if this device is doing refresh rate switching. This won't change at runtime.
     bool refreshRateSwitchingSupported() const { return mRefreshRateSwitching; }
 
+    // Describes the different options the layer voted for refresh rate
+    enum class LayerVoteType {
+        NoVote,    // Doesn't care about the refresh rate
+        Min,       // Minimal refresh rate available
+        Max,       // Maximal refresh rate available
+        Heuristic, // Specific refresh rate that was calculated by platform using a heuristic
+        Explicit,  // Specific refresh rate that was provided by the app
+    };
+
+    // Captures the layer requirements for a refresh rate. This will be used to determine the
+    // display refresh rate.
+    struct LayerRequirement {
+        std::string name;         // Layer's name. Used for debugging purposes.
+        LayerVoteType vote;       // Layer vote type.
+        float desiredRefreshRate; // Layer's desired refresh rate, if applicable.
+        float weight; // Layer's weight in the range of [0, 1]. The higher the weight the more
+                      // impact this layer would have on choosing the refresh rate.
+
+        bool operator==(const LayerRequirement& other) const {
+            return name == other.name && vote == other.vote &&
+                    desiredRefreshRate == other.desiredRefreshRate && weight == other.weight;
+        }
+
+        bool operator!=(const LayerRequirement& other) const { return !(*this == other); }
+    };
+
     // Returns all available refresh rates according to the current policy.
-    const RefreshRate& getRefreshRateForContent(float contentFramerate) const EXCLUDES(mLock);
+    const RefreshRate& getRefreshRateForContent(const std::vector<LayerRequirement>& layers) const
+            EXCLUDES(mLock);
+
+    // Returns all available refresh rates according to the current policy.
+    const RefreshRate& getRefreshRateForContentV2(const std::vector<LayerRequirement>& layers) const
+            EXCLUDES(mLock);
 
     // Returns all the refresh rates supported by the device. This won't change at runtime.
     const AllRefreshRatesMapType& getAllRefreshRates() const EXCLUDES(mLock);
diff --git a/services/surfaceflinger/Scheduler/RefreshRateStats.h b/services/surfaceflinger/Scheduler/RefreshRateStats.h
index a384dbe..e44cd52 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateStats.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateStats.h
@@ -114,7 +114,8 @@
                 mConfigModesTotalTime[mCurrentConfigMode] = 0;
             }
             mConfigModesTotalTime[mCurrentConfigMode] += timeElapsedMs;
-            fps = mRefreshRateConfigs.getRefreshRateFromConfigId(mCurrentConfigMode).fps;
+            fps = static_cast<uint32_t>(std::round(
+                    mRefreshRateConfigs.getRefreshRateFromConfigId(mCurrentConfigMode).fps));
         } else {
             mScreenOffTime += timeElapsedMs;
         }
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 0b645c4..7de35af 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #undef LOG_TAG
 #define LOG_TAG "Scheduler"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
@@ -103,16 +99,21 @@
 
 Scheduler::Scheduler(impl::EventControlThread::SetVSyncEnabledFunction function,
                      const scheduler::RefreshRateConfigs& refreshRateConfig,
-                     ISchedulerCallback& schedulerCallback)
+                     ISchedulerCallback& schedulerCallback, bool useContentDetectionV2)
       : mPrimaryDispSync(createDispSync()),
         mEventControlThread(new impl::EventControlThread(std::move(function))),
         mSupportKernelTimer(sysprop::support_kernel_idle_timer(false)),
         mSchedulerCallback(schedulerCallback),
-        mRefreshRateConfigs(refreshRateConfig) {
+        mRefreshRateConfigs(refreshRateConfig),
+        mUseContentDetectionV2(useContentDetectionV2) {
     using namespace sysprop;
 
     if (property_get_bool("debug.sf.use_smart_90_for_video", 0) || use_smart_90_for_video(false)) {
-        mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
+        if (mUseContentDetectionV2) {
+            mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>();
+        } else {
+            mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
+        }
     }
 
     const int setIdleTimerMs = property_get_int32("debug.sf.set_idle_timer_ms", 0);
@@ -120,7 +121,6 @@
     if (const auto millis = setIdleTimerMs ? setIdleTimerMs : set_idle_timer_ms(0); millis > 0) {
         const auto callback = mSupportKernelTimer ? &Scheduler::kernelIdleTimerCallback
                                                   : &Scheduler::idleTimerCallback;
-
         mIdleTimer.emplace(
                 std::chrono::milliseconds(millis),
                 [this, callback] { std::invoke(callback, this, TimerState::Reset); },
@@ -149,12 +149,13 @@
 Scheduler::Scheduler(std::unique_ptr<DispSync> primaryDispSync,
                      std::unique_ptr<EventControlThread> eventControlThread,
                      const scheduler::RefreshRateConfigs& configs,
-                     ISchedulerCallback& schedulerCallback)
+                     ISchedulerCallback& schedulerCallback, bool useContentDetectionV2)
       : mPrimaryDispSync(std::move(primaryDispSync)),
         mEventControlThread(std::move(eventControlThread)),
         mSupportKernelTimer(false),
         mSchedulerCallback(schedulerCallback),
-        mRefreshRateConfigs(configs) {}
+        mRefreshRateConfigs(configs),
+        mUseContentDetectionV2(useContentDetectionV2) {}
 
 Scheduler::~Scheduler() {
     // Ensure the OneShotTimer threads are joined before we start destroying state.
@@ -375,12 +376,42 @@
 void Scheduler::registerLayer(Layer* layer) {
     if (!mLayerHistory) return;
 
-    const auto lowFps = mRefreshRateConfigs.getMinRefreshRate().fps;
-    const auto highFps = layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER
-            ? lowFps
-            : mRefreshRateConfigs.getMaxRefreshRate().fps;
+    if (!mUseContentDetectionV2) {
+        const auto lowFps = mRefreshRateConfigs.getMinRefreshRate().fps;
+        const auto highFps = layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER
+                ? lowFps
+                : mRefreshRateConfigs.getMaxRefreshRate().fps;
 
-    mLayerHistory->registerLayer(layer, lowFps, highFps);
+        mLayerHistory->registerLayer(layer, lowFps, highFps,
+                                     scheduler::LayerHistory::LayerVoteType::Heuristic);
+    } else {
+        if (layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER) {
+            mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().fps,
+                                         mRefreshRateConfigs.getMaxRefreshRate().fps,
+                                         scheduler::LayerHistory::LayerVoteType::Min);
+        } else if (layer->getWindowType() == InputWindowInfo::TYPE_STATUS_BAR) {
+            mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().fps,
+                                         mRefreshRateConfigs.getMaxRefreshRate().fps,
+                                         scheduler::LayerHistory::LayerVoteType::NoVote);
+        } else {
+            mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().fps,
+                                         mRefreshRateConfigs.getMaxRefreshRate().fps,
+                                         scheduler::LayerHistory::LayerVoteType::Heuristic);
+        }
+
+        // TODO(146935143): Simulate youtube app vote. This should be removed once youtube calls the
+        // API to set desired rate
+        {
+            const auto vote = property_get_int32("experimental.sf.force_youtube_vote", 0);
+            if (vote != 0 &&
+                layer->getName() ==
+                        "SurfaceView - "
+                        "com.google.android.youtube/"
+                        "com.google.android.apps.youtube.app.WatchWhileActivity#0") {
+                layer->setFrameRate(vote);
+            }
+        }
+    }
 }
 
 void Scheduler::recordLayerHistory(Layer* layer, nsecs_t presentTime) {
@@ -392,27 +423,27 @@
 void Scheduler::chooseRefreshRateForContent() {
     if (!mLayerHistory) return;
 
-    auto [refreshRate] = mLayerHistory->summarize(systemTime());
-    const uint32_t refreshRateRound = std::round(refreshRate);
+    ATRACE_CALL();
+
+    scheduler::LayerHistory::Summary summary = mLayerHistory->summarize(systemTime());
     HwcConfigIndexType newConfigId;
     {
         std::lock_guard<std::mutex> lock(mFeatureStateLock);
-        if (mFeatures.contentRefreshRate == refreshRateRound) {
+        if (mFeatures.contentRequirements == summary) {
             return;
         }
-        mFeatures.contentRefreshRate = refreshRateRound;
-        ATRACE_INT("ContentFPS", refreshRateRound);
-
+        mFeatures.contentRequirements = summary;
         mFeatures.contentDetection =
-                refreshRateRound > 0 ? ContentDetectionState::On : ContentDetectionState::Off;
+                !summary.empty() ? ContentDetectionState::On : ContentDetectionState::Off;
+
         newConfigId = calculateRefreshRateType();
         if (mFeatures.configId == newConfigId) {
             return;
         }
         mFeatures.configId = newConfigId;
-    };
-    auto newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
-    mSchedulerCallback.changeRefreshRate(newRefreshRate, ConfigEvent::Changed);
+        auto newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
+        mSchedulerCallback.changeRefreshRate(newRefreshRate, ConfigEvent::Changed);
+    }
 }
 
 void Scheduler::resetIdleTimer() {
@@ -422,16 +453,17 @@
 }
 
 void Scheduler::notifyTouchEvent() {
+    if (!mTouchTimer) return;
+
     // Touch event will boost the refresh rate to performance.
     // Clear Layer History to get fresh FPS detection.
     // NOTE: Instead of checking all the layers, we should be checking the layer
     // that is currently on top. b/142507166 will give us this capability.
-    if (mLayerHistory && !mLayerHistory->hasClientSpecifiedFrameRate()) {
+    std::lock_guard<std::mutex> lock(mFeatureStateLock);
+    if (mLayerHistory && !layerHistoryHasClientSpecifiedFrameRate()) {
         mLayerHistory->clear();
 
-        if (mTouchTimer) {
-            mTouchTimer->reset();
-        }
+        mTouchTimer->reset();
 
         if (mSupportKernelTimer && mIdleTimer) {
             mIdleTimer->reset();
@@ -530,6 +562,16 @@
     mSchedulerCallback.changeRefreshRate(newRefreshRate, event);
 }
 
+bool Scheduler::layerHistoryHasClientSpecifiedFrameRate() {
+    for (const auto& layer : mFeatures.contentRequirements) {
+        if (layer.vote == scheduler::RefreshRateConfigs::LayerVoteType::Explicit) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 HwcConfigIndexType Scheduler::calculateRefreshRateType() {
     if (!mRefreshRateConfigs.refreshRateSwitchingSupported()) {
         return mRefreshRateConfigs.getCurrentRefreshRate().configId;
@@ -538,7 +580,7 @@
     // If the layer history doesn't have the frame rate specified, use the old path. NOTE:
     // if we remove the kernel idle timer, and use our internal idle timer, this code will have to
     // be refactored.
-    if (!mLayerHistory->hasClientSpecifiedFrameRate()) {
+    if (!layerHistoryHasClientSpecifiedFrameRate()) {
         // If Display Power is not in normal operation we want to be in performance mode.
         // When coming back to normal mode, a grace period is given with DisplayPowerTimer
         if (!mFeatures.isDisplayPowerStateNormal ||
@@ -555,17 +597,26 @@
         if (mFeatures.idleTimer == TimerState::Expired) {
             return mRefreshRateConfigs.getMinRefreshRateByPolicy().configId;
         }
+    }
 
+    if (!mUseContentDetectionV2) {
         // If content detection is off we choose performance as we don't know the content fps
         if (mFeatures.contentDetection == ContentDetectionState::Off) {
             return mRefreshRateConfigs.getMaxRefreshRateByPolicy().configId;
         }
+
+        // Content detection is on, find the appropriate refresh rate with minimal error
+        return mRefreshRateConfigs.getRefreshRateForContent(mFeatures.contentRequirements).configId;
     }
 
     // Content detection is on, find the appropriate refresh rate with minimal error
-    return mRefreshRateConfigs
-            .getRefreshRateForContent(static_cast<float>(mFeatures.contentRefreshRate))
-            .configId;
+    if (mFeatures.contentDetection == ContentDetectionState::On) {
+        return mRefreshRateConfigs.getRefreshRateForContentV2(mFeatures.contentRequirements)
+                .configId;
+    }
+
+    // There are no signals for refresh rate, just leave it as is
+    return mRefreshRateConfigs.getCurrentRefreshRate().configId;
 }
 
 std::optional<HwcConfigIndexType> Scheduler::getPreferredConfigId() {
@@ -606,7 +657,10 @@
     }
 }
 
-} // namespace android
+void Scheduler::onPrimaryDisplayAreaChanged(uint32_t displayArea) {
+    if (mLayerHistory) {
+        mLayerHistory->setDisplayArea(displayArea);
+    }
+}
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+} // namespace android
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index c6430c3..01062f8 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -23,7 +23,11 @@
 #include <optional>
 #include <unordered_map>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
 #include <ui/GraphicTypes.h>
+#pragma clang diagnostic pop
 
 #include "EventControlThread.h"
 #include "EventThread.h"
@@ -58,7 +62,8 @@
     enum class TransactionStart { EARLY, NORMAL };
 
     Scheduler(impl::EventControlThread::SetVSyncEnabledFunction,
-              const scheduler::RefreshRateConfigs&, ISchedulerCallback& schedulerCallback);
+              const scheduler::RefreshRateConfigs&, ISchedulerCallback& schedulerCallback,
+              bool useContentDetectionV2);
 
     virtual ~Scheduler();
 
@@ -136,6 +141,9 @@
     // Notifies the scheduler when the display was refreshed
     void onDisplayRefreshed(nsecs_t timestamp);
 
+    // Notifies the scheduler when the display size has changed. Called from SF's main thread
+    void onPrimaryDisplayAreaChanged(uint32_t displayArea);
+
 private:
     friend class TestableScheduler;
 
@@ -147,7 +155,8 @@
 
     // Used by tests to inject mocks.
     Scheduler(std::unique_ptr<DispSync>, std::unique_ptr<EventControlThread>,
-              const scheduler::RefreshRateConfigs&, ISchedulerCallback& schedulerCallback);
+              const scheduler::RefreshRateConfigs&, ISchedulerCallback& schedulerCallback,
+              bool useContentDetectionV2);
 
     std::unique_ptr<VSyncSource> makePrimaryDispSyncSource(const char* name, nsecs_t phaseOffsetNs);
 
@@ -170,6 +179,8 @@
 
     HwcConfigIndexType calculateRefreshRateType() REQUIRES(mFeatureStateLock);
 
+    bool layerHistoryHasClientSpecifiedFrameRate() REQUIRES(mFeatureStateLock);
+
     // Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
     struct Connection {
         sp<EventThreadConnection> connection;
@@ -218,7 +229,7 @@
         TimerState displayPowerTimer = TimerState::Expired;
 
         std::optional<HwcConfigIndexType> configId;
-        uint32_t contentRefreshRate = 0;
+        scheduler::LayerHistory::Summary contentRequirements;
 
         bool isDisplayPowerStateNormal = true;
     } mFeatures GUARDED_BY(mFeatureStateLock);
@@ -229,6 +240,8 @@
     std::optional<HWC2::VsyncPeriodChangeTimeline> mLastVsyncPeriodChangeTimeline
             GUARDED_BY(mVsyncTimelineLock);
     static constexpr std::chrono::nanoseconds MAX_VSYNC_APPLIED_TIME = 200ms;
+
+    const bool mUseContentDetectionV2;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/SchedulerUtils.cpp b/services/surfaceflinger/Scheduler/SchedulerUtils.cpp
index 27120be..e8e0444 100644
--- a/services/surfaceflinger/Scheduler/SchedulerUtils.cpp
+++ b/services/surfaceflinger/Scheduler/SchedulerUtils.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include "SchedulerUtils.h"
 
 #include <cinttypes>
@@ -34,12 +30,10 @@
     }
 
     size_t n = v->size() / 2;
-    nth_element(v->begin(), v->begin() + n, v->end());
+    nth_element(v->begin(), v->begin() + static_cast<long>(n), v->end());
     return v->at(n);
 }
 
 } // namespace scheduler
 } // namespace android
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/Scheduler/SchedulerUtils.h b/services/surfaceflinger/Scheduler/SchedulerUtils.h
index d301b99..04a4cd1 100644
--- a/services/surfaceflinger/Scheduler/SchedulerUtils.h
+++ b/services/surfaceflinger/Scheduler/SchedulerUtils.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <utils/Timers.h>
 #include <cinttypes>
 #include <numeric>
 #include <unordered_map>
@@ -70,6 +71,18 @@
     return static_cast<int>(std::max_element(counts.begin(), counts.end(), compareCounts)->first);
 }
 
+template <class T, size_t N>
+constexpr size_t arrayLen(T (&)[N]) {
+    return N;
+}
+
+static constexpr size_t max64print = std::numeric_limits<nsecs_t>::digits10 + 1;
+
+template <typename T>
+static inline T round(float f) {
+    return static_cast<T>(std::round(f));
+}
+
 } // namespace android::scheduler
 
 namespace std {
diff --git a/services/surfaceflinger/Scheduler/Timer.cpp b/services/surfaceflinger/Scheduler/Timer.cpp
index fb4f315..8f81c2c 100644
--- a/services/surfaceflinger/Scheduler/Timer.cpp
+++ b/services/surfaceflinger/Scheduler/Timer.cpp
@@ -25,6 +25,7 @@
 #include <chrono>
 #include <cstdint>
 
+#include "SchedulerUtils.h"
 #include "Timer.h"
 
 namespace android::scheduler {
@@ -32,11 +33,6 @@
 static constexpr size_t kReadPipe = 0;
 static constexpr size_t kWritePipe = 1;
 
-template <class T, size_t N>
-constexpr size_t arrayLen(T (&)[N]) {
-    return N;
-}
-
 Timer::Timer()
       : mTimerFd(timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)),
         mEpollFd(epoll_create1(EPOLL_CLOEXEC)) {
@@ -66,11 +62,8 @@
     return systemTime(SYSTEM_TIME_MONOTONIC);
 }
 
-constexpr char const* timerTraceTag = "AlarmInNs";
 void Timer::alarmIn(std::function<void()> const& cb, nsecs_t fireIn) {
     std::lock_guard<decltype(mMutex)> lk(mMutex);
-    ATRACE_INT64(timerTraceTag, fireIn);
-
     using namespace std::literals;
     static constexpr int ns_per_s =
             std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
@@ -91,7 +84,6 @@
 
 void Timer::alarmCancel() {
     std::lock_guard<decltype(mMutex)> lk(mMutex);
-    ATRACE_INT64(timerTraceTag, 0);
 
     struct itimerspec old_timer;
     struct itimerspec new_timer {
@@ -137,7 +129,6 @@
 
     uint64_t iteration = 0;
     char const traceNamePrefix[] = "TimerIteration #";
-    static constexpr size_t max64print = std::numeric_limits<decltype(iteration)>::digits10;
     static constexpr size_t maxlen = arrayLen(traceNamePrefix) + max64print;
     std::array<char, maxlen> str_buffer;
     auto timing = true;
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index 2e5b6e9..d0f18ab 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -52,6 +52,13 @@
     return {mArmedInfo->mActualWakeupTime};
 }
 
+std::optional<nsecs_t> VSyncDispatchTimerQueueEntry::targetVsync() const {
+    if (!mArmedInfo) {
+        return {};
+    }
+    return {mArmedInfo->mActualVsyncTime};
+}
+
 ScheduleResult VSyncDispatchTimerQueueEntry::schedule(nsecs_t workDuration, nsecs_t earliestVsync,
                                                       VSyncTracker& tracker, nsecs_t now) {
     auto nextVsyncTime =
@@ -143,9 +150,21 @@
     rearmTimerSkippingUpdateFor(now, mCallbacks.end());
 }
 
+void VSyncDispatchTimerQueue::TraceBuffer::note(std::string_view name, nsecs_t alarmIn,
+                                                nsecs_t vsFor) {
+    if (ATRACE_ENABLED()) {
+        snprintf(str_buffer.data(), str_buffer.size(), "%.4s%s%" PRId64 "%s%" PRId64,
+                 name.substr(0, kMaxNamePrint).data(), kTraceNamePrefix, alarmIn,
+                 kTraceNameSeparator, vsFor);
+    }
+    ATRACE_NAME(str_buffer.data());
+}
+
 void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor(
         nsecs_t now, CallbackMap::iterator const& skipUpdateIt) {
     std::optional<nsecs_t> min;
+    std::optional<nsecs_t> targetVsync;
+    std::optional<std::string_view> nextWakeupName;
     for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
         auto& callback = it->second;
         if (!callback->wakeupTime()) {
@@ -157,13 +176,19 @@
         }
         auto const wakeupTime = *callback->wakeupTime();
         if (!min || (min && *min > wakeupTime)) {
+            nextWakeupName = callback->name();
             min = wakeupTime;
+            targetVsync = callback->targetVsync();
         }
     }
 
     if (min && (min < mIntendedWakeupTime)) {
+        if (targetVsync && nextWakeupName) {
+            mTraceBuffer.note(*nextWakeupName, *min - now, *targetVsync - now);
+        }
         setTimer(*min, now);
     } else {
+        ATRACE_NAME("cancel timer");
         cancelTimer();
     }
 }
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
index 087acc7..fd0a034 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <android-base/thread_annotations.h>
+#include <array>
 #include <functional>
 #include <memory>
 #include <mutex>
@@ -24,6 +25,7 @@
 #include <string_view>
 #include <unordered_map>
 
+#include "SchedulerUtils.h"
 #include "VSyncDispatch.h"
 
 namespace android::scheduler {
@@ -54,6 +56,8 @@
     // It will not update the wakeupTime.
     std::optional<nsecs_t> wakeupTime() const;
 
+    std::optional<nsecs_t> targetVsync() const;
+
     // This moves state from armed->disarmed.
     void disarm();
 
@@ -134,6 +138,17 @@
 
     CallbackMap mCallbacks GUARDED_BY(mMutex);
     nsecs_t mIntendedWakeupTime GUARDED_BY(mMutex) = kInvalidTime;
+
+    struct TraceBuffer {
+        static constexpr char const kTraceNamePrefix[] = "-alarm in:";
+        static constexpr char const kTraceNameSeparator[] = " for vs:";
+        static constexpr size_t kMaxNamePrint = 4;
+        static constexpr size_t kNumTsPrinted = 2;
+        static constexpr size_t maxlen = kMaxNamePrint + arrayLen(kTraceNamePrefix) +
+                arrayLen(kTraceNameSeparator) - 1 + (kNumTsPrinted * max64print);
+        std::array<char, maxlen> str_buffer;
+        void note(std::string_view name, nsecs_t in, nsecs_t vs);
+    } mTraceBuffer GUARDED_BY(mMutex);
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index e8c47a5..ac32633 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -23,28 +23,35 @@
 #include "VSyncPredictor.h"
 #include <android-base/logging.h>
 #include <cutils/compiler.h>
+#include <cutils/properties.h>
 #include <utils/Log.h>
 #include <utils/Trace.h>
 #include <algorithm>
 #include <chrono>
 #include <sstream>
-#include "SchedulerUtils.h"
 
 namespace android::scheduler {
-static auto constexpr kNeedsSamplesTag = "SamplesRequested";
+
 static auto constexpr kMaxPercent = 100u;
 
 VSyncPredictor::~VSyncPredictor() = default;
 
 VSyncPredictor::VSyncPredictor(nsecs_t idealPeriod, size_t historySize,
                                size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent)
-      : kHistorySize(historySize),
+      : mTraceOn(property_get_bool("debug.sf.vsp_trace", false)),
+        kHistorySize(historySize),
         kMinimumSamplesForPrediction(minimumSamplesForPrediction),
         kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
         mIdealPeriod(idealPeriod) {
     resetModel();
 }
 
+inline void VSyncPredictor::traceInt64If(const char* name, int64_t value) const {
+    if (CC_UNLIKELY(mTraceOn)) {
+        ATRACE_INT64(name, value);
+    }
+}
+
 inline size_t VSyncPredictor::next(int i) const {
     return (i + 1) % timestamps.size();
 }
@@ -68,7 +75,7 @@
     std::lock_guard<std::mutex> lk(mMutex);
 
     if (!validate(timestamp)) {
-        ALOGW("timestamp was too far off the last known timestamp");
+        ALOGV("timestamp was too far off the last known timestamp");
         return;
     }
 
@@ -114,6 +121,8 @@
     static constexpr int kScalingFactor = 10;
 
     for (auto i = 0u; i < timestamps.size(); i++) {
+        traceInt64If("VSP-ts", timestamps[i]);
+
         vsyncTS[i] = timestamps[i] - oldest_ts;
         ordinals[i] = ((vsyncTS[i] + (currentPeriod / 2)) / currentPeriod) * kScalingFactor;
     }
@@ -140,6 +149,9 @@
     nsecs_t const anticipatedPeriod = top / bottom * kScalingFactor;
     nsecs_t const intercept = meanTS - (anticipatedPeriod * meanOrdinal / kScalingFactor);
 
+    traceInt64If("VSP-period", anticipatedPeriod);
+    traceInt64If("VSP-intercept", intercept);
+
     it->second = {anticipatedPeriod, intercept};
 
     ALOGV("model update ts: %" PRId64 " slope: %" PRId64 " intercept: %" PRId64, timestamp,
@@ -152,6 +164,7 @@
     auto const [slope, intercept] = getVSyncPredictionModel(lk);
 
     if (timestamps.empty()) {
+        traceInt64If("VSP-mode", 1);
         auto const knownTimestamp = mKnownTimestamp ? *mKnownTimestamp : timePoint;
         auto const numPeriodsOut = ((timePoint - knownTimestamp) / mIdealPeriod) + 1;
         return knownTimestamp + numPeriodsOut * mIdealPeriod;
@@ -164,6 +177,10 @@
     auto const ordinalRequest = (timePoint - zeroPoint + slope) / slope;
     auto const prediction = (ordinalRequest * slope) + intercept + oldest;
 
+    traceInt64If("VSP-mode", 0);
+    traceInt64If("VSP-timePoint", timePoint);
+    traceInt64If("VSP-prediction", prediction);
+
     auto const printer = [&, slope = slope, intercept = intercept] {
         std::stringstream str;
         str << "prediction made from: " << timePoint << "prediction: " << prediction << " (+"
@@ -227,7 +244,7 @@
         }
     }
 
-    ATRACE_INT(kNeedsSamplesTag, needsMoreSamples);
+    ATRACE_INT("VSP-moreSamples", needsMoreSamples);
     return needsMoreSamples;
 }
 
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 41e5469..e366555 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -20,6 +20,7 @@
 #include <mutex>
 #include <unordered_map>
 #include <vector>
+#include "SchedulerUtils.h"
 #include "VSyncTracker.h"
 
 namespace android::scheduler {
@@ -64,6 +65,9 @@
     VSyncPredictor& operator=(VSyncPredictor const&) = delete;
     void clearTimestamps() REQUIRES(mMutex);
 
+    inline void traceInt64If(const char* name, int64_t value) const;
+    bool const mTraceOn;
+
     size_t const kHistorySize;
     size_t const kMinimumSamplesForPrediction;
     size_t const kOutlierTolerancePercent;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 6d18922..62d47e1 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -353,6 +353,9 @@
     property_get("debug.sf.luma_sampling", value, "1");
     mLumaSampling = atoi(value);
 
+    property_get("debug.sf.disable_client_composition_cache", value, "0");
+    mDisableClientCompositionCache = atoi(value);
+
     // We should be reading 'persist.sys.sf.color_saturation' here
     // but since /data may be encrypted, we need to wait until after vold
     // comes online to attempt to read the property. The property is
@@ -540,6 +543,11 @@
             const auto& performanceRefreshRate = mRefreshRateConfigs->getMaxRefreshRateByPolicy();
             changeRefreshRateLocked(performanceRefreshRate, Scheduler::ConfigEvent::None);
         }
+
+        if (property_get_bool("sf.debug.show_refresh_rate_overlay", false)) {
+            mRefreshRateOverlay = std::make_unique<RefreshRateOverlay>(*this);
+            mRefreshRateOverlay->changeRefreshRate(mRefreshRateConfigs->getCurrentRefreshRate());
+        }
     }));
 }
 
@@ -1779,7 +1787,13 @@
 
             // Layers need to get updated (in the previous line) before we can use them for
             // choosing the refresh rate.
-            mScheduler->chooseRefreshRateForContent();
+            // Hold mStateLock as chooseRefreshRateForContent promotes wp<Layer> to sp<Layer>
+            // and may eventually call to ~Layer() if it holds the last reference
+            {
+                Mutex::Autolock _l(mStateLock);
+                mScheduler->chooseRefreshRateForContent();
+            }
+
             if (performSetActiveConfig()) {
                 break;
             }
@@ -1893,13 +1907,19 @@
     mHadClientComposition =
             std::any_of(mDisplays.cbegin(), mDisplays.cend(), [](const auto& tokenDisplayPair) {
                 auto& displayDevice = tokenDisplayPair.second;
-                return displayDevice->getCompositionDisplay()->getState().usesClientComposition;
+                return displayDevice->getCompositionDisplay()->getState().usesClientComposition &&
+                        !displayDevice->getCompositionDisplay()->getState().reusedClientComposition;
             });
     mHadDeviceComposition =
             std::any_of(mDisplays.cbegin(), mDisplays.cend(), [](const auto& tokenDisplayPair) {
                 auto& displayDevice = tokenDisplayPair.second;
                 return displayDevice->getCompositionDisplay()->getState().usesDeviceComposition;
             });
+    mReusedClientComposition =
+            std::any_of(mDisplays.cbegin(), mDisplays.cend(), [](const auto& tokenDisplayPair) {
+                auto& displayDevice = tokenDisplayPair.second;
+                return displayDevice->getCompositionDisplay()->getState().reusedClientComposition;
+            });
 
     mVSyncModulator->onRefreshed(mHadClientComposition);
 
@@ -2079,6 +2099,10 @@
         mTimeStats->incrementClientCompositionFrames();
     }
 
+    if (mReusedClientComposition) {
+        mTimeStats->incrementClientCompositionReusedFrames();
+    }
+
     mTimeStats->setPresentFenceGlobal(presentFenceTime);
 
     if (displayDevice && getHwComposer().isConnected(*displayDevice->getId()) &&
@@ -2378,6 +2402,9 @@
                     }
                     if (state.width != draw[i].width || state.height != draw[i].height) {
                         display->setDisplaySize(state.width, state.height);
+                        if (display->isPrimary()) {
+                            mScheduler->onPrimaryDisplayAreaChanged(state.width * state.height);
+                        }
                     }
                 }
             }
@@ -2450,6 +2477,12 @@
                         LOG_ALWAYS_FATAL_IF(!displayId);
                         dispatchDisplayHotplugEvent(displayId->value, true);
                     }
+
+                    const auto displayDevice = mDisplays[displayToken];
+                    if (displayDevice->isPrimary()) {
+                        mScheduler->onPrimaryDisplayAreaChanged(displayDevice->getWidth() *
+                                                                displayDevice->getHeight());
+                    }
                 }
             }
         }
@@ -2647,8 +2680,12 @@
 }
 
 void SurfaceFlinger::changeRefreshRate(const RefreshRate& refreshRate,
-                                       Scheduler::ConfigEvent event) {
-    Mutex::Autolock lock(mStateLock);
+                                       Scheduler::ConfigEvent event) NO_THREAD_SAFETY_ANALYSIS {
+    // If this is called from the main thread mStateLock must be locked before
+    // Currently the only way to call this function from the main thread is from
+    // Sheduler::chooseRefreshRateForContent
+
+    ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId);
     changeRefreshRateLocked(refreshRate, event);
 }
 
@@ -5408,7 +5445,7 @@
     const auto& displayViewport = renderArea.getDisplayViewport();
 
     renderengine::DisplaySettings clientCompositionDisplay;
-    std::vector<renderengine::LayerSettings> clientCompositionLayers;
+    std::vector<compositionengine::LayerFE::LayerSettings> clientCompositionLayers;
 
     // assume that bounds are never offset, and that they are the same as the
     // buffer bounds.
@@ -5469,7 +5506,7 @@
 
     const float alpha = RenderArea::getCaptureFillValue(renderArea.getCaptureFill());
 
-    renderengine::LayerSettings fillLayer;
+    compositionengine::LayerFE::LayerSettings fillLayer;
     fillLayer.source.buffer.buffer = nullptr;
     fillLayer.source.solidColor = half3(0.0, 0.0, 0.0);
     fillLayer.geometry.boundaries = FloatRect(0.0, 0.0, 1.0, 1.0);
@@ -5490,7 +5527,7 @@
         };
         auto result = layer->prepareClientComposition(targetSettings);
         if (result) {
-            std::optional<renderengine::LayerSettings> shadowLayer =
+            std::optional<compositionengine::LayerFE::LayerSettings> shadowLayer =
                     layer->prepareShadowClientComposition(*result, displayViewport,
                                                           clientCompositionDisplay.outputDataspace);
             if (shadowLayer) {
@@ -5500,13 +5537,20 @@
         }
     });
 
+    std::vector<const renderengine::LayerSettings*> clientCompositionLayerPointers;
+    clientCompositionLayers.reserve(clientCompositionLayers.size());
+    std::transform(clientCompositionLayers.begin(), clientCompositionLayers.end(),
+                   std::back_inserter(clientCompositionLayerPointers),
+                   [](compositionengine::LayerFE::LayerSettings& settings)
+                           -> renderengine::LayerSettings* { return &settings; });
+
     clientCompositionDisplay.clearRegion = clearRegion;
     // Use an empty fence for the buffer fence, since we just created the buffer so
     // there is no need for synchronization with the GPU.
     base::unique_fd bufferFence;
     base::unique_fd drawFence;
     getRenderEngine().useProtectedContext(false);
-    getRenderEngine().drawLayers(clientCompositionDisplay, clientCompositionLayers, buffer,
+    getRenderEngine().drawLayers(clientCompositionDisplay, clientCompositionLayerPointers, buffer,
                                  /*useFramebufferCache=*/false, std::move(bufferFence), &drawFence);
 
     *outSyncFd = drawFence.release();
@@ -5600,11 +5644,10 @@
             repaintEverythingForHWC();
         }
 
-        auto configId = HwcConfigIndexType(defaultConfig);
-        display->setActiveConfig(configId);
+        display->setActiveConfig(defaultConfig);
         const nsecs_t vsyncPeriod =
-                mRefreshRateConfigs->getRefreshRateFromConfigId(configId).vsyncPeriod;
-        mScheduler->onConfigChanged(mAppConnectionHandle, display->getId()->value, configId,
+                getHwComposer().getConfigs(*displayId)[defaultConfig.value()]->getVsyncPeriod();
+        mScheduler->onConfigChanged(mAppConnectionHandle, display->getId()->value, defaultConfig,
                                     vsyncPeriod);
         return NO_ERROR;
     }
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 7f7d8da..f8980a5 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -323,6 +323,10 @@
     // Inherit from ClientCache::ErasedRecipient
     void bufferErased(const client_cache_t& clientCacheId) override;
 
+    // If set, disables reusing client composition buffers. This can be set by
+    // debug.sf.disable_client_composition_cache
+    bool mDisableClientCompositionCache = false;
+
 private:
     friend class BufferLayer;
     friend class BufferQueueLayer;
@@ -987,6 +991,10 @@
     // Note that it is possible for a frame to be composed via both client and device
     // composition, for example in the case of overlays.
     bool mHadDeviceComposition = false;
+    // True if in the previous frame, the client composition was skipped by reusing the buffer
+    // used in a previous composition. This can happed if the client composition requests
+    // did not change.
+    bool mReusedClientComposition = false;
 
     enum class BootStage {
         BOOTLOADER,
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
index e12d31a..45889a5 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
@@ -75,7 +75,9 @@
 std::unique_ptr<Scheduler> DefaultFactory::createScheduler(
         SetVSyncEnabled setVSyncEnabled, const scheduler::RefreshRateConfigs& configs,
         ISchedulerCallback& schedulerCallback) {
-    return std::make_unique<Scheduler>(std::move(setVSyncEnabled), configs, schedulerCallback);
+    return std::make_unique<Scheduler>(std::move(setVSyncEnabled), configs, schedulerCallback,
+                                       property_get_bool("debug.sf.use_content_detection_v2",
+                                                         false));
 }
 
 std::unique_ptr<SurfaceInterceptor> DefaultFactory::createSurfaceInterceptor(
diff --git a/services/surfaceflinger/SurfaceTracing.cpp b/services/surfaceflinger/SurfaceTracing.cpp
index 6fd4e46..eb5c7de 100644
--- a/services/surfaceflinger/SurfaceTracing.cpp
+++ b/services/surfaceflinger/SurfaceTracing.cpp
@@ -188,8 +188,11 @@
         ALOGE("Could not save the proto file! Permission denied");
         mLastErr = PERMISSION_DENIED;
     }
-    if (!android::base::WriteStringToFile(output, kDefaultFileName, S_IRWXU | S_IRGRP, getuid(),
-                                          getgid(), true)) {
+
+    // -rw-r--r--
+    const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+    if (!android::base::WriteStringToFile(output, kDefaultFileName, mode, getuid(), getgid(),
+                                          true)) {
         ALOGE("Could not save the proto file! There are missing fields");
         mLastErr = PERMISSION_DENIED;
     }
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index 7c824ec..12c98da 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -248,6 +248,15 @@
     mTimeStats.clientCompositionFrames++;
 }
 
+void TimeStats::incrementClientCompositionReusedFrames() {
+    if (!mEnabled.load()) return;
+
+    ATRACE_CALL();
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    mTimeStats.clientCompositionReusedFrames++;
+}
+
 static int32_t msBetween(nsecs_t start, nsecs_t end) {
     int64_t delta = std::chrono::duration_cast<std::chrono::milliseconds>(
                             std::chrono::nanoseconds(end - start))
@@ -762,6 +771,7 @@
     mTimeStats.totalFrames = 0;
     mTimeStats.missedFrames = 0;
     mTimeStats.clientCompositionFrames = 0;
+    mTimeStats.clientCompositionReusedFrames = 0;
     mTimeStats.displayOnTime = 0;
     mTimeStats.presentToPresent.hist.clear();
     mTimeStats.frameDuration.hist.clear();
diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h
index cf1c3c6..71f06af 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.h
+++ b/services/surfaceflinger/TimeStats/TimeStats.h
@@ -51,6 +51,7 @@
     virtual void incrementTotalFrames() = 0;
     virtual void incrementMissedFrames() = 0;
     virtual void incrementClientCompositionFrames() = 0;
+    virtual void incrementClientCompositionReusedFrames() = 0;
 
     // Records the start and end times for a frame.
     // The start time is the same as the beginning of a SurfaceFlinger
@@ -189,6 +190,7 @@
     void incrementTotalFrames() override;
     void incrementMissedFrames() override;
     void incrementClientCompositionFrames() override;
+    void incrementClientCompositionReusedFrames() override;
 
     void recordFrameDuration(nsecs_t startTime, nsecs_t endTime) override;
     void recordRenderEngineDuration(nsecs_t startTime, nsecs_t endTime) override;
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
index 7e43880..0ba90e2 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
+++ b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
@@ -102,6 +102,7 @@
     StringAppendF(&result, "totalFrames = %d\n", totalFrames);
     StringAppendF(&result, "missedFrames = %d\n", missedFrames);
     StringAppendF(&result, "clientCompositionFrames = %d\n", clientCompositionFrames);
+    StringAppendF(&result, "clientCompositionReusedFrames = %d\n", clientCompositionReusedFrames);
     StringAppendF(&result, "displayOnTime = %" PRId64 " ms\n", displayOnTime);
     StringAppendF(&result, "displayConfigStats is as below:\n");
     for (const auto& [fps, duration] : refreshRateStats) {
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
index bd97ecc..702c50e 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
+++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
@@ -59,6 +59,7 @@
         int32_t totalFrames = 0;
         int32_t missedFrames = 0;
         int32_t clientCompositionFrames = 0;
+        int32_t clientCompositionReusedFrames = 0;
         int64_t displayOnTime = 0;
         Histogram presentToPresent;
         Histogram frameDuration;
diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp b/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp
index 1d3fed8..5cbf2ef 100644
--- a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp
+++ b/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp
@@ -789,6 +789,18 @@
     return V2_4::Error::NONE;
 }
 
+V2_4::Error FakeComposerClient::setLayerGenericMetadata(Display, Layer, const std::string&, bool,
+                                                        const std::vector<uint8_t>&) {
+    ALOGV("setLayerGenericMetadata");
+    return V2_4::Error::UNSUPPORTED;
+}
+
+V2_4::Error FakeComposerClient::getLayerGenericMetadataKeys(
+        std::vector<IComposerClient::LayerGenericMetadataKey>*) {
+    ALOGV("getLayerGenericMetadataKeys");
+    return V2_4::Error::UNSUPPORTED;
+}
+
 //////////////////////////////////////////////////////////////////
 
 void FakeComposerClient::requestVSync(uint64_t vsyncTime) {
diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.h b/services/surfaceflinger/tests/fakehwc/FakeComposerClient.h
index a524850..5240b72 100644
--- a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.h
+++ b/services/surfaceflinger/tests/fakehwc/FakeComposerClient.h
@@ -257,6 +257,10 @@
             uint32_t* outDisplayRequestMask, std::vector<Layer>* outRequestedLayers,
             std::vector<uint32_t>* outRequestMasks,
             IComposerClient::ClientTargetProperty* outClientTargetProperty) override;
+    V2_4::Error setLayerGenericMetadata(Display display, Layer layer, const std::string& key,
+                                        bool mandatory, const std::vector<uint8_t>& value) override;
+    V2_4::Error getLayerGenericMetadataKeys(
+            std::vector<IComposerClient::LayerGenericMetadataKey>* outKeys) override;
 
     void setClient(ComposerClient* client);
 
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 1eaf2dd..d046f76 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -44,6 +44,7 @@
         "EventThreadTest.cpp",
         "OneShotTimerTest.cpp",
         "LayerHistoryTest.cpp",
+        "LayerHistoryTestV2.cpp",
         "LayerMetadataTest.cpp",
         "PhaseOffsetsTest.cpp",
         "SchedulerTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 8a762d4..98cc023 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -328,8 +328,9 @@
         EXPECT_CALL(*test->mRenderEngine, drawLayers)
                 .WillRepeatedly(
                         [](const renderengine::DisplaySettings& displaySettings,
-                           const std::vector<renderengine::LayerSettings>&, ANativeWindowBuffer*,
-                           const bool, base::unique_fd&&, base::unique_fd*) -> status_t {
+                           const std::vector<const renderengine::LayerSettings*>&,
+                           ANativeWindowBuffer*, const bool, base::unique_fd&&,
+                           base::unique_fd*) -> status_t {
                             EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                             EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                                       displaySettings.physicalDisplay);
@@ -377,8 +378,9 @@
         EXPECT_CALL(*test->mRenderEngine, drawLayers)
                 .WillRepeatedly(
                         [](const renderengine::DisplaySettings& displaySettings,
-                           const std::vector<renderengine::LayerSettings>&, ANativeWindowBuffer*,
-                           const bool, base::unique_fd&&, base::unique_fd*) -> status_t {
+                           const std::vector<const renderengine::LayerSettings*>&,
+                           ANativeWindowBuffer*, const bool, base::unique_fd&&,
+                           base::unique_fd*) -> status_t {
                             EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                             EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                                       displaySettings.physicalDisplay);
@@ -627,7 +629,7 @@
     static void setupREBufferCompositionCommonCallExpectations(CompositionTest* test) {
         EXPECT_CALL(*test->mRenderEngine, drawLayers)
                 .WillOnce([](const renderengine::DisplaySettings& displaySettings,
-                             const std::vector<renderengine::LayerSettings>& layerSettings,
+                             const std::vector<const renderengine::LayerSettings*>& layerSettings,
                              ANativeWindowBuffer*, const bool, base::unique_fd&&,
                              base::unique_fd*) -> status_t {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
@@ -643,16 +645,16 @@
                                          "verification lambda";
                         return NO_ERROR;
                     }
-                    renderengine::LayerSettings layer = layerSettings.back();
-                    EXPECT_THAT(layer.source.buffer.buffer, Not(IsNull()));
-                    EXPECT_THAT(layer.source.buffer.fence, Not(IsNull()));
-                    EXPECT_EQ(DEFAULT_TEXTURE_ID, layer.source.buffer.textureName);
-                    EXPECT_EQ(false, layer.source.buffer.isY410BT2020);
-                    EXPECT_EQ(true, layer.source.buffer.usePremultipliedAlpha);
-                    EXPECT_EQ(false, layer.source.buffer.isOpaque);
-                    EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius);
-                    EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace);
-                    EXPECT_EQ(LayerProperties::COLOR[3], layer.alpha);
+                    const renderengine::LayerSettings* layer = layerSettings.back();
+                    EXPECT_THAT(layer->source.buffer.buffer, Not(IsNull()));
+                    EXPECT_THAT(layer->source.buffer.fence, Not(IsNull()));
+                    EXPECT_EQ(DEFAULT_TEXTURE_ID, layer->source.buffer.textureName);
+                    EXPECT_EQ(false, layer->source.buffer.isY410BT2020);
+                    EXPECT_EQ(true, layer->source.buffer.usePremultipliedAlpha);
+                    EXPECT_EQ(false, layer->source.buffer.isOpaque);
+                    EXPECT_EQ(0.0, layer->geometry.roundedCornersRadius);
+                    EXPECT_EQ(ui::Dataspace::UNKNOWN, layer->sourceDataspace);
+                    EXPECT_EQ(LayerProperties::COLOR[3], layer->alpha);
                     return NO_ERROR;
                 });
     }
@@ -676,7 +678,7 @@
     static void setupREColorCompositionCallExpectations(CompositionTest* test) {
         EXPECT_CALL(*test->mRenderEngine, drawLayers)
                 .WillOnce([](const renderengine::DisplaySettings& displaySettings,
-                             const std::vector<renderengine::LayerSettings>& layerSettings,
+                             const std::vector<const renderengine::LayerSettings*>& layerSettings,
                              ANativeWindowBuffer*, const bool, base::unique_fd&&,
                              base::unique_fd*) -> status_t {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
@@ -692,14 +694,14 @@
                                    "setupREColorCompositionCallExpectations verification lambda";
                         return NO_ERROR;
                     }
-                    renderengine::LayerSettings layer = layerSettings.back();
-                    EXPECT_THAT(layer.source.buffer.buffer, IsNull());
+                    const renderengine::LayerSettings* layer = layerSettings.back();
+                    EXPECT_THAT(layer->source.buffer.buffer, IsNull());
                     EXPECT_EQ(half3(LayerProperties::COLOR[0], LayerProperties::COLOR[1],
                                     LayerProperties::COLOR[2]),
-                              layer.source.solidColor);
-                    EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius);
-                    EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace);
-                    EXPECT_EQ(LayerProperties::COLOR[3], layer.alpha);
+                              layer->source.solidColor);
+                    EXPECT_EQ(0.0, layer->geometry.roundedCornersRadius);
+                    EXPECT_EQ(ui::Dataspace::UNKNOWN, layer->sourceDataspace);
+                    EXPECT_EQ(LayerProperties::COLOR[3], layer->alpha);
                     return NO_ERROR;
                 });
     }
@@ -752,7 +754,7 @@
     static void setupInsecureREBufferCompositionCommonCallExpectations(CompositionTest* test) {
         EXPECT_CALL(*test->mRenderEngine, drawLayers)
                 .WillOnce([](const renderengine::DisplaySettings& displaySettings,
-                             const std::vector<renderengine::LayerSettings>& layerSettings,
+                             const std::vector<const renderengine::LayerSettings*>& layerSettings,
                              ANativeWindowBuffer*, const bool, base::unique_fd&&,
                              base::unique_fd*) -> status_t {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
@@ -768,12 +770,12 @@
                                          "verification lambda";
                         return NO_ERROR;
                     }
-                    renderengine::LayerSettings layer = layerSettings.back();
-                    EXPECT_THAT(layer.source.buffer.buffer, IsNull());
-                    EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), layer.source.solidColor);
-                    EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius);
-                    EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace);
-                    EXPECT_EQ(1.0f, layer.alpha);
+                    const renderengine::LayerSettings* layer = layerSettings.back();
+                    EXPECT_THAT(layer->source.buffer.buffer, IsNull());
+                    EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), layer->source.solidColor);
+                    EXPECT_EQ(0.0, layer->geometry.roundedCornersRadius);
+                    EXPECT_EQ(ui::Dataspace::UNKNOWN, layer->sourceDataspace);
+                    EXPECT_EQ(1.0f, layer->alpha);
                     return NO_ERROR;
                 });
     }
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index ca51cb6..9ca1b70 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -73,7 +73,7 @@
                                                                         HI_FPS_PERIOD},
                                 },
                                 HwcConfigIndexType(0)};
-    TestableScheduler* const mScheduler{new TestableScheduler(mConfigs)};
+    TestableScheduler* const mScheduler{new TestableScheduler(mConfigs, false)};
     TestableSurfaceFlinger mFlinger;
 
     const nsecs_t mTime = systemTime();
@@ -85,53 +85,36 @@
     const auto layer = createLayer();
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameSelectionPriority()).WillRepeatedly(Return(1));
+    EXPECT_CALL(*layer, getFrameRate()).WillRepeatedly(Return(std::nullopt));
 
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
 
-    // 0 FPS is returned if no layers are active.
-    EXPECT_FLOAT_EQ(0, history().summarize(mTime).maxRefreshRate);
+    // no layers are returned if no layers are active.
+    ASSERT_TRUE(history().summarize(mTime).empty());
     EXPECT_EQ(0, activeLayerCount());
 
-    // 0 FPS is returned if active layers have insufficient history.
+    // no layers are returned if active layers have insufficient history.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
         history().record(layer.get(), 0, mTime);
-        EXPECT_FLOAT_EQ(0, history().summarize(mTime).maxRefreshRate);
+        ASSERT_TRUE(history().summarize(mTime).empty());
         EXPECT_EQ(1, activeLayerCount());
     }
 
     // High FPS is returned once enough history has been recorded.
     for (int i = 0; i < 10; i++) {
         history().record(layer.get(), 0, mTime);
-        EXPECT_FLOAT_EQ(HI_FPS, history().summarize(mTime).maxRefreshRate);
+        ASSERT_EQ(1, history().summarize(mTime).size());
+        EXPECT_FLOAT_EQ(HI_FPS, history().summarize(mTime)[0].desiredRefreshRate);
         EXPECT_EQ(1, activeLayerCount());
     }
 }
 
-TEST_F(LayerHistoryTest, oneHDRLayer) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameSelectionPriority()).WillRepeatedly(Return(1));
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    history().record(layer.get(), 0, mTime);
-    auto summary = history().summarize(mTime);
-    EXPECT_FLOAT_EQ(0, summary.maxRefreshRate);
-    EXPECT_EQ(1, activeLayerCount());
-
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
-
-    summary = history().summarize(mTime);
-    EXPECT_FLOAT_EQ(0, summary.maxRefreshRate);
-    EXPECT_EQ(0, activeLayerCount());
-}
-
 TEST_F(LayerHistoryTest, explicitTimestamp) {
     const auto layer = createLayer();
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameSelectionPriority()).WillRepeatedly(Return(1));
+    EXPECT_CALL(*layer, getFrameRate()).WillRepeatedly(Return(std::nullopt));
 
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
@@ -142,7 +125,8 @@
         time += LO_FPS_PERIOD;
     }
 
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(mTime).maxRefreshRate);
+    ASSERT_EQ(1, history().summarize(mTime).size());
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(mTime)[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 }
@@ -154,13 +138,15 @@
 
     EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer1, getFrameSelectionPriority()).WillRepeatedly(Return(1));
+    EXPECT_CALL(*layer1, getFrameRate()).WillRepeatedly(Return(std::nullopt));
 
     EXPECT_CALL(*layer2, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer2, getFrameSelectionPriority()).WillRepeatedly(Return(1));
+    EXPECT_CALL(*layer2, getFrameRate()).WillRepeatedly(Return(std::nullopt));
 
     EXPECT_CALL(*layer3, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer3, getFrameSelectionPriority()).WillRepeatedly(Return(1));
-
+    EXPECT_CALL(*layer3, getFrameRate()).WillRepeatedly(Return(std::nullopt));
     nsecs_t time = mTime;
 
     EXPECT_EQ(3, layerCount());
@@ -173,7 +159,8 @@
         time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
     }
 
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time).maxRefreshRate);
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
 
@@ -186,7 +173,9 @@
     // layer1 is still active but infrequent.
     history().record(layer1.get(), time, time);
 
-    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time).maxRefreshRate);
+    ASSERT_EQ(2, history().summarize(time).size());
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -197,7 +186,8 @@
         time += LO_FPS_PERIOD;
     }
 
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time).maxRefreshRate);
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -213,19 +203,24 @@
         time += HI_FPS_PERIOD;
     }
 
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time).maxRefreshRate);
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
 
     // layer3 becomes recently active.
     history().record(layer3.get(), time, time);
-    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time).maxRefreshRate);
+    ASSERT_EQ(2, history().summarize(time).size());
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
 
     // layer1 expires.
     layer1.clear();
-    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time).maxRefreshRate);
+    ASSERT_EQ(2, history().summarize(time).size());
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
     EXPECT_EQ(2, layerCount());
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
@@ -237,13 +232,14 @@
         time += LO_FPS_PERIOD;
     }
 
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time).maxRefreshRate);
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
     // layer2 expires.
     layer2.clear();
-    EXPECT_FLOAT_EQ(0, history().summarize(time).maxRefreshRate);
+    ASSERT_TRUE(history().summarize(time).empty());
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
@@ -254,14 +250,15 @@
         time += HI_FPS_PERIOD;
     }
 
-    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time).maxRefreshRate);
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[0].desiredRefreshRate);
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
     // layer3 expires.
     layer3.clear();
-    EXPECT_FLOAT_EQ(0, history().summarize(time).maxRefreshRate);
+    ASSERT_TRUE(history().summarize(time).empty());
     EXPECT_EQ(0, layerCount());
     EXPECT_EQ(0, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
new file mode 100644
index 0000000..11ace05
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
@@ -0,0 +1,418 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LayerHistoryTestV2"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+#include "Scheduler/LayerHistory.h"
+#include "Scheduler/LayerInfoV2.h"
+#include "TestableScheduler.h"
+#include "TestableSurfaceFlinger.h"
+#include "mock/MockLayer.h"
+
+using testing::_;
+using testing::Return;
+
+namespace android::scheduler {
+
+class LayerHistoryTestV2 : public testing::Test {
+protected:
+    static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfoV2::HISTORY_SIZE;
+    static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS = LayerInfoV2::MAX_FREQUENT_LAYER_PERIOD_NS;
+
+    static constexpr float LO_FPS = 30.f;
+    static constexpr auto LO_FPS_PERIOD = static_cast<nsecs_t>(1e9f / LO_FPS);
+
+    static constexpr float HI_FPS = 90.f;
+    static constexpr auto HI_FPS_PERIOD = static_cast<nsecs_t>(1e9f / HI_FPS);
+
+    LayerHistoryTestV2() { mFlinger.resetScheduler(mScheduler); }
+
+    impl::LayerHistoryV2& history() { return *mScheduler->mutableLayerHistoryV2(); }
+    const impl::LayerHistoryV2& history() const { return *mScheduler->mutableLayerHistoryV2(); }
+
+    size_t layerCount() const { return mScheduler->layerHistorySize(); }
+    size_t activeLayerCount() const NO_THREAD_SAFETY_ANALYSIS { return history().mActiveLayersEnd; }
+
+    auto frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+        const auto& infos = history().mLayerInfos;
+        return std::count_if(infos.begin(),
+                             infos.begin() + static_cast<long>(history().mActiveLayersEnd),
+                             [now](const auto& pair) { return pair.second->isFrequent(now); });
+    }
+
+    void setLayerInfoVote(Layer* layer,
+                          LayerHistory::LayerVoteType vote) NO_THREAD_SAFETY_ANALYSIS {
+        for (auto& [weak, info] : history().mLayerInfos) {
+            if (auto strong = weak.promote(); strong && strong.get() == layer) {
+                info->setDefaultLayerVote(vote);
+                info->setLayerVote(vote, 0);
+                return;
+            }
+        }
+    }
+
+    auto createLayer() { return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger())); }
+
+    RefreshRateConfigs mConfigs{true,
+                                {
+                                        RefreshRateConfigs::InputConfig{HwcConfigIndexType(0),
+                                                                        HwcConfigGroupType(0),
+                                                                        LO_FPS_PERIOD},
+                                        RefreshRateConfigs::InputConfig{HwcConfigIndexType(1),
+                                                                        HwcConfigGroupType(0),
+                                                                        HI_FPS_PERIOD},
+                                },
+                                HwcConfigIndexType(0)};
+    TestableScheduler* const mScheduler{new TestableScheduler(mConfigs, true)};
+    TestableSurfaceFlinger mFlinger;
+
+    const nsecs_t mTime = systemTime();
+};
+
+namespace {
+
+TEST_F(LayerHistoryTestV2, oneLayer) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRate()).WillRepeatedly(Return(std::nullopt));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    // No layers returned if no layers are active.
+    EXPECT_TRUE(history().summarize(mTime).empty());
+    EXPECT_EQ(0, activeLayerCount());
+
+    // Max returned if active layers have insufficient history.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
+        history().record(layer.get(), 0, mTime);
+        ASSERT_EQ(1, history().summarize(mTime).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(mTime)[0].vote);
+        EXPECT_EQ(1, activeLayerCount());
+    }
+
+    // Max is returned since we have enough history but there is no timestamp votes.
+    for (int i = 0; i < 10; i++) {
+        history().record(layer.get(), 0, mTime);
+        ASSERT_EQ(1, history().summarize(mTime).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(mTime)[0].vote);
+        EXPECT_EQ(1, activeLayerCount());
+    }
+}
+
+TEST_F(LayerHistoryTestV2, oneInvisibleLayer) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRate()).WillRepeatedly(Return(std::nullopt));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    history().record(layer.get(), 0, mTime);
+    auto summary = history().summarize(mTime);
+    ASSERT_EQ(1, history().summarize(mTime).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(mTime)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
+
+    summary = history().summarize(mTime);
+    EXPECT_TRUE(history().summarize(mTime).empty());
+    EXPECT_EQ(0, activeLayerCount());
+}
+
+TEST_F(LayerHistoryTestV2, explicitTimestamp) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRate()).WillRepeatedly(Return(std::nullopt));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = mTime;
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer.get(), time, time);
+        time += LO_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTestV2, oneLayerNoVote) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRate()).WillRepeatedly(Return(std::nullopt));
+
+    setLayerInfoVote(layer.get(), LayerHistory::LayerVoteType::NoVote);
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = mTime;
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer.get(), time, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_TRUE(history().summarize(time).empty());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_TRUE(history().summarize(time).empty());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTestV2, oneLayerMinVote) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRate()).WillRepeatedly(Return(std::nullopt));
+
+    setLayerInfoVote(layer.get(), LayerHistory::LayerVoteType::Min);
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = mTime;
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer.get(), time, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_TRUE(history().summarize(time).empty());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTestV2, oneLayerMaxVote) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRate()).WillRepeatedly(Return(std::nullopt));
+
+    setLayerInfoVote(layer.get(), LayerHistory::LayerVoteType::Max);
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = mTime;
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer.get(), time, time);
+        time += LO_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_TRUE(history().summarize(time).empty());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTestV2, oneLayerExplicitVote) {
+    auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRate()).WillRepeatedly(Return(73.4f));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = mTime;
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer.get(), time, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Explicit, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive
+    setLayerInfoVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_TRUE(history().summarize(time).empty());
+    // TODO: activeLayerCount() should be 0 but it is 1 since getFrameRate() returns a value > 0
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTestV2, multipleLayers) {
+    auto layer1 = createLayer();
+    auto layer2 = createLayer();
+    auto layer3 = createLayer();
+
+    EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer1, getFrameRate()).WillRepeatedly(Return(std::nullopt));
+
+    EXPECT_CALL(*layer2, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer2, getFrameRate()).WillRepeatedly(Return(std::nullopt));
+
+    EXPECT_CALL(*layer3, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer3, getFrameRate()).WillRepeatedly(Return(std::nullopt));
+
+    nsecs_t time = mTime;
+
+    EXPECT_EQ(3, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    // layer1 is active but infrequent.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer1.get(), time, time);
+        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    // layer2 is frequent and has high refresh rate.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer2.get(), time, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    // layer1 is still active but infrequent.
+    history().record(layer1.get(), time, time);
+
+    ASSERT_EQ(2, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[1].vote);
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
+    EXPECT_EQ(2, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer1 is no longer active.
+    // layer2 is frequent and has low refresh rate.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer2.get(), time, time);
+        time += LO_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer2 still has low refresh rate.
+    // layer3 has high refresh rate but not enough history.
+    constexpr int RATIO = LO_FPS_PERIOD / HI_FPS_PERIOD;
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
+        if (i % RATIO == 0) {
+            history().record(layer2.get(), time, time);
+        }
+
+        history().record(layer3.get(), time, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(2, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[1].vote);
+    EXPECT_EQ(2, activeLayerCount());
+    EXPECT_EQ(2, frequentLayerCount(time));
+
+    // layer3 becomes recently active.
+    history().record(layer3.get(), time, time);
+    ASSERT_EQ(2, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[1].vote);
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
+    EXPECT_EQ(2, activeLayerCount());
+    EXPECT_EQ(2, frequentLayerCount(time));
+
+    // layer1 expires.
+    layer1.clear();
+    ASSERT_EQ(2, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[1].vote);
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
+    EXPECT_EQ(2, layerCount());
+    EXPECT_EQ(2, activeLayerCount());
+    EXPECT_EQ(2, frequentLayerCount(time));
+
+    // layer2 still has low refresh rate.
+    // layer3 becomes inactive.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer2.get(), time, time);
+        time += LO_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer2 expires.
+    layer2.clear();
+    EXPECT_TRUE(history().summarize(time).empty());
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    // layer3 becomes active and has high refresh rate.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer3.get(), time, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer3 expires.
+    layer3.clear();
+    EXPECT_TRUE(history().summarize(time).empty());
+    EXPECT_EQ(0, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+} // namespace
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 86aa8fb..78009b8 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -31,16 +31,24 @@
 namespace scheduler {
 
 using RefreshRate = RefreshRateConfigs::RefreshRate;
+using LayerVoteType = RefreshRateConfigs::LayerVoteType;
+using LayerRequirement = RefreshRateConfigs::LayerRequirement;
 
 class RefreshRateConfigsTest : public testing::Test {
 protected:
     static inline const HwcConfigIndexType HWC_CONFIG_ID_60 = HwcConfigIndexType(0);
-    static inline const HwcConfigIndexType HWC_CONFIG_ID_90 = HwcConfigIndexType(1);
+    static inline const HwcConfigIndexType HWC_CONFIG_ID_72 = HwcConfigIndexType(1);
+    static inline const HwcConfigIndexType HWC_CONFIG_ID_90 = HwcConfigIndexType(2);
+    static inline const HwcConfigIndexType HWC_CONFIG_ID_120 = HwcConfigIndexType(3);
+    static inline const HwcConfigIndexType HWC_CONFIG_ID_30 = HwcConfigIndexType(4);
     static inline const HwcConfigGroupType HWC_GROUP_ID_0 = HwcConfigGroupType(0);
     static inline const HwcConfigGroupType HWC_GROUP_ID_1 = HwcConfigGroupType(1);
-    static constexpr int64_t VSYNC_60 = 16666667;
+    static constexpr auto VSYNC_30 = static_cast<int64_t>(1e9f / 30);
+    static constexpr auto VSYNC_60 = static_cast<int64_t>(1e9f / 60);
+    static constexpr auto VSYNC_72 = static_cast<int64_t>(1e9f / 72);
+    static constexpr auto VSYNC_90 = static_cast<int64_t>(1e9f / 90);
+    static constexpr auto VSYNC_120 = static_cast<int64_t>(1e9f / 120);
     static constexpr int64_t VSYNC_60_POINT_4 = 16666665;
-    static constexpr int64_t VSYNC_90 = 11111111;
 
     RefreshRateConfigsTest();
     ~RefreshRateConfigsTest();
@@ -212,31 +220,476 @@
     RefreshRate expected60Config = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
     RefreshRate expected90Config = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_0, "90fps", 90};
 
-    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(90.0f));
-    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(60.0f));
-    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(45.0f));
-    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(30.0f));
-    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(24.0f));
+    const auto makeLayerRequirements = [](float refreshRate) -> std::vector<LayerRequirement> {
+        return {{"testLayer", LayerVoteType::Heuristic, refreshRate, 1.0f}};
+    };
+
+    EXPECT_EQ(expected90Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
+    EXPECT_EQ(expected90Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
 
     ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 60, 60, nullptr), 0);
-    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(90.0f));
-    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(60.0f));
-    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(45.0f));
-    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(30.0f));
-    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(24.0f));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
 
     ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_90, 90, 90, nullptr), 0);
-    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(90.0f));
-    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(60.0f));
-    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(45.0f));
-    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(30.0f));
-    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(24.0f));
+    EXPECT_EQ(expected90Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
+    EXPECT_EQ(expected90Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
+    EXPECT_EQ(expected90Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
+    EXPECT_EQ(expected90Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
+    EXPECT_EQ(expected90Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
     ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 0, 120, nullptr), 0);
-    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(90.0f));
-    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(60.0f));
-    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(45.0f));
-    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(30.0f));
-    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(24.0f));
+    EXPECT_EQ(expected90Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
+    EXPECT_EQ(expected90Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContentV2_60_90) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_0, VSYNC_90}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+
+    RefreshRate expected60Config = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    RefreshRate expected90Config = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_0, "90fps", 90};
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 90.0f;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 45.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 30.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 24.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 60, 60, nullptr), 0);
+
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 90.0f;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 45.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 30.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 24.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_90, 90, 90, nullptr), 0);
+
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 90.0f;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 45.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 30.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 24.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 0, 120, nullptr), 0);
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 90.0f;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 45.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 30.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 24.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContentV2_60_72_90) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_72, HWC_GROUP_ID_0, VSYNC_72},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_0, VSYNC_90}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+
+    RefreshRate expected60Config = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    RefreshRate expected72Config = {HWC_CONFIG_ID_72, VSYNC_72, HWC_GROUP_ID_0, "72fps", 70};
+    RefreshRate expected90Config = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_0, "90fps", 90};
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 90.0f;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 45.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 30.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 24.0f;
+    EXPECT_EQ(expected72Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContentV2_30_60_72_90_120) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_30, HWC_GROUP_ID_0, VSYNC_30},
+             {HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_72, HWC_GROUP_ID_0, VSYNC_72},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_0, VSYNC_90},
+             {HWC_CONFIG_ID_120, HWC_GROUP_ID_0, VSYNC_120}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+
+    RefreshRate expected30Config = {HWC_CONFIG_ID_30, VSYNC_30, HWC_GROUP_ID_0, "30fps", 30};
+    RefreshRate expected60Config = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    RefreshRate expected72Config = {HWC_CONFIG_ID_72, VSYNC_72, HWC_GROUP_ID_0, "72fps", 70};
+    RefreshRate expected90Config = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_0, "90fps", 90};
+    RefreshRate expected120Config = {HWC_CONFIG_ID_120, VSYNC_120, HWC_GROUP_ID_0, "120fps", 120};
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
+                                                LayerRequirement{.weight = 1.0f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.desiredRefreshRate = 24.0f;
+    lr1.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 60.0f;
+    lr2.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected120Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr1.desiredRefreshRate = 24.0f;
+    lr1.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 48.0f;
+    lr2.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected72Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr1.desiredRefreshRate = 24.0f;
+    lr1.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 48.0f;
+    lr2.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected72Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContentV2_30_60) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_30, HWC_GROUP_ID_0, VSYNC_30}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+
+    RefreshRate expected60Config = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    RefreshRate expected30Config = {HWC_CONFIG_ID_30, VSYNC_30, HWC_GROUP_ID_0, "30fps", 30};
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(expected30Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 90.0f;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 45.0f;
+    EXPECT_EQ(expected30Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 30.0f;
+    EXPECT_EQ(expected30Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 24.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContentV2_30_60_72_90) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_30, HWC_GROUP_ID_0, VSYNC_30},
+             {HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_72, HWC_GROUP_ID_0, VSYNC_72},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_0, VSYNC_90}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+
+    RefreshRate expected30Config = {HWC_CONFIG_ID_30, VSYNC_30, HWC_GROUP_ID_0, "30fps", 30};
+    RefreshRate expected60Config = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    RefreshRate expected72Config = {HWC_CONFIG_ID_72, VSYNC_72, HWC_GROUP_ID_0, "72fps", 70};
+    RefreshRate expected90Config = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_0, "90fps", 90};
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(expected30Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 90.0f;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 45.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 30.0f;
+    EXPECT_EQ(expected30Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 24.0f;
+    EXPECT_EQ(expected72Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContentV2_PriorityTest) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_30, HWC_GROUP_ID_0, VSYNC_30},
+             {HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_0, VSYNC_90}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+
+    RefreshRate expected30Config = {HWC_CONFIG_ID_30, VSYNC_30, HWC_GROUP_ID_0, "30fps", 30};
+    RefreshRate expected60Config = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    RefreshRate expected90Config = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_0, "90fps", 90};
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
+                                                LayerRequirement{.weight = 1.0f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.vote = LayerVoteType::Min;
+    lr2.vote = LayerVoteType::Max;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr1.vote = LayerVoteType::Min;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 24.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr1.vote = LayerVoteType::Min;
+    lr2.vote = LayerVoteType::Explicit;
+    lr2.desiredRefreshRate = 24.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr1.vote = LayerVoteType::Max;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr1.vote = LayerVoteType::Max;
+    lr2.vote = LayerVoteType::Explicit;
+    lr2.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.desiredRefreshRate = 15.0f;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 45.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.desiredRefreshRate = 30.0f;
+    lr2.vote = LayerVoteType::Explicit;
+    lr2.desiredRefreshRate = 45.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContentV2_24FpsVideo) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_0, VSYNC_90}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+
+    RefreshRate expected30Config = {HWC_CONFIG_ID_30, VSYNC_30, HWC_GROUP_ID_0, "30fps", 30};
+    RefreshRate expected60Config = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    RefreshRate expected90Config = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_0, "90fps", 90};
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::Explicit;
+    for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
+        lr.desiredRefreshRate = fps;
+        const auto& refreshRate = refreshRateConfigs->getRefreshRateForContentV2(layers);
+        printf("%.2fHz chooses %s\n", fps, refreshRate.name.c_str());
+        EXPECT_EQ(expected60Config, refreshRate);
+    }
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContent_Explicit) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_0, VSYNC_90}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+
+    RefreshRate expected60Config = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    RefreshRate expected90Config = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_0, "90fps", 90};
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
+                                                LayerRequirement{.weight = 1.0f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.desiredRefreshRate = 60.0f;
+    lr2.vote = LayerVoteType::Explicit;
+    lr2.desiredRefreshRate = 90.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(layers));
+
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.desiredRefreshRate = 90.0f;
+    lr2.vote = LayerVoteType::Explicit;
+    lr2.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(layers));
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContentV2_Explicit) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_0, VSYNC_90}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+
+    RefreshRate expected60Config = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    RefreshRate expected90Config = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_0, "90fps", 90};
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
+                                                LayerRequirement{.weight = 1.0f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.desiredRefreshRate = 60.0f;
+    lr2.vote = LayerVoteType::Explicit;
+    lr2.desiredRefreshRate = 90.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.desiredRefreshRate = 90.0f;
+    lr2.vote = LayerVoteType::Explicit;
+    lr2.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, testInPolicy) {
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index b1ecf4d..82a00ee 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -76,7 +76,7 @@
             scheduler::RefreshRateConfigs>(/*refreshRateSwitching=*/false, configs,
                                            /*currentConfig=*/HwcConfigIndexType(0));
 
-    mScheduler = std::make_unique<TestableScheduler>(*mRefreshRateConfigs);
+    mScheduler = std::make_unique<TestableScheduler>(*mRefreshRateConfigs, false);
 
     auto eventThread = std::make_unique<mock::EventThread>();
     mEventThread = eventThread.get();
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index a67c24c..52da34b 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -28,16 +28,25 @@
 
 class TestableScheduler : public Scheduler, private ISchedulerCallback {
 public:
-    explicit TestableScheduler(const scheduler::RefreshRateConfigs& configs)
-          : Scheduler([](bool) {}, configs, *this) {
-        mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
+    TestableScheduler(const scheduler::RefreshRateConfigs& configs, bool useContentDetectionV2)
+          : Scheduler([](bool) {}, configs, *this, useContentDetectionV2) {
+        if (mUseContentDetectionV2) {
+            mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>();
+        } else {
+            mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
+        }
     }
 
     TestableScheduler(std::unique_ptr<DispSync> primaryDispSync,
                       std::unique_ptr<EventControlThread> eventControlThread,
-                      const scheduler::RefreshRateConfigs& configs)
-          : Scheduler(std::move(primaryDispSync), std::move(eventControlThread), configs, *this) {
-        mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
+                      const scheduler::RefreshRateConfigs& configs, bool useContentDetectionV2)
+          : Scheduler(std::move(primaryDispSync), std::move(eventControlThread), configs, *this,
+                      useContentDetectionV2) {
+        if (mUseContentDetectionV2) {
+            mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>();
+        } else {
+            mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
+        }
     }
 
     // Used to inject mock event thread.
@@ -46,7 +55,13 @@
     }
 
     size_t layerHistorySize() const NO_THREAD_SAFETY_ANALYSIS {
-        return static_cast<scheduler::impl::LayerHistory*>(mLayerHistory.get())->mLayerInfos.size();
+        if (mUseContentDetectionV2) {
+            return static_cast<scheduler::impl::LayerHistoryV2*>(mLayerHistory.get())
+                    ->mLayerInfos.size();
+        } else {
+            return static_cast<scheduler::impl::LayerHistory*>(mLayerHistory.get())
+                    ->mLayerInfos.size();
+        }
     }
 
     /* ------------------------------------------------------------------------
@@ -60,6 +75,9 @@
     auto mutableLayerHistory() {
         return static_cast<scheduler::impl::LayerHistory*>(mLayerHistory.get());
     }
+    auto mutableLayerHistoryV2() {
+        return static_cast<scheduler::impl::LayerHistoryV2*>(mLayerHistory.get());
+    }
 
     ~TestableScheduler() {
         // All these pointer and container clears help ensure that GMock does
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 8ddb872..2491533 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -198,7 +198,8 @@
     void setupScheduler(std::unique_ptr<DispSync> primaryDispSync,
                         std::unique_ptr<EventControlThread> eventControlThread,
                         std::unique_ptr<EventThread> appEventThread,
-                        std::unique_ptr<EventThread> sfEventThread) {
+                        std::unique_ptr<EventThread> sfEventThread,
+                        bool useContentDetectionV2 = false) {
         std::vector<scheduler::RefreshRateConfigs::InputConfig> configs{
                 {{HwcConfigIndexType(0), HwcConfigGroupType(0), 16666667}}};
         mFlinger->mRefreshRateConfigs = std::make_unique<
@@ -213,7 +214,7 @@
 
         mScheduler =
                 new TestableScheduler(std::move(primaryDispSync), std::move(eventControlThread),
-                                      *mFlinger->mRefreshRateConfigs);
+                                      *mFlinger->mRefreshRateConfigs, useContentDetectionV2);
 
         mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
         mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
@@ -443,7 +444,7 @@
         static constexpr int32_t DEFAULT_REFRESH_RATE = 16'666'666;
         static constexpr int32_t DEFAULT_CONFIG_GROUP = 7;
         static constexpr int32_t DEFAULT_DPI = 320;
-        static constexpr int32_t DEFAULT_ACTIVE_CONFIG = 0;
+        static constexpr hwc2_config_t DEFAULT_ACTIVE_CONFIG = 0;
         static constexpr int32_t DEFAULT_POWER_MODE = 2;
 
         FakeHwcDisplayInjector(DisplayId displayId, HWC2::DisplayType hwcDisplayType,
@@ -465,7 +466,7 @@
             return *this;
         }
 
-        auto& setRefreshRate(uint32_t refreshRate) {
+        auto& setRefreshRate(int32_t refreshRate) {
             mRefreshRate = refreshRate;
             return *this;
         }
@@ -480,7 +481,7 @@
             return *this;
         }
 
-        auto& setActiveConfig(int32_t config) {
+        auto& setActiveConfig(hwc2_config_t config) {
             mActiveConfig = config;
             return *this;
         }
@@ -513,7 +514,7 @@
             config.setDpiX(mDpiX);
             config.setDpiY(mDpiY);
             config.setConfigGroup(mConfigGroup);
-            display->mutableConfigs().emplace(mActiveConfig, config.build());
+            display->mutableConfigs().emplace(static_cast<int32_t>(mActiveConfig), config.build());
             display->mutableIsConnected() = true;
             display->setPowerMode(static_cast<HWC2::PowerMode>(mPowerMode));
 
@@ -534,11 +535,11 @@
         hwc2_display_t mHwcDisplayId = DEFAULT_HWC_DISPLAY_ID;
         int32_t mWidth = DEFAULT_WIDTH;
         int32_t mHeight = DEFAULT_HEIGHT;
-        uint32_t mRefreshRate = DEFAULT_REFRESH_RATE;
+        int32_t mRefreshRate = DEFAULT_REFRESH_RATE;
         int32_t mDpiX = DEFAULT_DPI;
         int32_t mConfigGroup = DEFAULT_CONFIG_GROUP;
         int32_t mDpiY = DEFAULT_DPI;
-        int32_t mActiveConfig = DEFAULT_ACTIVE_CONFIG;
+        hwc2_config_t mActiveConfig = DEFAULT_ACTIVE_CONFIG;
         int32_t mPowerMode = DEFAULT_POWER_MODE;
         const std::unordered_set<HWC2::Capability>* mCapabilities = nullptr;
     };
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
index 5044626..68e6697 100644
--- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -44,6 +44,7 @@
 using testing::_;
 using testing::AnyNumber;
 using testing::Contains;
+using testing::HasSubstr;
 using testing::InSequence;
 using testing::SizeIs;
 using testing::StrEq;
@@ -305,6 +306,21 @@
     EXPECT_EQ(CLIENT_COMPOSITION_FRAMES, globalProto.client_composition_frames());
 }
 
+TEST_F(TimeStatsTest, canIncreaseClientCompositionReusedFrames) {
+    // this stat is not in the proto so verify by checking the string dump
+    constexpr size_t CLIENT_COMPOSITION_REUSED_FRAMES = 2;
+
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+    for (size_t i = 0; i < CLIENT_COMPOSITION_REUSED_FRAMES; i++) {
+        ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementClientCompositionReusedFrames());
+    }
+
+    const std::string result(inputCommand(InputCommand::DUMP_ALL, FMT_STRING));
+    const std::string expectedResult =
+            "clientCompositionReusedFrames = " + std::to_string(CLIENT_COMPOSITION_REUSED_FRAMES);
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+}
+
 TEST_F(TimeStatsTest, canInsertGlobalPresentToPresent) {
     EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
 
@@ -649,6 +665,16 @@
     EXPECT_EQ(0, globalProto.stats_size());
 }
 
+TEST_F(TimeStatsTest, canClearClientCompositionSkippedFrames) {
+    // this stat is not in the proto so verify by checking the string dump
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+    ASSERT_NO_FATAL_FAILURE(mTimeStats->incrementClientCompositionReusedFrames());
+    EXPECT_TRUE(inputCommand(InputCommand::CLEAR, FMT_STRING).empty());
+
+    const std::string result(inputCommand(InputCommand::DUMP_ALL, FMT_STRING));
+    EXPECT_THAT(result, HasSubstr("clientCompositionReusedFrames = 0"));
+}
+
 TEST_F(TimeStatsTest, canDumpWithMaxLayers) {
     EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
 
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 0f9dd5b..5cbba81 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -134,6 +134,11 @@
     MOCK_METHOD2(getSupportedContentTypes,
                  V2_4::Error(Display, std::vector<IComposerClient::ContentType>*));
     MOCK_METHOD2(setContentType, V2_4::Error(Display, IComposerClient::ContentType));
+    MOCK_METHOD5(setLayerGenericMetadata,
+                 V2_4::Error(Display, Layer, const std::string&, bool,
+                             const std::vector<uint8_t>&));
+    MOCK_METHOD1(getLayerGenericMetadataKeys,
+                 V2_4::Error(std::vector<IComposerClient::LayerGenericMetadataKey>*));
 };
 
 } // namespace mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
index 1fd0e61..494e73d 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockLayer.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
@@ -31,6 +31,7 @@
     MOCK_METHOD0(getFrameSelectionPriority, int32_t());
     MOCK_CONST_METHOD0(isVisible, bool());
     MOCK_METHOD0(createClone, sp<Layer>());
+    MOCK_CONST_METHOD0(getFrameRate, std::optional<float>());
 };
 
 } // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
index 9ada5ef..d1df08c 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
@@ -35,6 +35,7 @@
     MOCK_METHOD0(incrementTotalFrames, void());
     MOCK_METHOD0(incrementMissedFrames, void());
     MOCK_METHOD0(incrementClientCompositionFrames, void());
+    MOCK_METHOD0(incrementClientCompositionReusedFrames, void());
     MOCK_METHOD2(recordFrameDuration, void(nsecs_t, nsecs_t));
     MOCK_METHOD2(recordRenderEngineDuration, void(nsecs_t, nsecs_t));
     MOCK_METHOD2(recordRenderEngineDuration, void(nsecs_t, const std::shared_ptr<FenceTime>&));