Merge "MSKP capture: write to a different directory" into main
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index e2a2927..1123d4f 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -250,12 +250,18 @@
 
 // we could have tighter checks, but this is only to avoid hard errors. Negative values are defined
 // in UserHandle.java and carry specific meanings that may not be handled by certain APIs here.
-#define ENFORCE_VALID_USER(userId)                                     \
-    {                                                                  \
-        if (static_cast<uid_t>(std::abs(userId)) >=                    \
-            std::numeric_limits<uid_t>::max() / AID_USER_OFFSET) {     \
-            return error("userId invalid: " + std::to_string(userId)); \
-        }                                                              \
+#define ENFORCE_VALID_USER(userId)                                                               \
+    {                                                                                            \
+        if (static_cast<uid_t>(userId) >= std::numeric_limits<uid_t>::max() / AID_USER_OFFSET) { \
+            return error("userId invalid: " + std::to_string(userId));                           \
+        }                                                                                        \
+    }
+
+#define ENFORCE_VALID_USER_OR_NULL(userId)             \
+    {                                                  \
+        if (static_cast<uid_t>(userId) != USER_NULL) { \
+            ENFORCE_VALID_USER(userId);                \
+        }                                              \
     }
 
 #define CHECK_ARGUMENT_UUID(uuid) {                         \
@@ -3841,7 +3847,7 @@
         int32_t userId, int32_t appId, const std::string& profileName, const std::string& codePath,
         const std::optional<std::string>& dexMetadata, bool* _aidl_return) {
     ENFORCE_UID(AID_SYSTEM);
-    ENFORCE_VALID_USER(userId);
+    ENFORCE_VALID_USER_OR_NULL(userId);
     CHECK_ARGUMENT_PACKAGE_NAME(packageName);
     CHECK_ARGUMENT_PATH(codePath);
     LOCK_PACKAGE_USER();
diff --git a/libs/binder/tests/parcel_fuzzer/test_fuzzer/Android.bp b/libs/binder/tests/parcel_fuzzer/test_fuzzer/Android.bp
index 96092b1..690c39a 100644
--- a/libs/binder/tests/parcel_fuzzer/test_fuzzer/Android.bp
+++ b/libs/binder/tests/parcel_fuzzer/test_fuzzer/Android.bp
@@ -36,8 +36,8 @@
         triage_assignee: "waghpawan@google.com",
 
         // This fuzzer should be used only test fuzzService locally
-        fuzz_on_haiku_host: true,
-        fuzz_on_haiku_device: true,
+        fuzz_on_haiku_host: false,
+        fuzz_on_haiku_device: false,
     },
 }
 
diff --git a/libs/binder/tests/parcel_fuzzer/test_fuzzer/run_fuzz_service_test.sh b/libs/binder/tests/parcel_fuzzer/test_fuzzer/run_fuzz_service_test.sh
index c447bff..5d68fe1 100755
--- a/libs/binder/tests/parcel_fuzzer/test_fuzzer/run_fuzz_service_test.sh
+++ b/libs/binder/tests/parcel_fuzzer/test_fuzzer/run_fuzz_service_test.sh
@@ -30,7 +30,7 @@
 for CRASH_TYPE in PLAIN KNOWN_UID AID_SYSTEM AID_ROOT BINDER DUMP SHELL_CMD; do
     echo "INFO: Running fuzzer : test_service_fuzzer_should_crash $CRASH_TYPE"
 
-    ./test_service_fuzzer_should_crash "$CRASH_TYPE" -max_total_time=30 &>"$FUZZER_OUT"
+    ./test_service_fuzzer_should_crash "$CRASH_TYPE" -max_total_time=60 &>"$FUZZER_OUT"
 
     echo "INFO: Searching fuzzer output for expected crashes"
     if grep -q "Expected crash, $CRASH_TYPE." "$FUZZER_OUT"
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 298838d..2ea4d16 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -192,6 +192,18 @@
     },
 }
 
+aconfig_declarations {
+    name: "libgui_flags",
+    package: "com.android.graphics.libgui.flags",
+    srcs: ["libgui_flags.aconfig"],
+}
+
+cc_aconfig_library {
+    name: "libguiflags",
+    vendor_available: true,
+    aconfig_declarations: "libgui_flags",
+}
+
 cc_library_shared {
     name: "libgui",
     vendor_available: true,
@@ -206,6 +218,7 @@
     static_libs: [
         "libgui_aidl_static",
         "libgui_window_info_static",
+        "libguiflags",
     ],
     export_static_lib_headers: [
         "libgui_aidl_static",
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 2cf5123..8c36058 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -267,8 +267,8 @@
             layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBlurRegionsChanged |
             layer_state_t::eColorTransformChanged | layer_state_t::eCornerRadiusChanged |
             layer_state_t::eFlagsChanged | layer_state_t::eTrustedOverlayChanged |
-            layer_state_t::eFrameRateChanged | layer_state_t::eFrameRateSelectionPriority |
-            layer_state_t::eFixedTransformHintChanged;
+            layer_state_t::eFrameRateChanged | layer_state_t::eFrameRateCategoryChanged |
+            layer_state_t::eFrameRateSelectionPriority | layer_state_t::eFixedTransformHintChanged;
 
     // Changes affecting data sent to input.
     static constexpr uint64_t INPUT_CHANGES = layer_state_t::eInputInfoChanged |
diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig
new file mode 100644
index 0000000..a16be78
--- /dev/null
+++ b/libs/gui/libgui_flags.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.graphics.libgui.flags"
+
+flag {
+  name: "bq_setframerate"
+  namespace: "core_graphics"
+  description: "This flag controls plumbing setFrameRate thru BufferQueue"
+  bug: "281695725"
+  is_fixed_read_only: true
+}
+
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 8656b26..36a01d3 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -33,6 +33,21 @@
     ],
 }
 
+/////////////////////////////////////////////////
+// flags
+/////////////////////////////////////////////////
+aconfig_declarations {
+    name: "aconfig_input_flags",
+    package: "com.android.input.flags",
+    srcs: ["input_flags.aconfig"],
+}
+
+cc_aconfig_library {
+    name: "aconfig_input_flags_c_lib",
+    aconfig_declarations: "aconfig_input_flags",
+    host_supported: true,
+}
+
 aidl_interface {
     name: "inputconstants",
     host_supported: true,
@@ -176,6 +191,7 @@
         "libtinyxml2",
         "libutils",
         "libvintf",
+        "server_configurable_flags",
     ],
 
     ldflags: [
@@ -196,6 +212,7 @@
     ],
 
     whole_static_libs: [
+        "aconfig_input_flags_c_lib",
         "libinput_rust_ffi",
     ],
 
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index bbbebf7..1f14396 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -23,9 +23,12 @@
 #include <log/log.h>
 #include <utils/Trace.h>
 
+#include <com_android_input_flags.h>
 #include <input/InputTransport.h>
 #include <input/TraceTools.h>
 
+namespace input_flags = com::android::input::flags;
+
 namespace {
 
 /**
@@ -139,7 +142,8 @@
  * Enable this via "adb shell setprop log.tag.InputTransportVerifyEvents DEBUG"
  */
 static bool verifyEvents() {
-    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VerifyEvents", ANDROID_LOG_INFO);
+    return input_flags::enable_outbound_event_verification() ||
+            __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VerifyEvents", ANDROID_LOG_INFO);
 }
 
 template<typename T>
diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index b5a5e72..c8d1da7 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -181,7 +181,8 @@
     int64_t predictionTime = mBuffers->lastTimestamp();
     const int64_t futureTime = timestamp + mPredictionTimestampOffsetNanos;
 
-    for (int i = 0; i < predictedR.size() && predictionTime <= futureTime; ++i) {
+    for (size_t i = 0; i < static_cast<size_t>(predictedR.size()) && predictionTime <= futureTime;
+         ++i) {
         if (predictedR[i] < mModel->config().distanceNoiseFloor) {
             // Stop predicting when the predicted output is below the model's noise floor.
             //
@@ -198,7 +199,7 @@
         const TfLiteMotionPredictorSample::Point predictedPoint =
                 convertPrediction(axisFrom, axisTo, predictedR[i], predictedPhi[i]);
 
-        ALOGD_IF(isDebug(), "prediction %d: %f, %f", i, predictedPoint.x, predictedPoint.y);
+        ALOGD_IF(isDebug(), "prediction %zu: %f, %f", i, predictedPoint.x, predictedPoint.y);
         PointerCoords coords;
         coords.clear();
         coords.setAxisValue(AMOTION_EVENT_AXIS_X, predictedPoint.x);
diff --git a/libs/input/TfLiteMotionPredictor.cpp b/libs/input/TfLiteMotionPredictor.cpp
index 5984b4d3..d17476e 100644
--- a/libs/input/TfLiteMotionPredictor.cpp
+++ b/libs/input/TfLiteMotionPredictor.cpp
@@ -143,8 +143,7 @@
                         tensor->name, TfLiteTypeGetName(tensor->type), TfLiteTypeGetName(type));
 
     LOG_ALWAYS_FATAL_IF(!tensor->data.data);
-    return {reinterpret_cast<T*>(tensor->data.data),
-            static_cast<typename std::span<T>::index_type>(tensor->bytes / sizeof(T))};
+    return std::span<T>(reinterpret_cast<T*>(tensor->data.data), tensor->bytes / sizeof(T));
 }
 
 // Verifies that a tensor exists and has an underlying buffer of type T.
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
new file mode 100644
index 0000000..2af5a4a
--- /dev/null
+++ b/libs/input/input_flags.aconfig
@@ -0,0 +1,22 @@
+package: "com.android.input.flags"
+
+flag {
+  name: "enable_outbound_event_verification"
+  namespace: "input"
+  description: "Set to true to enable crashing whenever bad outbound events are detected inside InputTransport"
+  bug: "271455682"
+}
+
+flag {
+  name: "enable_inbound_event_verification"
+  namespace: "input"
+  description: "Set to true to enable crashing whenever bad inbound events are going into InputDispatcher"
+  bug: "271455682"
+}
+
+flag {
+  name: "enable_pointer_choreographer"
+  namespace: "input"
+  description: "Set to true to enable PointerChoreographer: the new pipeline for showing pointer icons"
+  bug: "293587049"
+}
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index e7224ff..138898f 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -62,6 +62,7 @@
         "libtinyxml2",
         "libutils",
         "libvintf",
+        "server_configurable_flags",
     ],
     data: [
         "data/*",
diff --git a/libs/nativewindow/include/android/hardware_buffer_aidl.h b/libs/nativewindow/include/android/hardware_buffer_aidl.h
index e269f0d..3f77c78 100644
--- a/libs/nativewindow/include/android/hardware_buffer_aidl.h
+++ b/libs/nativewindow/include/android/hardware_buffer_aidl.h
@@ -95,14 +95,22 @@
 
     binder_status_t readFromParcel(const AParcel* _Nonnull parcel) {
         reset();
-        return AHardwareBuffer_readFromParcel(parcel, &mBuffer);
+        if (__builtin_available(android __ANDROID_API_U__, *)) {
+            return AHardwareBuffer_readFromParcel(parcel, &mBuffer);
+        } else {
+            return STATUS_FAILED_TRANSACTION;
+        }
     }
 
     binder_status_t writeToParcel(AParcel* _Nonnull parcel) const {
         if (!mBuffer) {
             return STATUS_BAD_VALUE;
         }
-        return AHardwareBuffer_writeToParcel(mBuffer, parcel);
+        if (__builtin_available(android __ANDROID_API_U__, *)) {
+            return AHardwareBuffer_writeToParcel(mBuffer, parcel);
+        } else {
+            return STATUS_FAILED_TRANSACTION;
+        }
     }
 
     /**
@@ -150,9 +158,13 @@
         if (!mBuffer) {
             return "<HardwareBuffer: Invalid>";
         }
-        uint64_t id = 0;
-        AHardwareBuffer_getId(mBuffer, &id);
-        return "<HardwareBuffer " + std::to_string(id) + ">";
+        if (__builtin_available(android __ANDROID_API_S__, *)) {
+            uint64_t id = 0;
+            AHardwareBuffer_getId(mBuffer, &id);
+            return "<HardwareBuffer " + std::to_string(id) + ">";
+        } else {
+            return "<HardwareBuffer (unknown)>";
+        }
     }
 
 private:
diff --git a/opengl/libs/EGL/egl_angle_platform.cpp b/opengl/libs/EGL/egl_angle_platform.cpp
index 9a6bb7a..ee605c2 100644
--- a/opengl/libs/EGL/egl_angle_platform.cpp
+++ b/opengl/libs/EGL/egl_angle_platform.cpp
@@ -35,6 +35,7 @@
 
 namespace angle {
 
+constexpr char kAngleEs2Lib[] = "libGLESv2_angle.so";
 constexpr int kAngleDlFlags = RTLD_LOCAL | RTLD_NOW;
 
 static GetDisplayPlatformFunc angleGetDisplayPlatform = nullptr;
@@ -115,8 +116,6 @@
     android_namespace_t* ns = android::GraphicsEnv::getInstance().getAngleNamespace();
     void* so = nullptr;
     if (ns) {
-        // Loading from an APK, so hard-code the suffix to "_angle".
-        constexpr char kAngleEs2Lib[] = "libGLESv2_angle.so";
         const android_dlextinfo dlextinfo = {
                 .flags = ANDROID_DLEXT_USE_NAMESPACE,
                 .library_namespace = ns,
@@ -130,19 +129,11 @@
         }
     } else {
         // If we are here, ANGLE is loaded as built-in gl driver in the sphal.
-        // Get the specified ANGLE library filename suffix.
-        std::string angleEs2LibSuffix = android::base::GetProperty("ro.hardware.egl", "");
-        if (angleEs2LibSuffix.empty()) {
-            ALOGE("%s failed to get valid ANGLE library filename suffix!", __FUNCTION__);
-            return false;
-        }
-
-        std::string angleEs2LibName = "libGLESv2_" + angleEs2LibSuffix + ".so";
-        so = android_load_sphal_library(angleEs2LibName.c_str(), kAngleDlFlags);
+        so = android_load_sphal_library(kAngleEs2Lib, kAngleDlFlags);
         if (so) {
-            ALOGD("dlopen (%s) success at %p", angleEs2LibName.c_str(), so);
+            ALOGD("dlopen (%s) success at %p", kAngleEs2Lib, so);
         } else {
-            ALOGE("%s failed to dlopen %s!", __FUNCTION__, angleEs2LibName.c_str());
+            ALOGE("%s failed to dlopen %s: %s!", __FUNCTION__, kAngleEs2Lib, dlerror());
             return false;
         }
     }
diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp
index 0567a32..92c65e1 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -27,10 +27,13 @@
 #include <android/binder_interface_utils.h>
 #include <android/sysprop/InputProperties.sysprop.h>
 #include <binder/IPCThreadState.h>
+#include <com_android_input_flags.h>
 #include <inputflinger_bootstrap.rs.h>
 #include <log/log.h>
 #include <private/android_filesystem_config.h>
 
+namespace input_flags = com::android::input::flags;
+
 namespace android {
 
 namespace {
@@ -38,8 +41,7 @@
 const bool ENABLE_INPUT_DEVICE_USAGE_METRICS =
         sysprop::InputProperties::enable_input_device_usage_metrics().value_or(true);
 
-const bool ENABLE_POINTER_CHOREOGRAPHER =
-        sysprop::InputProperties::enable_pointer_choreographer().value_or(false);
+const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer();
 
 int32_t exceptionCodeFromStatusT(status_t status) {
     switch (status) {
diff --git a/services/inputflinger/dispatcher/DebugConfig.h b/services/inputflinger/dispatcher/DebugConfig.h
index 7a41d68..c7d98ab 100644
--- a/services/inputflinger/dispatcher/DebugConfig.h
+++ b/services/inputflinger/dispatcher/DebugConfig.h
@@ -19,6 +19,9 @@
 #define LOG_TAG "InputDispatcher"
 
 #include <android-base/logging.h>
+#include <com_android_input_flags.h>
+
+namespace input_flags = com::android::input::flags;
 
 namespace android::inputdispatcher {
 
@@ -102,7 +105,7 @@
  * Crash if a bad stream from InputListener is detected.
  * Enable this via "adb shell setprop log.tag.InputDispatcherVerifyEvents DEBUG" (requires restart)
  */
-const bool DEBUG_VERIFY_EVENTS =
+const bool DEBUG_VERIFY_EVENTS = input_flags::enable_inbound_event_verification() ||
         android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "VerifyEvents");
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/LatencyAggregator.cpp b/services/inputflinger/dispatcher/LatencyAggregator.cpp
index 96d78c3..e09d97a 100644
--- a/services/inputflinger/dispatcher/LatencyAggregator.cpp
+++ b/services/inputflinger/dispatcher/LatencyAggregator.cpp
@@ -126,6 +126,7 @@
 }
 
 void LatencyAggregator::processStatistics(const InputEventTimeline& timeline) {
+    std::scoped_lock lock(mLock);
     // Before we do any processing, check that we have not yet exceeded MAX_SIZE
     if (mNumSketchEventsProcessed >= MAX_EVENTS_FOR_STATISTICS) {
         return;
@@ -167,6 +168,7 @@
 }
 
 AStatsManager_PullAtomCallbackReturn LatencyAggregator::pullData(AStatsEventList* data) {
+    std::scoped_lock lock(mLock);
     std::array<std::unique_ptr<SafeBytesField>, SketchIndex::SIZE> serializedDownData;
     std::array<std::unique_ptr<SafeBytesField>, SketchIndex::SIZE> serializedMoveData;
     for (size_t i = 0; i < SketchIndex::SIZE; i++) {
@@ -257,6 +259,7 @@
 }
 
 std::string LatencyAggregator::dump(const char* prefix) const {
+    std::scoped_lock lock(mLock);
     std::string sketchDump = StringPrintf("%s  Sketches:\n", prefix);
     for (size_t i = 0; i < SketchIndex::SIZE; i++) {
         const int64_t numDown = mDownSketches[i]->num_values();
diff --git a/services/inputflinger/dispatcher/LatencyAggregator.h b/services/inputflinger/dispatcher/LatencyAggregator.h
index 60b6813..d6d1686 100644
--- a/services/inputflinger/dispatcher/LatencyAggregator.h
+++ b/services/inputflinger/dispatcher/LatencyAggregator.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <android-base/thread_annotations.h>
 #include <kll.h>
 #include <statslog.h>
 #include <utils/Timers.h>
@@ -61,10 +62,13 @@
     ~LatencyAggregator();
 
 private:
+    // Binder call -- called on a binder thread. This is different from the thread where the rest of
+    // the public API is called.
     static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atom_tag,
                                                                  AStatsEventList* data,
                                                                  void* cookie);
     AStatsManager_PullAtomCallbackReturn pullData(AStatsEventList* data);
+
     // ---------- Slow event handling ----------
     void processSlowEvent(const InputEventTimeline& timeline);
     nsecs_t mLastSlowEventTime = 0;
@@ -74,14 +78,17 @@
     size_t mNumEventsSinceLastSlowEventReport = 0;
 
     // ---------- Statistics handling ----------
+    // Statistics is pulled rather than pushed. It's pulled on a binder thread, and therefore will
+    // be accessed by two different threads. The lock is needed to protect the pulled data.
+    mutable std::mutex mLock;
     void processStatistics(const InputEventTimeline& timeline);
     // Sketches
     std::array<std::unique_ptr<dist_proc::aggregation::KllQuantile>, SketchIndex::SIZE>
-            mDownSketches;
+            mDownSketches GUARDED_BY(mLock);
     std::array<std::unique_ptr<dist_proc::aggregation::KllQuantile>, SketchIndex::SIZE>
-            mMoveSketches;
+            mMoveSketches GUARDED_BY(mLock);
     // How many events have been processed so far
-    size_t mNumSketchEventsProcessed = 0;
+    size_t mNumSketchEventsProcessed GUARDED_BY(mLock) = 0;
 };
 
 } // namespace android::inputdispatcher
diff --git a/services/sensorservice/aidl/SensorManager.cpp b/services/sensorservice/aidl/SensorManager.cpp
index 08e00b4..2b6ea7c 100644
--- a/services/sensorservice/aidl/SensorManager.cpp
+++ b/services/sensorservice/aidl/SensorManager.cpp
@@ -196,6 +196,11 @@
 sp<Looper> SensorManagerAidl::getLooper() {
     std::lock_guard<std::mutex> lock(mThreadMutex);
 
+    if (!mJavaVm) {
+        LOG(ERROR) << "No Java VM. This must be running in a test or fuzzer.";
+        return mLooper;
+    }
+
     if (!mPollThread.joinable()) {
         // if thread not initialized, start thread
         mStopThread = false;
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
index 962dc09..1e5a6fb 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -145,7 +145,8 @@
 }
 
 void LayerHierarchy::dump(std::ostream& out, const std::string& prefix,
-                          LayerHierarchy::Variant variant, bool isLastChild) const {
+                          LayerHierarchy::Variant variant, bool isLastChild,
+                          bool includeMirroredHierarchy) const {
     if (!mLayer) {
         out << " ROOT";
     } else {
@@ -153,8 +154,11 @@
         if (variant == LayerHierarchy::Variant::Relative) {
             out << "(Relative) ";
         } else if (variant == LayerHierarchy::Variant::Mirror) {
-            out << "(Mirroring) " << *mLayer << "\n" + prefix + "   └─ ...";
-            return;
+            if (!includeMirroredHierarchy) {
+                out << "(Mirroring) " << *mLayer << "\n" + prefix + "   └─ ...";
+                return;
+            }
+            out << "(Mirroring) ";
         }
         out << *mLayer;
     }
@@ -168,7 +172,7 @@
             childPrefix += (isLastChild ? "   " : "│  ");
         }
         out << "\n";
-        child->dump(out, childPrefix, childVariant, lastChild);
+        child->dump(out, childPrefix, childVariant, lastChild, includeMirroredHierarchy);
     }
     return;
 }
@@ -435,8 +439,11 @@
     std::stringstream ss;
     ss << "TraversalPath{.id = " << id;
 
-    if (mirrorRootId != UNASSIGNED_LAYER_ID) {
-        ss << ", .mirrorRootId=" << mirrorRootId;
+    if (!mirrorRootIds.empty()) {
+        ss << ", .mirrorRootIds=";
+        for (auto rootId : mirrorRootIds) {
+            ss << rootId << ",";
+        }
     }
 
     if (!relativeRootIds.empty()) {
@@ -453,13 +460,6 @@
     return ss.str();
 }
 
-LayerHierarchy::TraversalPath LayerHierarchy::TraversalPath::getMirrorRoot() const {
-    LLOG_ALWAYS_FATAL_WITH_TRACE_IF(!isClone(), "Cannot get mirror root of a non cloned node");
-    TraversalPath mirrorRootPath = *this;
-    mirrorRootPath.id = mirrorRootId;
-    return mirrorRootPath;
-}
-
 // Helper class to update a passed in TraversalPath when visiting a child. When the object goes out
 // of scope the TraversalPath is reset to its original state.
 LayerHierarchy::ScopedAddToTraversalPath::ScopedAddToTraversalPath(TraversalPath& traversalPath,
@@ -471,7 +471,7 @@
     traversalPath.id = layerId;
     traversalPath.variant = variant;
     if (variant == LayerHierarchy::Variant::Mirror) {
-        traversalPath.mirrorRootId = mParentPath.id;
+        traversalPath.mirrorRootIds.emplace_back(mParentPath.id);
     } else if (variant == LayerHierarchy::Variant::Relative) {
         if (std::find(traversalPath.relativeRootIds.begin(), traversalPath.relativeRootIds.end(),
                       layerId) != traversalPath.relativeRootIds.end()) {
@@ -486,7 +486,7 @@
     // Reset the traversal id to its original parent state using the state that was saved in
     // the constructor.
     if (mTraversalPath.variant == LayerHierarchy::Variant::Mirror) {
-        mTraversalPath.mirrorRootId = mParentPath.mirrorRootId;
+        mTraversalPath.mirrorRootIds.pop_back();
     } else if (mTraversalPath.variant == LayerHierarchy::Variant::Relative) {
         mTraversalPath.relativeRootIds.pop_back();
     }
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
index 1e48387..ba2e262 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
@@ -57,25 +57,25 @@
     // ├─ B {Traversal path id = 2}
     // │  ├─ C {Traversal path id = 3}
     // │  ├─ D {Traversal path id = 4}
-    // │  └─ E {Traversal path id = 5}
-    // ├─ F (Mirrors B) {Traversal path id = 6}
-    // └─ G (Mirrors F) {Traversal path id = 7}
+    // │  └─ E (Mirrors C) {Traversal path id = 5}
+    // └─ F (Mirrors B) {Traversal path id = 6}
     //
-    // C, D and E can be traversed via B or via F then B or via G then F then B.
+    // C can be traversed via B or E or F and or via F then E.
     // Depending on how the node is reached, its properties such as geometry or visibility might be
     // different. And we can uniquely identify the node by keeping track of the nodes leading up to
     // it. But to be more efficient we only need to track the nodes id and the top mirror root path.
     // So C for example, would have the following unique traversal paths:
     //  - {Traversal path id = 3}
-    //  - {Traversal path id = 3, mirrorRootId = 6}
-    //  - {Traversal path id = 3, mirrorRootId = 7}
+    //  - {Traversal path id = 3, mirrorRootIds = 5}
+    //  - {Traversal path id = 3, mirrorRootIds = 6}
+    //  - {Traversal path id = 3, mirrorRootIds = 6, 5}
 
     struct TraversalPath {
         uint32_t id;
         LayerHierarchy::Variant variant;
         // Mirrored layers can have a different geometry than their parents so we need to track
         // the mirror roots in the traversal.
-        uint32_t mirrorRootId = UNASSIGNED_LAYER_ID;
+        ftl::SmallVector<uint32_t, 5> mirrorRootIds;
         // Relative layers can be visited twice, once by their parent and then once again by
         // their relative parent. We keep track of the roots here to detect any loops in the
         // hierarchy. If a relative root already exists in the list while building the
@@ -93,11 +93,10 @@
         // Returns true if the node or its parents are not Detached.
         bool isAttached() const { return !detached; }
         // Returns true if the node is a clone.
-        bool isClone() const { return mirrorRootId != UNASSIGNED_LAYER_ID; }
-        TraversalPath getMirrorRoot() const;
+        bool isClone() const { return !mirrorRootIds.empty(); }
 
         bool operator==(const TraversalPath& other) const {
-            return id == other.id && mirrorRootId == other.mirrorRootId;
+            return id == other.id && mirrorRootIds == other.mirrorRootIds;
         }
         std::string toString() const;
 
@@ -107,8 +106,8 @@
     struct TraversalPathHash {
         std::size_t operator()(const LayerHierarchy::TraversalPath& key) const {
             uint32_t hashCode = key.id * 31;
-            if (key.mirrorRootId != UNASSIGNED_LAYER_ID) {
-                hashCode += key.mirrorRootId * 31;
+            for (uint32_t mirrorRootId : key.mirrorRootIds) {
+                hashCode += mirrorRootId * 31;
             }
             return std::hash<size_t>{}(hashCode);
         }
@@ -158,9 +157,17 @@
     const LayerHierarchy* getParent() const;
     friend std::ostream& operator<<(std::ostream& os, const LayerHierarchy& obj) {
         std::string prefix = " ";
-        obj.dump(os, prefix, LayerHierarchy::Variant::Attached, /*isLastChild=*/false);
+        obj.dump(os, prefix, LayerHierarchy::Variant::Attached, /*isLastChild=*/false,
+                 /*includeMirroredHierarchy*/ false);
         return os;
     }
+    std::string dump() const {
+        std::string prefix = " ";
+        std::ostringstream os;
+        dump(os, prefix, LayerHierarchy::Variant::Attached, /*isLastChild=*/false,
+             /*includeMirroredHierarchy*/ true);
+        return os.str();
+    }
 
     std::string getDebugStringShort() const;
     // Traverse the hierarchy and return true if loops are found. The outInvalidRelativeRoot
@@ -178,7 +185,7 @@
     void traverseInZOrder(const Visitor& visitor, LayerHierarchy::TraversalPath& parent) const;
     void traverse(const Visitor& visitor, LayerHierarchy::TraversalPath& parent) const;
     void dump(std::ostream& out, const std::string& prefix, LayerHierarchy::Variant variant,
-              bool isLastChild) const;
+              bool isLastChild, bool includeMirroredHierarchy) const;
 
     const RequestedLayerState* mLayer;
     LayerHierarchy* mParent = nullptr;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
index 80bedf4..899d2de 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -294,8 +294,11 @@
 
 std::ostream& operator<<(std::ostream& out, const LayerSnapshot& obj) {
     out << "Layer [" << obj.path.id;
-    if (obj.path.mirrorRootId != UNASSIGNED_LAYER_ID) {
-        out << " mirrored from " << obj.path.mirrorRootId;
+    if (!obj.path.mirrorRootIds.empty()) {
+        out << " mirrored from ";
+        for (auto rootId : obj.path.mirrorRootIds) {
+            out << rootId << ",";
+        }
     }
     out << "] " << obj.name << "\n    " << (obj.isVisible ? "visible" : "invisible")
         << " reason=" << obj.getIsVisibleReason();
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 9a5173b..2bbfa42 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -2648,7 +2648,7 @@
 
 void Layer::setInitialValuesForClone(const sp<Layer>& clonedFrom, uint32_t mirrorRootId) {
     mSnapshot->path.id = clonedFrom->getSequence();
-    mSnapshot->path.mirrorRootId = mirrorRootId;
+    mSnapshot->path.mirrorRootIds.emplace_back(mirrorRootId);
 
     cloneDrawingState(clonedFrom.get());
     mClonedFrom = clonedFrom;
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index a0024d5..97c4145 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -345,7 +345,7 @@
 }
 
 int32_t LayerFE::getSequence() const {
-    return mSnapshot->sequence;
+    return static_cast<int32_t>(mSnapshot->uniqueSequence);
 }
 
 bool LayerFE::hasRoundedCorners() const {
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index e4df494..551d744 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -294,7 +294,7 @@
         if (mLayerVote.category != FrameRateCategory::Default) {
             ALOGV("%s uses frame rate category: %d", mName.c_str(),
                   static_cast<int>(mLayerVote.category));
-            votes.push_back({LayerHistory::LayerVoteType::ExplicitCategory, mLayerVote.fps,
+            votes.push_back({LayerHistory::LayerVoteType::ExplicitCategory, Fps(),
                              Seamlessness::Default, mLayerVote.category});
         }
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 7e77bbe..be28e98 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -501,6 +501,12 @@
                 break;
             case LayerVoteType::ExplicitCategory:
                 explicitCategoryVoteLayers++;
+                if (layer.frameRateCategory == FrameRateCategory::NoPreference) {
+                    // Count this layer for Min vote as well. The explicit vote avoids
+                    // touch boost and idle for choosing a category, while Min vote is for correct
+                    // behavior when all layers are Min or no vote.
+                    minVoteLayers++;
+                }
                 break;
             case LayerVoteType::Heuristic:
                 break;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index a7f2845..e6a2de5 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -494,7 +494,10 @@
     mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled ||
             base::GetBoolProperty("persist.debug.sf.enable_legacy_frontend"s, false);
 
+    // Trunk-Stable flags
     mMiscFlagValue = flags::misc1();
+    mConnectedDisplayFlagValue = flags::connected_display();
+    mMisc2FlagEarlyBootValue = flags::late_boot_misc2();
 }
 
 LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() {
@@ -738,6 +741,8 @@
     }));
 
     LOG_ALWAYS_FATAL_IF(flags::misc1() != mMiscFlagValue, "misc1 flag is not boot stable!");
+
+    mMisc2FlagLateBootValue = flags::late_boot_misc2();
 }
 
 static std::optional<renderengine::RenderEngine::RenderEngineType>
@@ -5821,6 +5826,7 @@
                 {"--timestats"s, protoDumper(&SurfaceFlinger::dumpTimeStats)},
                 {"--vsync"s, dumper(&SurfaceFlinger::dumpVsync)},
                 {"--wide-color"s, dumper(&SurfaceFlinger::dumpWideColorInfo)},
+                {"--frontend"s, dumper(&SurfaceFlinger::dumpFrontEnd)},
         };
 
         const auto flag = args.empty() ? ""s : std::string(String8(args[0]));
@@ -6130,6 +6136,38 @@
     }
 }
 
+void SurfaceFlinger::dumpFrontEnd(std::string& result) {
+    mScheduler
+            ->schedule([&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
+                std::ostringstream out;
+                out << "\nComposition list\n";
+                ui::LayerStack lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
+                for (const auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) {
+                    if (lastPrintedLayerStackHeader != snapshot->outputFilter.layerStack) {
+                        lastPrintedLayerStackHeader = snapshot->outputFilter.layerStack;
+                        out << "LayerStack=" << lastPrintedLayerStackHeader.id << "\n";
+                    }
+                    out << "  " << *snapshot << "\n";
+                }
+
+                out << "\nInput list\n";
+                lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
+                mLayerSnapshotBuilder.forEachInputSnapshot(
+                        [&](const frontend::LayerSnapshot& snapshot) {
+                            if (lastPrintedLayerStackHeader != snapshot.outputFilter.layerStack) {
+                                lastPrintedLayerStackHeader = snapshot.outputFilter.layerStack;
+                                out << "LayerStack=" << lastPrintedLayerStackHeader.id << "\n";
+                            }
+                            out << "  " << snapshot << "\n";
+                        });
+
+                out << "\nLayer Hierarchy\n"
+                    << mLayerHierarchyBuilder.getHierarchy().dump() << "\n\n";
+                result.append(out.str());
+            })
+            .get();
+}
+
 perfetto::protos::LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const {
     std::unordered_set<uint64_t> stackIdsToSkip;
 
@@ -6342,6 +6380,11 @@
     colorizer.reset(result);
 
     StringAppendF(&result, "MiscFlagValue: %s\n", mMiscFlagValue ? "true" : "false");
+    StringAppendF(&result, "ConnectedDisplayFlagValue: %s\n",
+                  mConnectedDisplayFlagValue ? "true" : "false");
+    StringAppendF(&result, "Misc2FlagValue: %s (%s after boot)\n",
+                  mMisc2FlagLateBootValue ? "true" : "false",
+                  mMisc2FlagEarlyBootValue == mMisc2FlagLateBootValue ? "stable" : "modified");
 
     getRenderEngine().dump(result);
 
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index f752584..e22dd56 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -1090,6 +1090,7 @@
     void dumpRawDisplayIdentificationData(const DumpArgs&, std::string& result) const;
     void dumpWideColorInfo(std::string& result) const REQUIRES(mStateLock);
     void dumpHdrInfo(std::string& result) const REQUIRES(mStateLock);
+    void dumpFrontEnd(std::string& result);
 
     perfetto::protos::LayersProto dumpDrawingStateProto(uint32_t traceFlags) const;
     void dumpOffscreenLayersProto(perfetto::protos::LayersProto& layersProto,
@@ -1451,7 +1452,11 @@
     void sfdo_scheduleComposite();
     void sfdo_scheduleCommit();
 
+    // Trunk-Stable flags
     bool mMiscFlagValue;
+    bool mConnectedDisplayFlagValue;
+    bool mMisc2FlagEarlyBootValue;
+    bool mMisc2FlagLateBootValue;
 };
 
 class SurfaceComposerAIDL : public gui::BnSurfaceComposer {
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index 0a36888..6519d3a 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -6,4 +6,19 @@
   description: "This flag controls minor miscellaneous SurfaceFlinger changes"
   bug: "297389311"
   is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+  name: "connected_display"
+  namespace: "core_graphics"
+  description: "Controls SurfaceFlinger support for Connected Displays"
+  bug: "278199093"
+  is_fixed_read_only: true
+}
+
+flag{
+  name: "late_boot_misc2"
+  namespace: "core_graphics"
+  description: "This flag controls minor miscellaneous SurfaceFlinger changes. Cannot be read before boot finished!"
+  bug: "297389311"
+}
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
index e4f49e8..95f1940 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
@@ -736,4 +736,22 @@
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
 }
 
+TEST_F(LayerHierarchyTest, canMirrorDisplayWithMirrors) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    reparentLayer(12, UNASSIGNED_LAYER_ID);
+    mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+    setLayerStack(3, 1);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expected = {1, 11, 111, 13, 14, 11, 111, 2, 3,
+                                      1, 11, 111, 13, 14, 11, 111, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
+    expected = {12, 121, 122, 1221};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index b67494f..b7996ce 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -529,7 +529,7 @@
     EXPECT_EQ(1, frequentLayerCount(time));
     // First LayerRequirement is the layer's category specification
     EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
     EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
 
     // Second LayerRequirement is the frame rate specification
@@ -544,7 +544,7 @@
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
     EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
     EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
 }
 
diff --git a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
index e0133d6..11072bc 100644
--- a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
@@ -223,8 +223,6 @@
 }
 
 TEST_F(LayerInfoTest, getRefreshRateVote_explicitCategory) {
-    // When a layer only has a category set, the LayerVoteType should be the LayerInfo's default.
-    // The most common case should be Heuristic.
     LayerInfo::LayerVote vote = {.type = LayerHistory::LayerVoteType::ExplicitDefault,
                                  .category = FrameRateCategory::High};
     layerInfo.setLayerVote(vote);
@@ -234,6 +232,20 @@
     ASSERT_EQ(actualVotes.size(), 1u);
     ASSERT_EQ(actualVotes[0].type, LayerHistory::LayerVoteType::ExplicitCategory);
     ASSERT_EQ(actualVotes[0].category, vote.category);
+    ASSERT_EQ(actualVotes[0].fps, 0_Hz);
+}
+
+TEST_F(LayerInfoTest, getRefreshRateVote_categoryNoPreference) {
+    LayerInfo::LayerVote vote = {.type = LayerHistory::LayerVoteType::ExplicitDefault,
+                                 .category = FrameRateCategory::NoPreference};
+    layerInfo.setLayerVote(vote);
+
+    auto actualVotes =
+            layerInfo.getRefreshRateVote(*mScheduler->refreshRateSelector(), systemTime());
+    ASSERT_EQ(actualVotes.size(), 1u);
+    ASSERT_EQ(actualVotes[0].type, LayerHistory::LayerVoteType::ExplicitCategory);
+    ASSERT_EQ(actualVotes[0].category, vote.category);
+    ASSERT_EQ(actualVotes[0].fps, 0_Hz);
 }
 
 TEST_F(LayerInfoTest, getRefreshRateVote_noData) {
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 72ed4c8..1227b99 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -410,8 +410,8 @@
     std::vector<uint32_t> expected = {1,  11, 111, 13, 2,  3,   1,  11, 111,
                                       13, 2,  4,   1,  11, 111, 13, 2};
     UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
-    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootId = 3})->outputFilter.layerStack.id, 3u);
-    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootId = 4})->outputFilter.layerStack.id, 4u);
+    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootIds = 3u})->outputFilter.layerStack.id, 3u);
+    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootIds = 4u})->outputFilter.layerStack.id, 4u);
 }
 
 // ROOT (DISPLAY 0)
@@ -435,7 +435,7 @@
     UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
     EXPECT_TRUE(getSnapshot({.id = 111})->inputInfo.touchableRegion.hasSameRects(touch));
     Region touchCroppedByMirrorRoot{Rect{0, 0, 50, 50}};
-    EXPECT_TRUE(getSnapshot({.id = 111, .mirrorRootId = 3})
+    EXPECT_TRUE(getSnapshot({.id = 111, .mirrorRootIds = 3u})
                         ->inputInfo.touchableRegion.hasSameRects(touchCroppedByMirrorRoot));
 }
 
@@ -462,6 +462,21 @@
     EXPECT_EQ(startingNumSnapshots, mSnapshotBuilder.getSnapshots().size());
 }
 
+TEST_F(LayerSnapshotTest, canMirrorDisplayWithMirrors) {
+    reparentLayer(12, UNASSIGNED_LAYER_ID);
+    mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
+    std::vector<uint32_t> expected = {1, 11, 111, 13, 14, 11, 111, 2};
+    UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+
+    createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+    setLayerStack(3, 3);
+    expected = {1, 11, 111, 13, 14, 11, 111, 2, 3, 1, 11, 111, 13, 14, 11, 111, 2};
+    UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+    EXPECT_EQ(getSnapshot({.id = 11, .mirrorRootIds = 14u})->outputFilter.layerStack.id, 0u);
+    EXPECT_EQ(getSnapshot({.id = 11, .mirrorRootIds = 3u})->outputFilter.layerStack.id, 3u);
+    EXPECT_EQ(getSnapshot({.id = 11, .mirrorRootIds = 3u, 14u})->outputFilter.layerStack.id, 3u);
+}
+
 // Rel z doesn't create duplicate snapshots but this is for completeness
 TEST_F(LayerSnapshotTest, cleanUpUnreachableSnapshotsAfterRelZ) {
     size_t startingNumSnapshots = mSnapshotBuilder.getSnapshots().size();
@@ -667,6 +682,8 @@
               scheduler::LayerInfo::FrameRateCompatibility::Default);
     EXPECT_EQ(getSnapshot({.id = 122})->frameRate.category, FrameRateCategory::Normal);
     EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+    EXPECT_TRUE(
+            getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::AffectsChildren));
 
     EXPECT_FALSE(getSnapshot({.id = 1221})->frameRate.vote.rate.isValid());
     EXPECT_TRUE(getSnapshot({.id = 1221})->frameRate.isValid());
@@ -674,6 +691,8 @@
               scheduler::LayerInfo::FrameRateCompatibility::Default);
     EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.category, FrameRateCategory::Normal);
     EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+    EXPECT_TRUE(
+            getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::AffectsChildren));
 
     // reparent and verify the child does NOT get the new parent's framerate because it already has
     // the frame rate category specified.