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.