Merge "Return unique_ptr from readFromParcel" into main
diff --git a/data/etc/aosp_excluded_hardware.xml b/data/etc/aosp_excluded_hardware.xml
index c12f435..013f278 100644
--- a/data/etc/aosp_excluded_hardware.xml
+++ b/data/etc/aosp_excluded_hardware.xml
@@ -18,5 +18,4 @@
     <!-- This should be used to exclude this feature from aosp targets. As aosp configurations
     may or may not have a valid location provider -->
     <unavailable-feature name="android.hardware.location.network" />
-    <unavailable-feature name="android.software.device_id_attestation" />
 </permissions>
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index fd2b27f..620c23c 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -359,6 +359,38 @@
 }
 
 cc_library_static {
+    name: "libbinder_rpc_no_blob",
+    vendor_available: true,
+    defaults: [
+        "libbinder_common_defaults",
+        "libbinder_android_defaults",
+        "libbinder_kernel_defaults",
+    ],
+    cflags: [
+        "-DBINDER_DISABLE_BLOB",
+    ],
+    visibility: [
+        ":__subpackages__",
+    ],
+}
+
+cc_library_static {
+    name: "libbinder_rpc_no_native_handle",
+    vendor_available: true,
+    defaults: [
+        "libbinder_common_defaults",
+        "libbinder_android_defaults",
+        "libbinder_kernel_defaults",
+    ],
+    cflags: [
+        "-DBINDER_DISABLE_NATIVE_HANDLE",
+    ],
+    visibility: [
+        ":__subpackages__",
+    ],
+}
+
+cc_library_static {
     name: "libbinder_rpc_single_threaded",
     defaults: [
         "libbinder_common_defaults",
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index b78b0b3..a3ff7d2 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -40,7 +40,9 @@
 #include <binder/TextOutput.h>
 
 #include <android-base/scopeguard.h>
+#ifndef BINDER_DISABLE_BLOB
 #include <cutils/ashmem.h>
+#endif
 #include <utils/Flattenable.h>
 #include <utils/Log.h>
 #include <utils/String16.h>
@@ -1475,6 +1477,7 @@
     return writeParcelable(*parcelable);
 }
 
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
 status_t Parcel::writeNativeHandle(const native_handle* handle)
 {
     if (!handle || handle->version != sizeof(native_handle))
@@ -1497,6 +1500,7 @@
     err = write(handle->data + handle->numFds, sizeof(int)*handle->numInts);
     return err;
 }
+#endif
 
 status_t Parcel::writeFileDescriptor(int fd, bool takeOwnership) {
     if (auto* rpcFields = maybeRpcFields()) {
@@ -1589,6 +1593,12 @@
 
 status_t Parcel::writeBlob(size_t len, bool mutableCopy, WritableBlob* outBlob)
 {
+#ifdef BINDER_DISABLE_BLOB
+    (void)len;
+    (void)mutableCopy;
+    (void)outBlob;
+    return INVALID_OPERATION;
+#else
     if (len > INT32_MAX) {
         // don't accept size_t values which may have come from an
         // inadvertent conversion from a negative int.
@@ -1640,6 +1650,7 @@
     }
     ::close(fd);
     return status;
+#endif
 }
 
 status_t Parcel::writeDupImmutableBlobFileDescriptor(int fd)
@@ -2271,6 +2282,7 @@
     return status.exceptionCode();
 }
 
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
 native_handle* Parcel::readNativeHandle() const
 {
     int numFds, numInts;
@@ -2303,6 +2315,7 @@
     }
     return h;
 }
+#endif
 
 int Parcel::readFileDescriptor() const {
     if (const auto* rpcFields = maybeRpcFields()) {
@@ -2423,6 +2436,11 @@
 
 status_t Parcel::readBlob(size_t len, ReadableBlob* outBlob) const
 {
+#ifdef BINDER_DISABLE_BLOB
+    (void)len;
+    (void)outBlob;
+    return INVALID_OPERATION;
+#else
     int32_t blobType;
     status_t status = readInt32(&blobType);
     if (status) return status;
@@ -2456,6 +2474,7 @@
 
     outBlob->init(fd, ptr, len, isMutable);
     return NO_ERROR;
+#endif
 }
 
 status_t Parcel::read(FlattenableHelperInterface& val) const
@@ -3204,6 +3223,7 @@
     }
 
     size_t openAshmemSize = 0;
+#ifndef BINDER_DISABLE_BLOB
     for (size_t i = 0; i < kernelFields->mObjectsSize; i++) {
         const flat_binder_object* flat =
                 reinterpret_cast<const flat_binder_object*>(mData + kernelFields->mObjects[i]);
@@ -3218,6 +3238,7 @@
             }
         }
     }
+#endif
     return openAshmemSize;
 }
 #endif // BINDER_WITH_KERNEL_IPC
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index b94267c..98d12bb 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -26,7 +26,9 @@
 #include <vector>
 
 #include <android-base/unique_fd.h>
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
 #include <cutils/native_handle.h>
+#endif
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
 #include <utils/String16.h>
@@ -324,11 +326,13 @@
     template<typename T>
     status_t            writeVectorSize(const std::unique_ptr<std::vector<T>>& val) __attribute__((deprecated("use std::optional version instead")));
 
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
     // Place a native_handle into the parcel (the native_handle's file-
     // descriptors are dup'ed, so it is safe to delete the native_handle
     // when this function returns).
     // Doesn't take ownership of the native_handle.
     status_t            writeNativeHandle(const native_handle* handle);
+#endif
 
     // Place a file descriptor into the parcel.  The given fd must remain
     // valid for the lifetime of the parcel.
@@ -559,13 +563,14 @@
     // response headers rather than doing it by hand.
     int32_t             readExceptionCode() const;
 
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
     // Retrieve native_handle from the parcel. This returns a copy of the
     // parcel's native_handle (the caller takes ownership). The caller
-    // must free the native_handle with native_handle_close() and 
+    // must free the native_handle with native_handle_close() and
     // native_handle_delete().
     native_handle*     readNativeHandle() const;
+#endif
 
-    
     // Retrieve a file descriptor from the parcel.  This returns the raw fd
     // in the parcel, which you do not own -- use dup() to get your own copy.
     int                 readFileDescriptor() const;
diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h
index 89fd7a3..316a79c 100644
--- a/libs/binder/ndk/include_platform/android/binder_manager.h
+++ b/libs/binder/ndk/include_platform/android/binder_manager.h
@@ -120,7 +120,7 @@
 
 /**
  * Gets a binder object with this specific instance name. Efficiently waits for the service.
- * If the service is not declared, it will wait indefinitely. Requires the threadpool
+ * If the service is not ever registered, it will wait indefinitely. Requires the threadpool
  * to be started in the service.
  * This also implicitly calls AIBinder_incStrong (so the caller of this function is responsible
  * for calling AIBinder_decStrong).
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index 8841fe6..ed870b6 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -144,6 +144,7 @@
 #[doc(hidden)]
 pub mod unstable_api {
     pub use crate::binder::AsNative;
+    pub use crate::error::status_result;
     pub use crate::proxy::unstable_api::new_spibinder;
     pub use crate::sys::AIBinder;
     pub use crate::sys::AParcel;
diff --git a/libs/binder/rust/src/parcel.rs b/libs/binder/rust/src/parcel.rs
index 3c615ed..f9f135d 100644
--- a/libs/binder/rust/src/parcel.rs
+++ b/libs/binder/rust/src/parcel.rs
@@ -54,6 +54,10 @@
 
 /// Safety: This type guarantees that it owns the AParcel and that all access to
 /// the AParcel happens through the Parcel, so it is ok to send across threads.
+///
+/// It would not be okay to implement Sync, because that would allow you to call
+/// the reading methods from several threads in parallel, which would be a data
+/// race on the cursor position inside the AParcel.
 unsafe impl Send for Parcel {}
 
 /// Container for a message (data and object references) that can be sent
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 5884dbe..1340ea1 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
+#ifndef __ANDROID_VENDOR__
+// only used on NDK tests outside of vendor
 #include <aidl/IBinderRpcTest.h>
+#endif
 #include <android-base/stringprintf.h>
 
 #include <chrono>
diff --git a/libs/binder/tests/parcel_fuzzer/random_parcel.cpp b/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
index f0beed2..f367b41 100644
--- a/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
+++ b/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
@@ -66,6 +66,11 @@
                 },
                 // write FD
                 [&]() {
+                    // b/296516864 - Limit number of objects written to a parcel.
+                    if (p->objectsCount() > 100) {
+                        return;
+                    }
+
                     if (options->extraFds.size() > 0 && provider.ConsumeBool()) {
                         const base::unique_fd& fd = options->extraFds.at(
                                 provider.ConsumeIntegralInRange<size_t>(0,
@@ -82,7 +87,6 @@
                         CHECK(OK ==
                               p->writeFileDescriptor(fds.begin()->release(),
                                                      true /*takeOwnership*/));
-
                         options->extraFds.insert(options->extraFds.end(),
                                                  std::make_move_iterator(fds.begin() + 1),
                                                  std::make_move_iterator(fds.end()));
@@ -90,6 +94,11 @@
                 },
                 // write binder
                 [&]() {
+                    // b/296516864 - Limit number of objects written to a parcel.
+                    if (p->objectsCount() > 100) {
+                        return;
+                    }
+
                     sp<IBinder> binder;
                     if (options->extraBinders.size() > 0 && provider.ConsumeBool()) {
                         binder = options->extraBinders.at(
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 05e2ddf..a351811 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -2604,7 +2604,7 @@
         outMode.resolution.height = mode.resolution.height;
         outMode.xDpi = mode.xDpi;
         outMode.yDpi = mode.yDpi;
-        outMode.refreshRate = mode.refreshRate;
+        outMode.peakRefreshRate = mode.peakRefreshRate;
         outMode.vsyncRate = mode.vsyncRate;
         outMode.appVsyncOffset = mode.appVsyncOffset;
         outMode.sfVsyncOffset = mode.sfVsyncOffset;
diff --git a/libs/gui/aidl/android/gui/DisplayMode.aidl b/libs/gui/aidl/android/gui/DisplayMode.aidl
index b057653..f605177 100644
--- a/libs/gui/aidl/android/gui/DisplayMode.aidl
+++ b/libs/gui/aidl/android/gui/DisplayMode.aidl
@@ -30,7 +30,7 @@
     int[] supportedHdrTypes;
 
     // Some modes have peak refresh rate lower than the panel vsync rate.
-    float refreshRate = 0.0f;
+    float peakRefreshRate = 0.0f;
     float vsyncRate = 0.0f;
     long appVsyncOffset = 0;
     long sfVsyncOffset = 0;
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index 662e9fe..d4b8dbe 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -394,7 +394,7 @@
         // After a new buffer is queued, SurfaceFlinger is notified and will
         // latch the new buffer on next vsync.  Let's heuristically wait for 3
         // vsyncs.
-        mBufferPostDelay = static_cast<int32_t>(1e6 / mode.refreshRate) * 3;
+        mBufferPostDelay = static_cast<int32_t>(1e6 / mode.peakRefreshRate) * 3;
     }
 
     void TearDown() {
diff --git a/libs/nativedisplay/ADisplay.cpp b/libs/nativedisplay/ADisplay.cpp
index bf0805b..e3be3bc 100644
--- a/libs/nativedisplay/ADisplay.cpp
+++ b/libs/nativedisplay/ADisplay.cpp
@@ -155,7 +155,7 @@
             const ui::DisplayMode& mode = modes[j];
             modesPerDisplay[i].emplace_back(
                     DisplayConfigImpl{static_cast<size_t>(mode.id), mode.resolution.getWidth(),
-                                      mode.resolution.getHeight(), mode.refreshRate,
+                                      mode.resolution.getHeight(), mode.peakRefreshRate,
                                       mode.sfVsyncOffset, mode.appVsyncOffset});
         }
     }
diff --git a/libs/nativewindow/rust/Android.bp b/libs/nativewindow/rust/Android.bp
index dc1575c..90d0a8e 100644
--- a/libs/nativewindow/rust/Android.bp
+++ b/libs/nativewindow/rust/Android.bp
@@ -19,7 +19,7 @@
 }
 
 rust_bindgen {
-    name: "libnativewindow_bindgen",
+    name: "libnativewindow_bindgen_internal",
     crate_name: "nativewindow_bindgen",
     wrapper_src: "sys/nativewindow_bindings.h",
     source_stem: "bindings",
@@ -28,13 +28,21 @@
         "--bitfield-enum=AHardwareBuffer_UsageFlags",
 
         "--allowlist-file=.*/nativewindow/include/.*\\.h",
+        "--blocklist-type",
+        "AParcel",
+        "--raw-line",
+        "use binder::unstable_api::AParcel;",
 
         "--with-derive-eq",
         "--with-derive-partialeq",
     ],
     shared_libs: [
+        "libbinder_ndk",
         "libnativewindow",
     ],
+    rustlibs: [
+        "libbinder_rs",
+    ],
 
     // Currently necessary for host builds
     // TODO(b/31559095): bionic on host should define this
@@ -44,12 +52,40 @@
         },
     },
     min_sdk_version: "VanillaIceCream",
+    vendor_available: true,
+}
+
+rust_library {
+    name: "libnativewindow_bindgen",
+    crate_name: "nativewindow_bindgen",
+    srcs: [":libnativewindow_bindgen_internal"],
+    shared_libs: [
+        "libbinder_ndk",
+        "libnativewindow",
+    ],
+    rustlibs: [
+        "libbinder_rs",
+    ],
+    lints: "none",
+    clippy_lints: "none",
+    // Currently necessary for host builds
+    // TODO(b/31559095): bionic on host should define this
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+    min_sdk_version: "VanillaIceCream",
+    vendor_available: true,
 }
 
 rust_test {
     name: "libnativewindow_bindgen_test",
-    srcs: [":libnativewindow_bindgen"],
+    srcs: [":libnativewindow_bindgen_internal"],
     crate_name: "nativewindow_bindgen_test",
+    rustlibs: [
+        "libbinder_rs",
+    ],
     test_suites: ["general-tests"],
     auto_gen_config: true,
     clippy_lints: "none",
@@ -60,6 +96,7 @@
     name: "libnativewindow_defaults",
     srcs: ["src/lib.rs"],
     rustlibs: [
+        "libbinder_rs",
         "libnativewindow_bindgen",
     ],
 }
@@ -77,6 +114,7 @@
         },
     },
     min_sdk_version: "VanillaIceCream",
+    vendor_available: true,
 }
 
 rust_test {
diff --git a/libs/nativewindow/rust/src/lib.rs b/libs/nativewindow/rust/src/lib.rs
index 6eb3bbc..6f86c4a 100644
--- a/libs/nativewindow/rust/src/lib.rs
+++ b/libs/nativewindow/rust/src/lib.rs
@@ -16,13 +16,22 @@
 
 extern crate nativewindow_bindgen as ffi;
 
-pub use ffi::{AHardwareBuffer, AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
+pub use ffi::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
 
+use binder::{
+    binder_impl::{
+        BorrowedParcel, Deserialize, DeserializeArray, DeserializeOption, Serialize,
+        SerializeArray, SerializeOption, NON_NULL_PARCELABLE_FLAG, NULL_PARCELABLE_FLAG,
+    },
+    unstable_api::{status_result, AsNative},
+    StatusCode,
+};
+use ffi::{AHardwareBuffer, AHardwareBuffer_readFromParcel, AHardwareBuffer_writeToParcel};
 use std::fmt::{self, Debug, Formatter};
 use std::mem::ManuallyDrop;
-use std::ptr::{self, NonNull};
+use std::ptr::{self, null_mut, NonNull};
 
-/// Wrapper around an opaque C AHardwareBuffer.
+/// Wrapper around an opaque C `AHardwareBuffer`.
 #[derive(PartialEq, Eq)]
 pub struct HardwareBuffer(NonNull<AHardwareBuffer>);
 
@@ -120,8 +129,11 @@
     /// Available since API level 31.
     pub fn id(&self) -> u64 {
         let mut out_id = 0;
-        // SAFETY: Neither pointers can be null.
-        let status = unsafe { ffi::AHardwareBuffer_getId(self.0.as_ref(), &mut out_id) };
+        // SAFETY: The AHardwareBuffer pointer we pass is guaranteed to be non-null and valid
+        // because it must have been allocated by `AHardwareBuffer_allocate`,
+        // `AHardwareBuffer_readFromParcel` or the caller of `from_raw` and we have not yet
+        // released it. The id pointer must be valid because it comes from a reference.
+        let status = unsafe { ffi::AHardwareBuffer_getId(self.0.as_ptr(), &mut out_id) };
         assert_eq!(status, 0, "id() failed for AHardwareBuffer with error code: {status}");
 
         out_id
@@ -176,9 +188,10 @@
 
 impl Drop for HardwareBuffer {
     fn drop(&mut self) {
-        // SAFETY: self.0 will never be null. AHardwareBuffers allocated from within Rust will have
-        // a refcount of one, and there is a safety warning on taking an AHardwareBuffer from a raw
-        // pointer requiring callers to ensure the refcount is managed appropriately.
+        // SAFETY: The AHardwareBuffer pointer we pass is guaranteed to be non-null and valid
+        // because it must have been allocated by `AHardwareBuffer_allocate`,
+        // `AHardwareBuffer_readFromParcel` or the caller of `from_raw` and we have not yet
+        // released it.
         unsafe { ffi::AHardwareBuffer_release(self.0.as_ptr()) }
     }
 }
@@ -197,6 +210,82 @@
     }
 }
 
+impl Serialize for HardwareBuffer {
+    fn serialize(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
+        SerializeOption::serialize_option(Some(self), parcel)
+    }
+}
+
+impl SerializeOption for HardwareBuffer {
+    fn serialize_option(
+        this: Option<&Self>,
+        parcel: &mut BorrowedParcel,
+    ) -> Result<(), StatusCode> {
+        if let Some(this) = this {
+            parcel.write(&NON_NULL_PARCELABLE_FLAG)?;
+
+            let status =
+            // SAFETY: The AHardwareBuffer pointer we pass is guaranteed to be non-null and valid
+            // because it must have been allocated by `AHardwareBuffer_allocate`,
+            // `AHardwareBuffer_readFromParcel` or the caller of `from_raw` and we have not yet
+            // released it.
+                unsafe { AHardwareBuffer_writeToParcel(this.0.as_ptr(), parcel.as_native_mut()) };
+            status_result(status)
+        } else {
+            parcel.write(&NULL_PARCELABLE_FLAG)
+        }
+    }
+}
+
+impl Deserialize for HardwareBuffer {
+    type UninitType = Option<Self>;
+
+    fn uninit() -> Option<Self> {
+        None
+    }
+
+    fn from_init(value: Self) -> Option<Self> {
+        Some(value)
+    }
+
+    fn deserialize(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
+        DeserializeOption::deserialize_option(parcel)
+            .transpose()
+            .unwrap_or(Err(StatusCode::UNEXPECTED_NULL))
+    }
+}
+
+impl DeserializeOption for HardwareBuffer {
+    fn deserialize_option(parcel: &BorrowedParcel) -> Result<Option<Self>, StatusCode> {
+        let present: i32 = parcel.read()?;
+        match present {
+            NULL_PARCELABLE_FLAG => Ok(None),
+            NON_NULL_PARCELABLE_FLAG => {
+                let mut buffer = null_mut();
+
+                let status =
+                // SAFETY: Both pointers must be valid because they are obtained from references.
+                // `AHardwareBuffer_readFromParcel` doesn't store them or do anything else special
+                // with them. If it returns success then it will have allocated a new
+                // `AHardwareBuffer` and incremented the reference count, so we can use it until we
+                // release it.
+                    unsafe { AHardwareBuffer_readFromParcel(parcel.as_native(), &mut buffer) };
+
+                status_result(status)?;
+
+                Ok(Some(Self(NonNull::new(buffer).expect(
+                    "AHardwareBuffer_readFromParcel returned success but didn't allocate buffer",
+                ))))
+            }
+            _ => Err(StatusCode::BAD_VALUE),
+        }
+    }
+}
+
+impl SerializeArray for HardwareBuffer {}
+
+impl DeserializeArray for HardwareBuffer {}
+
 // SAFETY: The underlying *AHardwareBuffers can be moved between threads.
 unsafe impl Send for HardwareBuffer {}
 
diff --git a/libs/nativewindow/rust/sys/nativewindow_bindings.h b/libs/nativewindow/rust/sys/nativewindow_bindings.h
index e652aee..4525a42 100644
--- a/libs/nativewindow/rust/sys/nativewindow_bindings.h
+++ b/libs/nativewindow/rust/sys/nativewindow_bindings.h
@@ -16,5 +16,6 @@
 
 #include <android/data_space.h>
 #include <android/hardware_buffer.h>
+#include <android/hardware_buffer_aidl.h>
 #include <android/hdr_metadata.h>
 #include <android/native_window.h>
diff --git a/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp b/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp
index f24a4f1..5bf6560 100644
--- a/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp
+++ b/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp
@@ -18,7 +18,6 @@
 
 #include "SkiaMemoryReporter.h"
 
-#include <SkString.h>
 #include <android-base/stringprintf.h>
 #include <log/log_main.h>
 
@@ -142,7 +141,7 @@
                 TraceValue traceValue = convertUnits(result->second);
                 const char* entry = (traceValue.count > 1) ? "entries" : "entry";
                 StringAppendF(&log, "  %s: %.2f %s (%d %s)\n", categoryItem->first.c_str(),
-                              traceValue.value, traceValue.units, traceValue.count, entry);
+                              traceValue.value, traceValue.units.c_str(), traceValue.count, entry);
             }
             if (mItemize) {
                 for (const auto& individualItem : resultsMap) {
@@ -153,7 +152,7 @@
                         auto result = individualItem.second.find("size");
                         TraceValue size = convertUnits(result->second);
                         StringAppendF(&log, "    %s: size[%.2f %s]", individualItem.first.c_str(),
-                                      size.value, size.units);
+                                      size.value, size.units.c_str());
                         if (!wrappedResources) {
                             for (const auto& itemValues : individualItem.second) {
                                 if (strcmp("size", itemValues.first) == 0) {
@@ -162,10 +161,10 @@
                                 TraceValue traceValue = convertUnits(itemValues.second);
                                 if (traceValue.value == 0.0f) {
                                     StringAppendF(&log, " %s[%s]", itemValues.first,
-                                                  traceValue.units);
+                                                  traceValue.units.c_str());
                                 } else {
                                     StringAppendF(&log, " %s[%.2f %s]", itemValues.first,
-                                                  traceValue.value, traceValue.units);
+                                                  traceValue.value, traceValue.units.c_str());
                                 }
                             }
                         }
@@ -184,16 +183,16 @@
     TraceValue total = convertUnits(mTotalSize);
     TraceValue purgeable = convertUnits(mPurgeableSize);
     StringAppendF(&log, " %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value,
-                  total.value, total.units, purgeable.value, purgeable.units);
+                  total.value, total.units.c_str(), purgeable.value, purgeable.units.c_str());
 }
 
 SkiaMemoryReporter::TraceValue SkiaMemoryReporter::convertUnits(const TraceValue& value) {
     TraceValue output(value);
-    if (SkString("bytes") == SkString(output.units) && output.value >= 1024) {
+    if (SkString("bytes") == output.units && output.value >= 1024) {
         output.value = output.value / 1024.0f;
         output.units = "KB";
     }
-    if (SkString("KB") == SkString(output.units) && output.value >= 1024) {
+    if (SkString("KB") == output.units && output.value >= 1024) {
         output.value = output.value / 1024.0f;
         output.units = "MB";
     }
diff --git a/libs/renderengine/skia/debug/SkiaMemoryReporter.h b/libs/renderengine/skia/debug/SkiaMemoryReporter.h
index dbbd65b..da91674 100644
--- a/libs/renderengine/skia/debug/SkiaMemoryReporter.h
+++ b/libs/renderengine/skia/debug/SkiaMemoryReporter.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <SkString.h>
 #include <SkTraceMemoryDump.h>
 
 #include <string>
@@ -75,7 +76,7 @@
         TraceValue(const char* units, uint64_t value) : units(units), value(value), count(1) {}
         TraceValue(const TraceValue& v) : units(v.units), value(v.value), count(v.count) {}
 
-        const char* units;
+        SkString units;
         float value;
         int count;
     };
@@ -104,4 +105,4 @@
 
 } /* namespace skia */
 } /* namespace renderengine */
-} /* namespace android */
\ No newline at end of file
+} /* namespace android */
diff --git a/libs/sensor/Android.bp b/libs/sensor/Android.bp
index b6b9cc4..d992aa5 100644
--- a/libs/sensor/Android.bp
+++ b/libs/sensor/Android.bp
@@ -49,6 +49,7 @@
         "liblog",
         "libhardware",
         "libpermission",
+        "android.companion.virtual.virtualdevice_aidl-cpp",
     ],
 
     export_include_dirs: ["include"],
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index 3ed8c8a..d112a12 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -26,6 +26,8 @@
 #include <utils/RefBase.h>
 #include <utils/Singleton.h>
 
+#include <android/companion/virtualnative/IVirtualDeviceManagerNative.h>
+
 #include <binder/IBinder.h>
 #include <binder/IPermissionController.h>
 #include <binder/IServiceManager.h>
@@ -39,6 +41,43 @@
 namespace android {
 // ----------------------------------------------------------------------------
 
+namespace {
+
+using ::android::companion::virtualnative::IVirtualDeviceManagerNative;
+
+static constexpr int DEVICE_ID_DEFAULT = 0;
+
+// Returns the deviceId of the device where this uid is observed. If the uid is present on more than
+// one devices, return the default deviceId.
+int getDeviceIdForUid(uid_t uid) {
+    sp<IBinder> binder =
+            defaultServiceManager()->checkService(String16("virtualdevice_native"));
+    if (binder != nullptr) {
+        auto vdm = interface_cast<IVirtualDeviceManagerNative>(binder);
+        std::vector<int> deviceIds;
+        vdm->getDeviceIdsForUid(uid, &deviceIds);
+        // If the UID is associated with multiple virtual devices, use the default device's
+        // sensors as we cannot disambiguate here. This effectively means that the app has
+        // activities on different devices at the same time, so it must handle the device
+        // awareness by itself.
+        if (deviceIds.size() == 1) {
+            const int deviceId = deviceIds.at(0);
+            int devicePolicy = IVirtualDeviceManagerNative::DEVICE_POLICY_DEFAULT;
+            vdm->getDevicePolicy(deviceId,
+                                 IVirtualDeviceManagerNative::POLICY_TYPE_SENSORS,
+                                 &devicePolicy);
+            if (devicePolicy == IVirtualDeviceManagerNative::DEVICE_POLICY_CUSTOM) {
+                return deviceId;
+            }
+        }
+    } else {
+        ALOGW("Cannot get virtualdevice_native service");
+    }
+    return DEVICE_ID_DEFAULT;
+}
+
+}  // namespace
+
 Mutex SensorManager::sLock;
 std::map<String16, SensorManager*> SensorManager::sPackageInstances;
 
@@ -53,6 +92,7 @@
         sensorManager = iterator->second;
     } else {
         String16 opPackageName = packageName;
+        const uid_t uid = IPCThreadState::self()->getCallingUid();
 
         // It is possible that the calling code has no access to the package name.
         // In this case we will get the packages for the calling UID and pick the
@@ -63,7 +103,6 @@
         if (opPackageName.size() <= 0) {
             sp<IBinder> binder = defaultServiceManager()->getService(String16("permission"));
             if (binder != nullptr) {
-                const uid_t uid = IPCThreadState::self()->getCallingUid();
                 Vector<String16> packages;
                 interface_cast<IPermissionController>(binder)->getPackagesForUid(uid, packages);
                 if (!packages.isEmpty()) {
@@ -76,7 +115,10 @@
             }
         }
 
-        sensorManager = new SensorManager(opPackageName);
+        // Check if the calling UID is observed on a virtual device. If so, provide that device's
+        // sensors by default instead of the default device's sensors.
+        const int deviceId = getDeviceIdForUid(uid);
+        sensorManager = new SensorManager(opPackageName, deviceId);
 
         // If we had no package name, we looked it up from the UID and the sensor
         // manager instance we created should also be mapped to the empty package
@@ -102,8 +144,9 @@
     }
 }
 
-SensorManager::SensorManager(const String16& opPackageName)
-    : mSensorList(nullptr), mOpPackageName(opPackageName), mDirectConnectionHandle(1) {
+SensorManager::SensorManager(const String16& opPackageName, int deviceId)
+    : mSensorList(nullptr), mOpPackageName(opPackageName), mDeviceId(deviceId),
+        mDirectConnectionHandle(1) {
     Mutex::Autolock _l(mLock);
     assertStateLocked();
 }
@@ -174,7 +217,12 @@
         mDeathObserver = new DeathObserver(*const_cast<SensorManager *>(this));
         IInterface::asBinder(mSensorServer)->linkToDeath(mDeathObserver);
 
-        mSensors = mSensorServer->getSensorList(mOpPackageName);
+        if (mDeviceId == DEVICE_ID_DEFAULT) {
+            mSensors = mSensorServer->getSensorList(mOpPackageName);
+        } else {
+            mSensors = mSensorServer->getRuntimeSensorList(mOpPackageName, mDeviceId);
+        }
+
         size_t count = mSensors.size();
         // If count is 0, mSensorList will be non-null. This is old
         // existing behavior and callers expect this.
diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h
index 64b4501..e67fac7 100644
--- a/libs/sensor/include/sensor/SensorManager.h
+++ b/libs/sensor/include/sensor/SensorManager.h
@@ -79,7 +79,7 @@
     void sensorManagerDied();
     static status_t waitForSensorService(sp<ISensorServer> *server);
 
-    explicit SensorManager(const String16& opPackageName);
+    explicit SensorManager(const String16& opPackageName, int deviceId);
     status_t assertStateLocked();
 
 private:
@@ -94,6 +94,7 @@
     Vector<Sensor> mDynamicSensors;
     sp<IBinder::DeathRecipient> mDeathObserver;
     const String16 mOpPackageName;
+    const int mDeviceId;
     std::unordered_map<int, sp<ISensorEventConnection>> mDirectConnection;
     int32_t mDirectConnectionHandle;
 };
diff --git a/libs/ui/include/ui/DisplayMode.h b/libs/ui/include/ui/DisplayMode.h
index a469c78..ddb9bbd 100644
--- a/libs/ui/include/ui/DisplayMode.h
+++ b/libs/ui/include/ui/DisplayMode.h
@@ -38,7 +38,7 @@
     std::vector<ui::Hdr> supportedHdrTypes;
 
     // Some modes have peak refresh rate lower than the panel vsync rate.
-    float refreshRate = 0.f;
+    float peakRefreshRate = 0.f;
     float vsyncRate = 0.f;
     nsecs_t appVsyncOffset = 0;
     nsecs_t sfVsyncOffset = 0;
diff --git a/services/sensorservice/aidl/fuzzer/Android.bp b/services/sensorservice/aidl/fuzzer/Android.bp
index 6870d4e..f6f104e 100644
--- a/services/sensorservice/aidl/fuzzer/Android.bp
+++ b/services/sensorservice/aidl/fuzzer/Android.bp
@@ -17,6 +17,7 @@
     static_libs: [
         "libsensorserviceaidl",
         "libpermission",
+        "android.companion.virtual.virtualdevice_aidl-cpp",
         "android.frameworks.sensorservice-V1-ndk",
         "android.hardware.sensors-V1-convert",
         "android.hardware.sensors-V2-ndk",
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 892bb8f..6807c8e 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -147,6 +147,8 @@
     MOCK_METHOD(const aidl::android::hardware::graphics::composer3::OverlayProperties&,
                 getOverlaySupport, (), (const, override));
     MOCK_METHOD(status_t, setRefreshRateChangedCallbackDebugEnabled, (PhysicalDisplayId, bool));
+    MOCK_METHOD(status_t, notifyExpectedPresentIfRequired,
+                (PhysicalDisplayId, nsecs_t, int32_t, int32_t));
 };
 
 } // namespace mock
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 1f409c6..1643ad0 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -1450,6 +1450,20 @@
     return Error::NONE;
 }
 
+Error AidlComposer::notifyExpectedPresent(Display displayId, nsecs_t expectedPresentTime,
+                                          int32_t frameIntervalNs) {
+    const auto status =
+            mAidlComposerClient->notifyExpectedPresent(translate<int64_t>(displayId),
+                                                       ClockMonotonicTimestamp{expectedPresentTime},
+                                                       frameIntervalNs);
+
+    if (!status.isOk()) {
+        ALOGE("notifyExpectedPresent failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
 Error AidlComposer::getClientTargetProperty(
         Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) {
     Error error = Error::NONE;
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index b1b57a4..7693a80 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -240,6 +240,8 @@
     Error getHdrConversionCapabilities(std::vector<HdrConversionCapability>*) override;
     Error setHdrConversionStrategy(HdrConversionStrategy, Hdr*) override;
     Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) override;
+    Error notifyExpectedPresent(Display, nsecs_t expectedPresentTime,
+                                int32_t frameIntervalNs) override;
 
 private:
     // Many public functions above simply write a command into the command
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index e942587..6704d88 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -298,6 +298,8 @@
     virtual Error setHdrConversionStrategy(
             ::aidl::android::hardware::graphics::common::HdrConversionStrategy, Hdr*) = 0;
     virtual Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) = 0;
+    virtual Error notifyExpectedPresent(Display, nsecs_t expectedPresentTime,
+                                        int32_t frameIntervalNs) = 0;
 };
 
 } // namespace Hwc2
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 812621f..fb6089d 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -31,6 +31,7 @@
 #include <compositionengine/OutputLayer.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
 #include <ftl/concat.h>
+#include <gui/TraceUtils.h>
 #include <log/log.h>
 #include <ui/DebugUtils.h>
 #include <ui/GraphicBuffer.h>
@@ -484,6 +485,7 @@
     }();
 
     displayData.validateWasSkipped = false;
+    displayData.lastExpectedPresentTimestamp = expectedPresentTime;
     if (canSkipValidate) {
         sp<Fence> outPresentFence;
         uint32_t state = UINT32_MAX;
@@ -876,6 +878,30 @@
     return NO_ERROR;
 }
 
+status_t HWComposer::notifyExpectedPresentIfRequired(PhysicalDisplayId displayId,
+                                                     nsecs_t expectedPresentTime,
+                                                     int32_t frameIntervalNs, int32_t timeoutNs) {
+    RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
+
+    auto& displayData = mDisplayData[displayId];
+    if (expectedPresentTime >= displayData.lastExpectedPresentTimestamp &&
+        expectedPresentTime < displayData.lastExpectedPresentTimestamp + timeoutNs) {
+        return NO_ERROR;
+    }
+
+    displayData.lastExpectedPresentTimestamp = expectedPresentTime;
+    ATRACE_FORMAT("%s ExpectedPresentTime %" PRId64 " frameIntervalNs %d", __func__,
+                  expectedPresentTime, frameIntervalNs);
+    const auto error = mComposer->notifyExpectedPresent(displayData.hwcDisplay->getId(),
+                                                        expectedPresentTime, frameIntervalNs);
+
+    if (error != hal::Error::NONE) {
+        ALOGE("Error in notifyExpectedPresent call %s", to_string(error).c_str());
+        return INVALID_OPERATION;
+    }
+    return NO_ERROR;
+}
+
 status_t HWComposer::getDisplayDecorationSupport(
         PhysicalDisplayId displayId,
         std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index b4d3d28..726a8ea 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -60,6 +60,7 @@
 struct DisplayedFrameStats;
 class GraphicBuffer;
 class TestableSurfaceFlinger;
+struct HWComposerTest;
 struct CompositionInfo;
 
 namespace Hwc2 {
@@ -300,6 +301,9 @@
             aidl::android::hardware::graphics::common::HdrConversionStrategy,
             aidl::android::hardware::graphics::common::Hdr*) = 0;
     virtual status_t setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId, bool enabled) = 0;
+    virtual status_t notifyExpectedPresentIfRequired(PhysicalDisplayId, nsecs_t expectedPresentTime,
+                                                     int32_t frameIntervalNs,
+                                                     int32_t timeoutNs) = 0;
 };
 
 static inline bool operator==(const android::HWComposer::DeviceRequestedChanges& lhs,
@@ -458,6 +462,8 @@
             aidl::android::hardware::graphics::common::HdrConversionStrategy,
             aidl::android::hardware::graphics::common::Hdr*) override;
     status_t setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId, bool enabled) override;
+    status_t notifyExpectedPresentIfRequired(PhysicalDisplayId, nsecs_t expectedPresentTime,
+                                             int32_t frameIntervalNs, int32_t timeoutNs) override;
 
     // for debugging ----------------------------------------------------------
     void dump(std::string& out) const override;
@@ -483,6 +489,7 @@
 private:
     // For unit tests
     friend TestableSurfaceFlinger;
+    friend HWComposerTest;
 
     struct DisplayData {
         std::unique_ptr<HWC2::Display> hwcDisplay;
@@ -490,6 +497,8 @@
         sp<Fence> lastPresentFence = Fence::NO_FENCE; // signals when the last set op retires
         nsecs_t lastPresentTimestamp = 0;
 
+        nsecs_t lastExpectedPresentTimestamp = 0;
+
         std::unordered_map<HWC2::Layer*, sp<Fence>> releaseFences;
 
         bool validateWasSkipped;
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index 70d48de..c13e568 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -1378,6 +1378,10 @@
     return Error::UNSUPPORTED;
 }
 
+Error HidlComposer::notifyExpectedPresent(Display, nsecs_t, int32_t) {
+    return Error::UNSUPPORTED;
+}
+
 Error HidlComposer::getClientTargetProperty(
         Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) {
     IComposerClient::ClientTargetProperty property;
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index 26d2222..1004ddd 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -348,6 +348,7 @@
     Error setHdrConversionStrategy(aidl::android::hardware::graphics::common::HdrConversionStrategy,
                                    Hdr*) override;
     Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) override;
+    Error notifyExpectedPresent(Display, nsecs_t, int32_t) override;
 
 private:
     class CommandWriter : public CommandWriterBase {
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index 80a51ea..4fd6495 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -84,6 +84,7 @@
     bool isTrustedOverlay;
     gui::GameMode gameMode;
     scheduler::LayerInfo::FrameRate frameRate;
+    scheduler::LayerInfo::FrameRate inheritedFrameRate;
     scheduler::LayerInfo::FrameRateSelectionStrategy frameRateSelectionStrategy;
     scheduler::FrameRateCompatibility defaultFrameRateCompatibility =
             scheduler::FrameRateCompatibility::Default;
@@ -122,6 +123,8 @@
         ReachableByRelativeParent
     };
     Reachablilty reachablilty;
+    // True when the surfaceDamage is recognized as a small area update.
+    bool isSmallDirty = false;
 
     static bool isOpaqueFormat(PixelFormat format);
     static bool isTransformValid(const ui::Transform& t);
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 4db2b66..2a0857d 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -815,9 +815,14 @@
                              RequestedLayerState::Changes::Hierarchy)) {
         bool shouldOverrideChildren = parentSnapshot.frameRateSelectionStrategy ==
                 scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren;
-        snapshot.frameRate = !requested.requestedFrameRate.isValid() || shouldOverrideChildren
-                ? parentSnapshot.frameRate
-                : requested.requestedFrameRate;
+        if (!requested.requestedFrameRate.isValid() || shouldOverrideChildren) {
+            snapshot.inheritedFrameRate = parentSnapshot.inheritedFrameRate;
+        } else {
+            snapshot.inheritedFrameRate = requested.requestedFrameRate;
+        }
+        // Set the framerate as the inherited frame rate and allow children to override it if
+        // needed.
+        snapshot.frameRate = snapshot.inheritedFrameRate;
         snapshot.changes |= RequestedLayerState::Changes::FrameRate;
     }
 
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index ce845d9..0e49b75 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -209,7 +209,7 @@
                  (barrierFrameNumber > bufferData->frameNumber))) {
                 ALOGE("Out of order buffers detected for %s producedId=%d frameNumber=%" PRIu64
                       " -> producedId=%d frameNumber=%" PRIu64,
-                      getDebugString().c_str(), bufferData->producerId, bufferData->frameNumber,
+                      getDebugString().c_str(), barrierProducerId, barrierFrameNumber,
                       bufferData->producerId, frameNumber);
                 TransactionTraceWriter::getInstance().invoke("out_of_order_buffers_",
                                                              /*overwrite=*/false);
@@ -401,6 +401,7 @@
     if (!handleAlive) debug << " !handle";
     if (z != 0) debug << " z=" << z;
     if (layerStack.id != 0) debug << " layerStack=" << layerStack.id;
+    debug << "}";
     return debug.str();
 }
 
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 73b1ca8..700baa2 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -2932,11 +2932,6 @@
         }
     }
 
-    // Prevent tracing the same release multiple times.
-    if (mPreviousFrameNumber != mPreviousReleasedFrameNumber) {
-        mPreviousReleasedFrameNumber = mPreviousFrameNumber;
-    }
-
     if (ch != nullptr) {
         ch->previousReleaseCallbackId = mPreviousReleaseCallbackId;
         ch->previousReleaseFences.emplace_back(std::move(futureFenceResult));
@@ -3369,7 +3364,7 @@
     mDrawingState.surfaceDamageRegion = surfaceDamage;
     mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
-    setIsSmallDirty();
+    setIsSmallDirty(surfaceDamage, getTransform());
     return true;
 }
 
@@ -4416,7 +4411,9 @@
     mLastLatchTime = latchTime;
 }
 
-void Layer::setIsSmallDirty() {
+void Layer::setIsSmallDirty(const Region& damageRegion,
+                            const ui::Transform& layerToDisplayTransform) {
+    mSmallDirty = false;
     if (!mFlinger->mScheduler->supportSmallDirtyDetection()) {
         return;
     }
@@ -4425,17 +4422,26 @@
         mWindowType != WindowInfo::Type::BASE_APPLICATION) {
         return;
     }
-    Rect bounds = mDrawingState.surfaceDamageRegion.getBounds();
+
+    Rect bounds = damageRegion.getBounds();
     if (!bounds.isValid()) {
         return;
     }
 
+    // Transform to screen space.
+    bounds = layerToDisplayTransform.transform(bounds);
+
     // If the damage region is a small dirty, this could give the hint for the layer history that
     // it could suppress the heuristic rate when calculating.
     mSmallDirty = mFlinger->mScheduler->isSmallDirtyArea(mOwnerAppId,
                                                          bounds.getWidth() * bounds.getHeight());
 }
 
+void Layer::setIsSmallDirty(frontend::LayerSnapshot* snapshot) {
+    setIsSmallDirty(snapshot->surfaceDamage, snapshot->localTransform);
+    snapshot->isSmallDirty = mSmallDirty;
+}
+
 } // namespace android
 
 #if defined(__gl_h_)
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index f67da2a..dd91adc 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -937,7 +937,8 @@
     const sp<SurfaceFlinger> mFlinger;
 
     // Check if the damage region is a small dirty.
-    void setIsSmallDirty();
+    void setIsSmallDirty(const Region& damageRegion, const ui::Transform& layerToDisplayTransform);
+    void setIsSmallDirty(frontend::LayerSnapshot* snapshot);
 
 protected:
     // For unit tests
@@ -1235,9 +1236,6 @@
 
     ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID;
     sp<IBinder> mPreviousReleaseBufferEndpoint;
-    uint64_t mPreviousReleasedFrameNumber = 0;
-
-    uint64_t mPreviousBarrierFrameNumber = 0;
 
     bool mReleasePreviousBuffer = false;
 
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index c4c9fa5..f467670 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -40,16 +40,18 @@
 
 namespace {
 
-nsecs_t getExpectedCallbackTime(nsecs_t nextVsyncTime,
+nsecs_t getExpectedCallbackTime(nsecs_t now, nsecs_t nextVsyncTime,
                                 const VSyncDispatch::ScheduleTiming& timing) {
-    return nextVsyncTime - timing.readyDuration - timing.workDuration;
+    const auto expectedCallbackTime = nextVsyncTime - timing.readyDuration - timing.workDuration;
+    const auto baseTime = flags::dont_skip_on_early() ? now : expectedCallbackTime;
+    return std::max(baseTime, expectedCallbackTime);
 }
 
 nsecs_t getExpectedCallbackTime(VSyncTracker& tracker, nsecs_t now,
                                 const VSyncDispatch::ScheduleTiming& timing) {
     const auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(
             std::max(timing.earliestVsync, now + timing.workDuration + timing.readyDuration));
-    return getExpectedCallbackTime(nextVsyncTime, timing);
+    return getExpectedCallbackTime(now, nextVsyncTime, timing);
 }
 
 } // namespace
@@ -105,11 +107,11 @@
             mArmedInfo && ((nextWakeupTime > (mArmedInfo->mActualWakeupTime + mMinVsyncDistance)));
     if (flags::dont_skip_on_early()) {
         if (wouldSkipAVsyncTarget || wouldSkipAWakeup) {
-            return getExpectedCallbackTime(mArmedInfo->mActualVsyncTime, timing);
+            return getExpectedCallbackTime(now, mArmedInfo->mActualVsyncTime, timing);
         }
     } else {
         if (wouldSkipAVsyncTarget && wouldSkipAWakeup) {
-            return getExpectedCallbackTime(nextVsyncTime, timing);
+            return getExpectedCallbackTime(now, nextVsyncTime, timing);
         }
     }
 
@@ -119,7 +121,7 @@
     auto const nextReadyTime = nextVsyncTime - timing.readyDuration;
     mScheduleTiming = timing;
     mArmedInfo = {nextWakeupTime, nextVsyncTime, nextReadyTime};
-    return getExpectedCallbackTime(nextVsyncTime, timing);
+    return getExpectedCallbackTime(now, nextVsyncTime, timing);
 }
 
 void VSyncDispatchTimerQueueEntry::addPendingWorkloadUpdate(VSyncDispatch::ScheduleTiming timing) {
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index b13c0e8..fc51721 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -266,7 +266,7 @@
 
 bool isAbove4k30(const ui::DisplayMode& outMode) {
     using fps_approx_ops::operator>;
-    Fps refreshRate = Fps::fromValue(outMode.refreshRate);
+    Fps refreshRate = Fps::fromValue(outMode.peakRefreshRate);
     return outMode.resolution.getWidth() >= FOUR_K_WIDTH &&
             outMode.resolution.getHeight() >= FOUR_K_HEIGHT && refreshRate > 30_Hz;
 }
@@ -1046,11 +1046,11 @@
         outMode.yDpi = yDpi;
 
         const auto peakFps = mode->getPeakFps();
-        outMode.refreshRate = peakFps.getValue();
+        outMode.peakRefreshRate = peakFps.getValue();
         outMode.vsyncRate = mode->getVsyncRate().getValue();
 
-        const auto vsyncConfigSet =
-                mVsyncConfiguration->getConfigsForRefreshRate(Fps::fromValue(outMode.refreshRate));
+        const auto vsyncConfigSet = mVsyncConfiguration->getConfigsForRefreshRate(
+                Fps::fromValue(outMode.peakRefreshRate));
         outMode.appVsyncOffset = vsyncConfigSet.late.appOffset;
         outMode.sfVsyncOffset = vsyncConfigSet.late.sfOffset;
         outMode.group = mode->getGroup();
@@ -1232,7 +1232,7 @@
 }
 
 status_t SurfaceFlinger::setActiveModeFromBackdoor(const sp<display::DisplayToken>& displayToken,
-                                                   DisplayModeId modeId) {
+                                                   DisplayModeId modeId, Fps minFps, Fps maxFps) {
     ATRACE_CALL();
 
     if (!displayToken) {
@@ -1265,13 +1265,15 @@
         }
 
         const Fps fps = *fpsOpt;
+        const FpsRange physical = {fps, fps};
+        const FpsRange render = {minFps.isValid() ? minFps : fps, maxFps.isValid() ? maxFps : fps};
+        const FpsRanges ranges = {physical, render};
 
         // Keep the old switching type.
         const bool allowGroupSwitching =
                 display->refreshRateSelector().getCurrentPolicy().allowGroupSwitching;
 
-        const scheduler::RefreshRateSelector::DisplayManagerPolicy policy{modeId,
-                                                                          {fps, fps},
+        const scheduler::RefreshRateSelector::DisplayManagerPolicy policy{modeId, ranges, ranges,
                                                                           allowGroupSwitching};
 
         return setDesiredDisplayModeSpecsInternal(display, policy);
@@ -2211,8 +2213,29 @@
             continue;
         }
 
-        if (!snapshot->changes.any(Changes::FrameRate | Changes::Buffer | Changes::Animation) &&
-            (snapshot->clientChanges & layer_state_t::eDefaultFrameRateCompatibilityChanged) == 0) {
+        const bool updateSmallDirty = mScheduler->supportSmallDirtyDetection() &&
+                ((snapshot->clientChanges & layer_state_t::eSurfaceDamageRegionChanged) ||
+                 snapshot->changes.any(Changes::Geometry));
+
+        const bool hasChanges =
+                snapshot->changes.any(Changes::FrameRate | Changes::Buffer | Changes::Animation) ||
+                (snapshot->clientChanges & layer_state_t::eDefaultFrameRateCompatibilityChanged) !=
+                        0;
+
+        if (!updateSmallDirty && !hasChanges) {
+            continue;
+        }
+
+        auto it = mLegacyLayers.find(snapshot->sequence);
+        LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldn't find layer object for %s",
+                            snapshot->getDebugString().c_str());
+
+        if (updateSmallDirty) {
+            // Update small dirty flag while surface damage region or geometry changed
+            it->second->setIsSmallDirty(snapshot.get());
+        }
+
+        if (!hasChanges) {
             continue;
         }
 
@@ -2222,12 +2245,9 @@
                 .transform = snapshot->geomLayerTransform,
                 .setFrameRateVote = snapshot->frameRate,
                 .frameRateSelectionPriority = snapshot->frameRateSelectionPriority,
+                .isSmallDirty = snapshot->isSmallDirty,
         };
 
-        auto it = mLegacyLayers.find(snapshot->sequence);
-        LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s",
-                            snapshot->getDebugString().c_str());
-
         if (snapshot->clientChanges & layer_state_t::eDefaultFrameRateCompatibilityChanged) {
             mScheduler->setDefaultFrameRateCompatibility(snapshot->sequence,
                                                          snapshot->defaultFrameRateCompatibility);
@@ -4031,7 +4051,7 @@
 
     if (sysprop::use_content_detection_for_refresh_rate(false)) {
         features |= Feature::kContentDetection;
-        if (flags::vrr_small_dirty_detection()) {
+        if (flags::enable_small_area_detection()) {
             features |= Feature::kSmallDirtyContentDetection;
         }
     }
@@ -6932,6 +6952,12 @@
                 return NO_ERROR;
             }
             case 1035: {
+                // Parameters:
+                // - (required) i32 mode id.
+                // - (optional) i64 display id. Using default display if not provided.
+                // - (optional) f min render rate. Using mode's fps is not provided.
+                // - (optional) f max render rate. Using mode's fps is not provided.
+
                 const int modeId = data.readInt32();
 
                 const auto display = [&]() -> sp<IBinder> {
@@ -6948,8 +6974,21 @@
                     return nullptr;
                 }();
 
+                const auto getFps = [&] {
+                    float value;
+                    if (data.readFloat(&value) == NO_ERROR) {
+                        return Fps::fromValue(value);
+                    }
+
+                    return Fps();
+                };
+
+                const auto minFps = getFps();
+                const auto maxFps = getFps();
+
                 mDebugDisplayModeSetByBackdoor = false;
-                const status_t result = setActiveModeFromBackdoor(display, DisplayModeId{modeId});
+                const status_t result =
+                        setActiveModeFromBackdoor(display, DisplayModeId{modeId}, minFps, maxFps);
                 mDebugDisplayModeSetByBackdoor = result == NO_ERROR;
                 return result;
             }
@@ -8492,15 +8531,6 @@
 void SurfaceFlinger::onActiveDisplaySizeChanged(const DisplayDevice& activeDisplay) {
     mScheduler->onActiveDisplayAreaChanged(activeDisplay.getWidth() * activeDisplay.getHeight());
     getRenderEngine().onActiveDisplaySizeChanged(activeDisplay.getSize());
-
-    // Notify layers to update small dirty flag.
-    if (mScheduler->supportSmallDirtyDetection()) {
-        mCurrentState.traverse([&](Layer* layer) {
-            if (layer->getLayerStack() == activeDisplay.getLayerStack()) {
-                layer->setIsSmallDirty();
-            }
-        });
-    }
 }
 
 sp<DisplayDevice> SurfaceFlinger::getActivatableDisplay() const {
@@ -9191,7 +9221,7 @@
         outMode.resolution.height = mode.resolution.height;
         outMode.xDpi = mode.xDpi;
         outMode.yDpi = mode.yDpi;
-        outMode.refreshRate = mode.refreshRate;
+        outMode.peakRefreshRate = mode.peakRefreshRate;
         outMode.vsyncRate = mode.vsyncRate;
         outMode.appVsyncOffset = mode.appVsyncOffset;
         outMode.sfVsyncOffset = mode.sfVsyncOffset;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 7500b96..96b67b8 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -684,7 +684,8 @@
     void setDesiredActiveMode(display::DisplayModeRequest&&, bool force = false)
             REQUIRES(mStateLock);
 
-    status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId);
+    status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId, Fps minFps,
+                                       Fps maxFps);
 
     void initiateDisplayModeChanges() REQUIRES(mStateLock, kMainThreadContext);
     void finalizeDisplayModeChange(DisplayDevice&) REQUIRES(mStateLock, kMainThreadContext);
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.cpp b/services/surfaceflinger/SurfaceFlingerProperties.cpp
index 66c8f33..a765078 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.cpp
+++ b/services/surfaceflinger/SurfaceFlingerProperties.cpp
@@ -371,5 +371,9 @@
     return SurfaceFlingerProperties::clear_slots_with_set_layer_buffer().value_or(defaultValue);
 }
 
+int32_t game_default_frame_rate_override(int32_t defaultValue) {
+    return SurfaceFlingerProperties::game_default_frame_rate_override().value_or(defaultValue);
+}
+
 } // namespace sysprop
 } // namespace android
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.h b/services/surfaceflinger/SurfaceFlingerProperties.h
index a080420..65ebe2a 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.h
+++ b/services/surfaceflinger/SurfaceFlingerProperties.h
@@ -101,6 +101,8 @@
 
 bool clear_slots_with_set_layer_buffer(bool defaultValue);
 
+int32_t game_default_frame_rate_override(int32_t defaultValue);
+
 } // namespace sysprop
 } // namespace android
 #endif // SURFACEFLINGERPROPERTIES_H_
diff --git a/services/surfaceflinger/Tracing/tools/main.cpp b/services/surfaceflinger/Tracing/tools/main.cpp
index 698ef06..18022b1 100644
--- a/services/surfaceflinger/Tracing/tools/main.cpp
+++ b/services/surfaceflinger/Tracing/tools/main.cpp
@@ -64,8 +64,17 @@
 
     if (!LayerTraceGenerator().generate(transactionTraceFile, traceFlags, layerTracing,
                                         generateLastEntryOnly)) {
-        std::cout << "Error: Failed to generate layers trace " << outputLayersTracePath;
+        std::cout << "Error: Failed to generate layers trace " << outputLayersTracePath << "\n";
         return -1;
     }
+
+    // Set output file permissions (-rw-r--r--)
+    outStream.close();
+    const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+    if (chmod(outputLayersTracePath, mode) != 0) {
+        std::cout << "Error: Failed to set permissions of " << outputLayersTracePath << "\n";
+        return -1;
+    }
+
     return 0;
 }
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp
index a22a778..8978971 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp
@@ -23,12 +23,19 @@
 
 using namespace google::protobuf;
 
-constexpr size_t kMaxStringSize = 100;
+constexpr size_t kMaxStringSize = 256;
 constexpr size_t kMinLayerIds = 1;
 constexpr size_t kMaxLayerIds = 10;
+constexpr int32_t kMinRange = 0;
 constexpr int32_t kConfigDuration = 500;
 constexpr int32_t kBufferSize = 1024;
 constexpr int32_t kTimeOffset = 100000;
+constexpr perfetto::BackendType backendTypes[] = {
+        perfetto::kUnspecifiedBackend,
+        perfetto::kInProcessBackend,
+        perfetto::kSystemBackend,
+        perfetto::kCustomBackend,
+};
 
 class FrameTracerFuzzer {
 public:
@@ -47,24 +54,25 @@
     void process();
 
 private:
-    std::unique_ptr<perfetto::TracingSession> getTracingSessionForTest();
     void traceTimestamp();
-    std::vector<int32_t> generateLayerIds(size_t numLayerIds);
     void traceTimestamp(std::vector<int32_t> layerIds, size_t numLayerIds);
     void traceFence(std::vector<int32_t> layerIds, size_t numLayerIds);
+    std::unique_ptr<perfetto::TracingSession> getTracingSessionForTest();
     std::unique_ptr<android::FrameTracer> mFrameTracer = nullptr;
-    FuzzedDataProvider mFdp;
+    std::vector<int32_t> generateLayerIds(size_t numLayerIds);
     android::FenceToFenceTimeMap mFenceFactory;
+    FuzzedDataProvider mFdp;
 };
 
 std::unique_ptr<perfetto::TracingSession> FrameTracerFuzzer::getTracingSessionForTest() {
     perfetto::TraceConfig cfg;
-    cfg.set_duration_ms(kConfigDuration);
-    cfg.add_buffers()->set_size_kb(kBufferSize);
+    cfg.set_duration_ms(mFdp.ConsumeIntegralInRange<int32_t>(kMinRange, kConfigDuration));
+    cfg.add_buffers()->set_size_kb(mFdp.ConsumeIntegralInRange<int32_t>(kMinRange, kBufferSize));
     auto* dsCfg = cfg.add_data_sources()->mutable_config();
     dsCfg->set_name(android::FrameTracer::kFrameTracerDataSource);
 
-    auto tracingSession = perfetto::Tracing::NewTrace(perfetto::kInProcessBackend);
+    auto tracingSession =
+            perfetto::Tracing::NewTrace(mFdp.PickValueInArray<perfetto::BackendType>(backendTypes));
     tracingSession->Setup(cfg);
     return tracingSession;
 }
@@ -78,17 +86,23 @@
 }
 
 void FrameTracerFuzzer::traceTimestamp(std::vector<int32_t> layerIds, size_t numLayerIds) {
-    int32_t layerId = layerIds.at(mFdp.ConsumeIntegralInRange<size_t>(0, numLayerIds - 1));
+    uint32_t layerId = layerIds.at(mFdp.ConsumeIntegralInRange<size_t>(0, numLayerIds - 1));
+    android::FrameTracer::FrameEvent::BufferEventType type = static_cast<
+            android::FrameTracer::FrameEvent::BufferEventType>(
+            mFdp.ConsumeIntegralInRange<uint32_t>(android::FrameTracer::FrameEvent::UNSPECIFIED,
+                                                  android::FrameTracer::FrameEvent::CANCEL));
     mFrameTracer->traceTimestamp(layerId, mFdp.ConsumeIntegral<uint64_t>() /*bufferID*/,
                                  mFdp.ConsumeIntegral<uint64_t>() /*frameNumber*/,
-                                 mFdp.ConsumeIntegral<nsecs_t>() /*timestamp*/,
-                                 android::FrameTracer::FrameEvent::UNSPECIFIED,
+                                 mFdp.ConsumeIntegral<nsecs_t>() /*timestamp*/, type,
                                  mFdp.ConsumeIntegral<nsecs_t>() /*duration*/);
 }
 
 void FrameTracerFuzzer::traceFence(std::vector<int32_t> layerIds, size_t numLayerIds) {
-    const nsecs_t signalTime = systemTime();
-    const nsecs_t startTime = signalTime + kTimeOffset;
+    const nsecs_t signalTime =
+            mFdp.ConsumeBool() ? android::Fence::SIGNAL_TIME_PENDING : systemTime();
+    const nsecs_t startTime = (signalTime == android::Fence::SIGNAL_TIME_PENDING)
+            ? signalTime - kTimeOffset
+            : signalTime + kTimeOffset;
     auto fence = mFenceFactory.createFenceTimeForTest(android::Fence::NO_FENCE);
     mFenceFactory.signalAllForTest(android::Fence::NO_FENCE, signalTime);
     int32_t layerId = layerIds.at(mFdp.ConsumeIntegralInRange<size_t>(0, numLayerIds - 1));
@@ -98,26 +112,30 @@
 }
 
 void FrameTracerFuzzer::process() {
-    mFrameTracer->registerDataSource();
+    std::vector<int32_t> layerIds =
+            generateLayerIds(mFdp.ConsumeIntegralInRange<size_t>(kMinLayerIds, kMaxLayerIds));
 
-    auto tracingSession = getTracingSessionForTest();
-    tracingSession->StartBlocking();
-
-    size_t numLayerIds = mFdp.ConsumeIntegralInRange<size_t>(kMinLayerIds, kMaxLayerIds);
-    std::vector<int32_t> layerIds = generateLayerIds(numLayerIds);
-
-    for (auto it = layerIds.begin(); it != layerIds.end(); ++it) {
-        mFrameTracer->traceNewLayer(*it /*layerId*/,
-                                    mFdp.ConsumeRandomLengthString(kMaxStringSize) /*layerName*/);
+    while (mFdp.remaining_bytes()) {
+        auto invokeFrametracerAPI = mFdp.PickValueInArray<const std::function<void()>>({
+                [&]() { mFrameTracer->registerDataSource(); },
+                [&]() {
+                    auto tracingSession = getTracingSessionForTest();
+                    tracingSession->StartBlocking();
+                },
+                [&]() { traceTimestamp(layerIds, layerIds.size()); },
+                [&]() { traceFence(layerIds, layerIds.size()); },
+                [&]() {
+                    for (auto it = layerIds.begin(); it != layerIds.end(); ++it) {
+                        mFrameTracer->traceNewLayer(*it /*layerId*/,
+                                                    mFdp.ConsumeRandomLengthString(
+                                                            kMaxStringSize) /*layerName*/);
+                    }
+                },
+                [&]() { mFenceFactory.signalAllForTest(android::Fence::NO_FENCE, systemTime()); },
+        });
+        invokeFrametracerAPI();
     }
 
-    traceTimestamp(layerIds, numLayerIds);
-    traceFence(layerIds, numLayerIds);
-
-    mFenceFactory.signalAllForTest(android::Fence::NO_FENCE, systemTime());
-
-    tracingSession->StopBlocking();
-
     for (auto it = layerIds.begin(); it != layerIds.end(); ++it) {
         mFrameTracer->onDestroy(*it);
     }
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index 5a277bd..19d194f 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -32,14 +32,6 @@
 }
 
 flag {
-  name: "vrr_small_dirty_detection"
-  namespace: "core_graphics"
-  description: "Controls small dirty detection for VRR"
-  bug: "283055450"
-  is_fixed_read_only: true
-}
-
-flag {
   name: "dont_skip_on_early"
   namespace: "core_graphics"
   description: "This flag is guarding the behaviour where SurfaceFlinger is trying to opportunistically present a frame when the configuration change from late to early"
@@ -53,3 +45,11 @@
   bug: "259132483"
   is_fixed_read_only: true
 }
+
+flag {
+  name: "enable_small_area_detection"
+  namespace: "core_graphics"
+  description: "Feature flag for SmallAreaDetection"
+  bug: "283055450"
+  is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
index be29be4..0ad5ac9 100644
--- a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
+++ b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
@@ -495,3 +495,15 @@
     access: Readonly
     prop_name: "ro.surface_flinger.clear_slots_with_set_layer_buffer"
 }
+
+# Controls the default frame rate override of game applications. Ideally, game applications set
+# desired frame rate via setFrameRate() API. However, to cover the scenario when the game didn't
+# have a set frame rate, we introduce the default frame rate. The priority of this override is the
+# lowest among setFrameRate() and game intervention override.
+prop {
+    api_name: "game_default_frame_rate_override"
+    type: Integer
+    scope: Public
+    access: Readonly
+    prop_name: "ro.surface_flinger.game_default_frame_rate_override"
+}
diff --git a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
index ba88acc..0017300 100644
--- a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
+++ b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
@@ -65,6 +65,11 @@
     prop_name: "ro.surface_flinger.force_hwc_copy_for_virtual_displays"
   }
   prop {
+    api_name: "game_default_frame_rate_override"
+    type: Integer
+    prop_name: "ro.surface_flinger.game_default_frame_rate_override"
+  }
+  prop {
     api_name: "has_HDR_display"
     prop_name: "ro.surface_flinger.has_HDR_display"
   }
diff --git a/services/surfaceflinger/tests/DisplayConfigs_test.cpp b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
index 4be961b..0a951d4 100644
--- a/services/surfaceflinger/tests/DisplayConfigs_test.cpp
+++ b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
@@ -75,8 +75,8 @@
     setSpecs.allowGroupSwitching = false;
     for (size_t i = 0; i < modes.size(); i++) {
         setSpecs.defaultMode = modes[i].id;
-        setSpecs.primaryRanges.physical.min = modes[i].refreshRate;
-        setSpecs.primaryRanges.physical.max = modes[i].refreshRate;
+        setSpecs.primaryRanges.physical.min = modes[i].peakRefreshRate;
+        setSpecs.primaryRanges.physical.max = modes[i].peakRefreshRate;
         setSpecs.primaryRanges.render = setSpecs.primaryRanges.physical;
         setSpecs.appRequestRanges = setSpecs.primaryRanges;
         res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, setSpecs);
diff --git a/services/surfaceflinger/tests/LayerTransactionTest.h b/services/surfaceflinger/tests/LayerTransactionTest.h
index 2bdb8a4..9269e7c 100644
--- a/services/surfaceflinger/tests/LayerTransactionTest.h
+++ b/services/surfaceflinger/tests/LayerTransactionTest.h
@@ -299,7 +299,7 @@
         // After a new buffer is queued, SurfaceFlinger is notified and will
         // latch the new buffer on next vsync.  Let's heuristically wait for 3
         // vsyncs.
-        mBufferPostDelay = static_cast<int32_t>(1e6 / mode.refreshRate) * 3;
+        mBufferPostDelay = static_cast<int32_t>(1e6 / mode.peakRefreshRate) * 3;
 
         mBlackBgSurface =
                 createSurface(mClient, "BaseSurface", 0 /* buffer width */, 0 /* buffer height */,
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index 8a45f17..4f545a9 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -32,6 +32,7 @@
 
 #include <gui/LayerMetadata.h>
 #include <log/log.h>
+#include <chrono>
 
 #include "DisplayHardware/DisplayMode.h"
 #include "DisplayHardware/HWComposer.h"
@@ -44,11 +45,11 @@
 #pragma clang diagnostic pop // ignored "-Wconversion"
 
 namespace android {
-namespace {
 
 namespace V2_1 = hardware::graphics::composer::V2_1;
 namespace V2_4 = hardware::graphics::composer::V2_4;
 namespace aidl = aidl::android::hardware::graphics::composer3;
+using namespace std::chrono_literals;
 
 using Hwc2::Config;
 
@@ -77,6 +78,12 @@
         EXPECT_CALL(*mHal, setVsyncEnabled(hwcDisplayId, Hwc2::IComposerClient::Vsync::DISABLE));
         EXPECT_CALL(*mHal, onHotplugConnect(hwcDisplayId));
     }
+
+    void setDisplayData(HalDisplayId displayId, nsecs_t lastExpectedPresentTimestamp) {
+        ASSERT_TRUE(mHwc.mDisplayData.find(displayId) != mHwc.mDisplayData.end());
+        auto& displayData = mHwc.mDisplayData.at(displayId);
+        displayData.lastExpectedPresentTimestamp = lastExpectedPresentTimestamp;
+    }
 };
 
 TEST_F(HWComposerTest, isHeadless) {
@@ -227,12 +234,19 @@
         constexpr int32_t kHeight = 720;
         constexpr int32_t kConfigGroup = 1;
         constexpr int32_t kVsyncPeriod = 16666667;
-        hal::DisplayConfiguration displayConfiguration;
-        displayConfiguration.configId = kConfigId;
-        displayConfiguration.configGroup = kConfigGroup;
-        displayConfiguration.height = kHeight;
-        displayConfiguration.width = kWidth;
-        displayConfiguration.vsyncPeriod = kVsyncPeriod;
+        const hal::VrrConfig vrrConfig =
+                hal::VrrConfig{.minFrameIntervalNs = static_cast<Fps>(120_Hz).getPeriodNsecs(),
+                               .notifyExpectedPresentConfig = hal::VrrConfig::
+                                       NotifyExpectedPresentConfig{.notifyExpectedPresentHeadsUpNs =
+                                                                           ms2ns(30),
+                                                                   .notifyExpectedPresentTimeoutNs =
+                                                                           ms2ns(30)}};
+        hal::DisplayConfiguration displayConfiguration{.configId = kConfigId,
+                                                       .width = kWidth,
+                                                       .height = kHeight,
+                                                       .configGroup = kConfigGroup,
+                                                       .vsyncPeriod = kVsyncPeriod,
+                                                       .vrrConfig = vrrConfig};
 
         EXPECT_CALL(*mHal, getDisplayConfigurations(kHwcDisplayId, _, _))
                 .WillOnce(DoAll(SetArgPointee<2>(std::vector<hal::DisplayConfiguration>{
@@ -247,6 +261,7 @@
         EXPECT_EQ(modes.front().height, kHeight);
         EXPECT_EQ(modes.front().configGroup, kConfigGroup);
         EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
+        EXPECT_EQ(modes.front().vrrConfig, vrrConfig);
         EXPECT_EQ(modes.front().dpiX, -1);
         EXPECT_EQ(modes.front().dpiY, -1);
 
@@ -266,6 +281,7 @@
         EXPECT_EQ(modes.front().height, kHeight);
         EXPECT_EQ(modes.front().configGroup, kConfigGroup);
         EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
+        EXPECT_EQ(modes.front().vrrConfig, vrrConfig);
         EXPECT_EQ(modes.front().dpiX, kDpi);
         EXPECT_EQ(modes.front().dpiY, kDpi);
     }
@@ -299,6 +315,55 @@
     EXPECT_FALSE(displayIdOpt);
 }
 
+TEST_F(HWComposerTest, notifyExpectedPresentTimeout) {
+    constexpr hal::HWDisplayId kHwcDisplayId = 2;
+    expectHotplugConnect(kHwcDisplayId);
+    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+    ASSERT_TRUE(info);
+
+    auto expectedPresentTime = systemTime() + ms2ns(10);
+    const int32_t frameIntervalNs = static_cast<Fps>(60_Hz).getPeriodNsecs();
+    static constexpr nsecs_t kTimeoutNs = ms2ns(30);
+
+    ASSERT_NO_FATAL_FAILURE(setDisplayData(info->id, /* lastExpectedPresentTimestamp= */ 0));
+
+    {
+        // Very first ExpectedPresent after idle, no previous timestamp
+        EXPECT_CALL(*mHal,
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, frameIntervalNs))
+                .WillOnce(Return(HalError::NONE));
+        mHwc.notifyExpectedPresentIfRequired(info->id, expectedPresentTime, frameIntervalNs,
+                                             kTimeoutNs);
+    }
+    {
+        // ExpectedPresent is after the timeoutNs
+        expectedPresentTime += ms2ns(50);
+        EXPECT_CALL(*mHal,
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, frameIntervalNs))
+                .WillOnce(Return(HalError::NONE));
+        mHwc.notifyExpectedPresentIfRequired(info->id, expectedPresentTime, frameIntervalNs,
+                                             kTimeoutNs);
+    }
+    {
+        // ExpectedPresent is after the last reported ExpectedPresent.
+        expectedPresentTime += ms2ns(10);
+        EXPECT_CALL(*mHal, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
+        mHwc.notifyExpectedPresentIfRequired(info->id, expectedPresentTime, frameIntervalNs,
+                                             kTimeoutNs);
+    }
+    {
+        // ExpectedPresent is before the last reported ExpectedPresent but after the timeoutNs,
+        // representing we changed our decision and want to present earlier than previously
+        // reported.
+        expectedPresentTime -= ms2ns(20);
+        EXPECT_CALL(*mHal,
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, frameIntervalNs))
+                .WillOnce(Return(HalError::NONE));
+        mHwc.notifyExpectedPresentIfRequired(info->id, expectedPresentTime, frameIntervalNs,
+                                             kTimeoutNs);
+    }
+}
+
 struct MockHWC2ComposerCallback final : StrictMock<HWC2::ComposerCallback> {
     MOCK_METHOD2(onComposerHalHotplug, void(hal::HWDisplayId, hal::Connection));
     MOCK_METHOD1(onComposerHalRefresh, void(hal::HWDisplayId));
@@ -423,5 +488,4 @@
     EXPECT_EQ(hal::Error::UNSUPPORTED, result);
 }
 
-} // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index 7f3171f..48f8923 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -421,6 +421,17 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
+    void setDamageRegion(uint32_t id, const Region& damageRegion) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eSurfaceDamageRegionChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.surfaceDamageRegion = damageRegion;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
     void setDataspace(uint32_t id, ui::Dataspace dataspace) {
         std::vector<TransactionState> transactions;
         transactions.emplace_back();
@@ -432,6 +443,19 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
+    void setMatrix(uint32_t id, float dsdx, float dtdx, float dtdy, float dsdy) {
+        layer_state_t::matrix22_t matrix{dsdx, dtdx, dtdy, dsdy};
+
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eMatrixChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.matrix = matrix;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
     void setShadowRadius(uint32_t id, float shadowRadius) {
         std::vector<TransactionState> transactions;
         transactions.emplace_back();
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index a462082..631adf1 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -866,6 +866,104 @@
     }
 }
 
+class SmallAreaDetectionTest : public LayerHistoryIntegrationTest {
+protected:
+    static constexpr int32_t DISPLAY_WIDTH = 100;
+    static constexpr int32_t DISPLAY_HEIGHT = 100;
+
+    static constexpr int32_t kAppId1 = 10100;
+    static constexpr int32_t kAppId2 = 10101;
+
+    static constexpr float kThreshold1 = 0.05f;
+    static constexpr float kThreshold2 = 0.07f;
+
+    SmallAreaDetectionTest() : LayerHistoryIntegrationTest() {
+        std::vector<std::pair<int32_t, float>> mappings;
+        mappings.reserve(2);
+        mappings.push_back(std::make_pair(kAppId1, kThreshold1));
+        mappings.push_back(std::make_pair(kAppId2, kThreshold2));
+
+        mFlinger.enableNewFrontEnd();
+
+        mScheduler->onActiveDisplayAreaChanged(DISPLAY_WIDTH * DISPLAY_HEIGHT);
+        mScheduler->updateSmallAreaDetection(mappings);
+    }
+
+    auto createLegacyAndFrontedEndLayer(uint32_t sequence) {
+        std::string layerName = "test layer:" + std::to_string(sequence);
+
+        LayerCreationArgs args = LayerCreationArgs{mFlinger.flinger(),
+                                                   nullptr,
+                                                   layerName,
+                                                   0,
+                                                   {},
+                                                   std::make_optional<uint32_t>(sequence)};
+        args.ownerUid = kAppId1;
+        args.metadata.setInt32(gui::METADATA_WINDOW_TYPE, 2); // APPLICATION
+        const auto layer = sp<Layer>::make(args);
+        mFlinger.injectLegacyLayer(layer);
+        createRootLayer(sequence);
+        return layer;
+    }
+};
+
+TEST_F(SmallAreaDetectionTest, SmallDirtyLayer) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    uint32_t sequence = static_cast<uint32_t>(layer->sequence);
+    setBuffer(sequence);
+    setDamageRegion(sequence, Region(Rect(10, 10)));
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    ASSERT_EQ(true, mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->isSmallDirty);
+}
+
+TEST_F(SmallAreaDetectionTest, NotSmallDirtyLayer) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    uint32_t sequence = static_cast<uint32_t>(layer->sequence);
+    setBuffer(sequence);
+    setDamageRegion(sequence, Region(Rect(50, 50)));
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    ASSERT_EQ(false, mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->isSmallDirty);
+}
+
+TEST_F(SmallAreaDetectionTest, smallDirtyLayerWithMatrix) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    // Original damage region is a small dirty.
+    uint32_t sequence = static_cast<uint32_t>(layer->sequence);
+    setBuffer(sequence);
+    setDamageRegion(sequence, Region(Rect(20, 20)));
+    updateLayerSnapshotsAndLayerHistory(time);
+    ASSERT_EQ(true, mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->isSmallDirty);
+
+    setMatrix(sequence, 2.0f, 0, 0, 2.0f);
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    // Verify if the small dirty is scaled.
+    ASSERT_EQ(false, mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->isSmallDirty);
+}
+
 INSTANTIATE_TEST_CASE_P(LeapYearTests, LayerHistoryIntegrationTestParameterized,
                         ::testing::Values(1s, 2s, 3s, 4s, 5s));
 
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index fc4bb22..e784eb7 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -282,17 +282,7 @@
     // │   └── 13
     // └── 2
 
-    std::vector<TransactionState> transactions;
-    transactions.emplace_back();
-    transactions.back().states.push_back({});
-    transactions.back().states.front().state.what = layer_state_t::eFrameRateChanged;
-    transactions.back().states.front().state.frameRate = 90.0;
-    transactions.back().states.front().state.frameRateCompatibility =
-            ANATIVEWINDOW_FRAME_RATE_EXACT;
-    transactions.back().states.front().state.changeFrameRateStrategy =
-            ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS;
-    transactions.back().states.front().layerId = 11;
-    mLifecycleManager.applyTransactions(transactions);
+    setFrameRate(11, 90.0, ANATIVEWINDOW_FRAME_RATE_EXACT, ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS);
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
 
     EXPECT_EQ(getSnapshot(11)->frameRate.vote.rate.getIntValue(), 90);
@@ -303,6 +293,42 @@
     EXPECT_EQ(getSnapshot(1)->frameRate.vote.type, scheduler::FrameRateCompatibility::NoVote);
 }
 
+TEST_F(LayerSnapshotTest, NoLayerVoteForParentWithChildVotesDoesNotAffectSiblings) {
+    // ROOT
+    // ├── 1 (verify layer has no vote)
+    // │   ├── 11 (frame rate set)
+    // │   │   └── 111
+    // │   ├── 12 (frame rate set)
+    // │   │   ├── 121
+    // │   │   └── 122
+    // │   │       └── 1221
+    // │   └── 13 (verify layer has default vote)
+    // └── 2
+
+    setFrameRate(11, 90.0, ANATIVEWINDOW_FRAME_RATE_EXACT, ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS);
+    setFrameRate(12, 45.0, ANATIVEWINDOW_FRAME_RATE_EXACT, ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    EXPECT_EQ(getSnapshot(11)->frameRate.vote.rate.getIntValue(), 90);
+    EXPECT_EQ(getSnapshot(11)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+    EXPECT_EQ(getSnapshot(111)->frameRate.vote.rate.getIntValue(), 90);
+    EXPECT_EQ(getSnapshot(111)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+    EXPECT_EQ(getSnapshot(12)->frameRate.vote.rate.getIntValue(), 45);
+    EXPECT_EQ(getSnapshot(12)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+    EXPECT_EQ(getSnapshot(121)->frameRate.vote.rate.getIntValue(), 45);
+    EXPECT_EQ(getSnapshot(121)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+    EXPECT_EQ(getSnapshot(1221)->frameRate.vote.rate.getIntValue(), 45);
+    EXPECT_EQ(getSnapshot(1221)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+
+    EXPECT_EQ(getSnapshot(1)->frameRate.vote.rate.getIntValue(), 0);
+    EXPECT_EQ(getSnapshot(1)->frameRate.vote.type, scheduler::FrameRateCompatibility::NoVote);
+    EXPECT_EQ(getSnapshot(13)->frameRate.vote.rate.getIntValue(), 0);
+    EXPECT_EQ(getSnapshot(13)->frameRate.vote.type, scheduler::FrameRateCompatibility::Default);
+    EXPECT_EQ(getSnapshot(2)->frameRate.vote.rate.getIntValue(), 0);
+    EXPECT_EQ(getSnapshot(2)->frameRate.vote.type, scheduler::FrameRateCompatibility::Default);
+}
+
 TEST_F(LayerSnapshotTest, CanCropTouchableRegion) {
     // ROOT
     // ├── 1
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 014d07c..3d1c900 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -44,7 +44,10 @@
     TestableScheduler(std::unique_ptr<VsyncController> controller,
                       std::shared_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr,
                       sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback)
-          : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) {
+          : Scheduler(*this, callback,
+                      (FeatureFlags)Feature::kContentDetection |
+                              Feature::kSmallDirtyContentDetection,
+                      std::move(modulatorPtr)) {
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplay(displayId, std::move(selectorPtr), std::move(controller),
                         std::move(tracker));
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 3b39420..8f1982d 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -670,6 +670,11 @@
         return mFlinger->initTransactionTraceWriter();
     }
 
+    void enableNewFrontEnd() {
+        mFlinger->mLayerLifecycleManagerEnabled = true;
+        mFlinger->mLegacyFrontEndEnabled = false;
+    }
+
     ~TestableSurfaceFlinger() {
         // All these pointer and container clears help ensure that GMock does
         // not report a leaked object, since the SurfaceFlinger instance may
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index b8fdce1..1dc5498 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -759,6 +759,8 @@
 
 // b/1450138150
 TEST_F(VSyncDispatchTimerQueueTest, doesNotMoveCallbackBackwardsAndSkipAScheduledTargetVSync) {
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false);
+
     EXPECT_CALL(mMockClock, alarmAt(_, 500));
     CountingCallback cb(mDispatch);
     auto result =
@@ -772,6 +774,29 @@
                                  {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1200, *result);
+
+    advanceToNextCallback();
+    ASSERT_THAT(cb.mCalls.size(), Eq(1));
+}
+
+// b/1450138150
+TEST_F(VSyncDispatchTimerQueueTest, movesCallbackBackwardsAndSkipAScheduledTargetVSync) {
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
+
+    EXPECT_CALL(mMockClock, alarmAt(_, 500));
+    CountingCallback cb(mDispatch);
+    auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(500, *result);
+    mMockClock.advanceBy(400);
+
+    result = mDispatch->schedule(cb,
+                                 {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(400, *result);
+
     advanceToNextCallback();
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
 }
@@ -826,6 +851,8 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesNotAffectSchedulingState) {
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false);
+
     EXPECT_CALL(mMockClock, alarmAt(_, 600));
 
     CountingCallback cb(mDispatch);
@@ -843,6 +870,26 @@
     advanceToNextCallback();
 }
 
+TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesAffectSchedulingState) {
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
+
+    EXPECT_CALL(mMockClock, alarmAt(_, 600));
+
+    CountingCallback cb(mDispatch);
+    auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(600, *result);
+
+    result = mDispatch->schedule(cb,
+                                 {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(0, *result);
+
+    advanceToNextCallback();
+}
+
 TEST_F(VSyncDispatchTimerQueueTest, helperMove) {
     EXPECT_CALL(mMockClock, alarmAt(_, 500)).Times(1);
     EXPECT_CALL(mMockClock, alarmCancel()).Times(1);
@@ -1045,6 +1092,8 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, updatesVsyncTimeForCloseWakeupTime) {
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false);
+
     Sequence seq;
     EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
 
@@ -1065,6 +1114,29 @@
     EXPECT_THAT(cb.mReadyTime[0], Eq(2000));
 }
 
+TEST_F(VSyncDispatchTimerQueueTest, doesNotUpdatesVsyncTimeForCloseWakeupTime) {
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
+
+    Sequence seq;
+    EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
+
+    CountingCallback cb(mDispatch);
+
+    mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+
+    advanceToNextCallback();
+
+    advanceToNextCallback();
+
+    ASSERT_THAT(cb.mCalls.size(), Eq(1));
+    EXPECT_THAT(cb.mCalls[0], Eq(1000));
+    ASSERT_THAT(cb.mWakeupTime.size(), Eq(1));
+    EXPECT_THAT(cb.mWakeupTime[0], Eq(600));
+    ASSERT_THAT(cb.mReadyTime.size(), Eq(1));
+    EXPECT_THAT(cb.mReadyTime[0], Eq(1000));
+}
+
 TEST_F(VSyncDispatchTimerQueueTest, skipAVsyc) {
     SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false);
 
@@ -1101,7 +1173,7 @@
     result = mDispatch->schedule(cb,
                                  {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(200, *result);
+    EXPECT_EQ(300, *result);
 
     advanceToNextCallback();
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 95004a4..0b07745 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -178,6 +178,7 @@
     MOCK_METHOD1(onHotplugConnect, void(Display));
     MOCK_METHOD1(onHotplugDisconnect, void(Display));
     MOCK_METHOD(Error, setRefreshRateChangedCallbackDebugEnabled, (Display, bool));
+    MOCK_METHOD(Error, notifyExpectedPresent, (Display, nsecs_t, int32_t));
 };
 
 } // namespace Hwc2::mock