Merge "Prevent keyboards from being recognized as a stylus" into main
diff --git a/include/input/MotionPredictorMetricsManager.h b/include/input/MotionPredictorMetricsManager.h
index 6284f07..12e50ba 100644
--- a/include/input/MotionPredictorMetricsManager.h
+++ b/include/input/MotionPredictorMetricsManager.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,23 +14,193 @@
* limitations under the License.
*/
-#include <utils/Timers.h>
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <limits>
+#include <optional>
+#include <vector>
+
+#include <input/Input.h> // for MotionEvent
+#include <input/RingBuffer.h>
+#include <utils/Timers.h> // for nsecs_t
+
+#include "Eigen/Core"
namespace android {
/**
* Class to handle computing and reporting metrics for MotionPredictor.
*
- * Currently an empty implementation, containing only the API.
+ * The public API provides two methods: `onRecord` and `onPredict`, which expect to receive the
+ * MotionEvents from the corresponding methods in MotionPredictor.
+ *
+ * This class stores AggregatedStrokeMetrics, updating them as new MotionEvents are passed in. When
+ * onRecord receives an UP or CANCEL event, this indicates the end of the stroke, and the final
+ * AtomFields are computed and reported to the stats library.
+ *
+ * If mMockLoggedAtomFields is set, the batch of AtomFields that are reported to the stats library
+ * for one stroke are also stored in mMockLoggedAtomFields at the time they're reported.
*/
class MotionPredictorMetricsManager {
public:
// Note: the MetricsManager assumes that the input interval equals the prediction interval.
- MotionPredictorMetricsManager(nsecs_t /*predictionInterval*/, size_t /*maxNumPredictions*/) {}
+ MotionPredictorMetricsManager(nsecs_t predictionInterval, size_t maxNumPredictions);
- void onRecord(const MotionEvent& /*inputEvent*/) {}
+ // This method should be called once for each call to MotionPredictor::record, receiving the
+ // forwarded MotionEvent argument.
+ void onRecord(const MotionEvent& inputEvent);
- void onPredict(const MotionEvent& /*predictionEvent*/) {}
+ // This method should be called once for each call to MotionPredictor::predict, receiving the
+ // MotionEvent that will be returned by MotionPredictor::predict.
+ void onPredict(const MotionEvent& predictionEvent);
+
+ // Simple structs to hold relevant touch input information. Public so they can be used in tests.
+
+ struct TouchPoint {
+ Eigen::Vector2f position; // (y, x) in pixels
+ float pressure;
+ };
+
+ struct GroundTruthPoint : TouchPoint {
+ nsecs_t timestamp;
+ };
+
+ struct PredictionPoint : TouchPoint {
+ // The timestamp of the last ground truth point when the prediction was made.
+ nsecs_t originTimestamp;
+
+ nsecs_t targetTimestamp;
+
+ // Order by targetTimestamp when sorting.
+ bool operator<(const PredictionPoint& other) const {
+ return this->targetTimestamp < other.targetTimestamp;
+ }
+ };
+
+ // Metrics aggregated so far for the current stroke. These are not the final fields to be
+ // reported in the atom (see AtomFields below), but rather an intermediate representation of the
+ // data that can be conveniently aggregated and from which the atom fields can be derived later.
+ //
+ // Displacement units are in pixels.
+ //
+ // "Along-trajectory error" is the dot product of the prediction error with the unit vector
+ // pointing towards the ground truth point whose timestamp corresponds to the prediction
+ // target timestamp, originating from the preceding ground truth point.
+ //
+ // "Off-trajectory error" is the component of the prediction error orthogonal to the
+ // "along-trajectory" unit vector described above.
+ //
+ // "High-velocity" errors are errors that are only accumulated when the velocity between the
+ // most recent two input events exceeds a certain threshold.
+ //
+ // "Scale-invariant errors" are the errors produced when the path length of the stroke is
+ // scaled to 1. (In other words, the error distances are normalized by the path length.)
+ struct AggregatedStrokeMetrics {
+ // General errors
+ float alongTrajectoryErrorSum = 0;
+ float alongTrajectorySumSquaredErrors = 0;
+ float offTrajectorySumSquaredErrors = 0;
+ float pressureSumSquaredErrors = 0;
+ size_t generalErrorsCount = 0;
+
+ // High-velocity errors
+ float highVelocityAlongTrajectorySse = 0;
+ float highVelocityOffTrajectorySse = 0;
+ size_t highVelocityErrorsCount = 0;
+
+ // Scale-invariant errors
+ float scaleInvariantAlongTrajectorySse = 0;
+ float scaleInvariantOffTrajectorySse = 0;
+ size_t scaleInvariantErrorsCount = 0;
+ };
+
+ // In order to explicitly indicate "no relevant data" for a metric, we report this
+ // large-magnitude negative sentinel value. (Most metrics are non-negative, so this value is
+ // completely unobtainable. For along-trajectory error mean, which can be negative, the
+ // magnitude makes it unobtainable in practice.)
+ static const int NO_DATA_SENTINEL = std::numeric_limits<int32_t>::min();
+
+ // Final metrics reported in the atom.
+ struct AtomFields {
+ int deltaTimeBucketMilliseconds = 0;
+
+ // General errors
+ int alongTrajectoryErrorMeanMillipixels = NO_DATA_SENTINEL;
+ int alongTrajectoryErrorStdMillipixels = NO_DATA_SENTINEL;
+ int offTrajectoryRmseMillipixels = NO_DATA_SENTINEL;
+ int pressureRmseMilliunits = NO_DATA_SENTINEL;
+
+ // High-velocity errors
+ int highVelocityAlongTrajectoryRmse = NO_DATA_SENTINEL; // millipixels
+ int highVelocityOffTrajectoryRmse = NO_DATA_SENTINEL; // millipixels
+
+ // Scale-invariant errors
+ int scaleInvariantAlongTrajectoryRmse = NO_DATA_SENTINEL; // millipixels
+ int scaleInvariantOffTrajectoryRmse = NO_DATA_SENTINEL; // millipixels
+ };
+
+ // Allow tests to pass in a mock AtomFields pointer.
+ //
+ // When metrics are reported to the stats library on stroke end, they will also be written to
+ // mockLoggedAtomFields, overwriting existing data. The size of mockLoggedAtomFields will equal
+ // the number of calls to stats_write for that stroke.
+ void setMockLoggedAtomFields(std::vector<AtomFields>* mockLoggedAtomFields) {
+ mMockLoggedAtomFields = mockLoggedAtomFields;
+ }
+
+private:
+ // The interval between consecutive predictions' target timestamps. We assume that the input
+ // interval also equals this value.
+ const nsecs_t mPredictionInterval;
+
+ // The maximum number of input frames into the future the model can predict.
+ // Used to perform time-bucketing of metrics.
+ const size_t mMaxNumPredictions;
+
+ // History of mMaxNumPredictions + 1 ground truth points, used to compute scale-invariant
+ // error. (Also, the last two points are used to compute the ground truth trajectory.)
+ RingBuffer<GroundTruthPoint> mRecentGroundTruthPoints;
+
+ // Predictions having a targetTimestamp after the most recent ground truth point's timestamp.
+ // Invariant: sorted in ascending order of targetTimestamp.
+ std::vector<PredictionPoint> mRecentPredictions;
+
+ // Containers for the intermediate representation of stroke metrics and the final atom fields.
+ // These are indexed by the number of input frames into the future being predicted minus one,
+ // and always have size mMaxNumPredictions.
+ std::vector<AggregatedStrokeMetrics> mAggregatedMetrics;
+ std::vector<AtomFields> mAtomFields;
+
+ // Non-owning pointer to the location of mock AtomFields. If present, will be filled with the
+ // values reported to stats_write on each batch of reported metrics.
+ //
+ // This pointer must remain valid as long as the MotionPredictorMetricsManager exists.
+ std::vector<AtomFields>* mMockLoggedAtomFields = nullptr;
+
+ // Helper methods for the implementation of onRecord and onPredict.
+
+ // Clears stored ground truth and prediction points, as well as all stored metrics for the
+ // current stroke.
+ void clearStrokeData();
+
+ // Adds the new ground truth point to mRecentGroundTruths, removes outdated predictions from
+ // mRecentPredictions, and updates the aggregated metrics to include the recent predictions that
+ // fuzzily match with the new ground truth point.
+ void incorporateNewGroundTruth(const GroundTruthPoint& groundTruthPoint);
+
+ // Given a new prediction with targetTimestamp matching the latest ground truth point's
+ // timestamp, computes the corresponding metrics and updates mAggregatedMetrics.
+ void updateAggregatedMetrics(const PredictionPoint& predictionPoint);
+
+ // Computes the atom fields to mAtomFields from the values in mAggregatedMetrics.
+ void computeAtomFields();
+
+ // Reports the metrics given by the current data in mAtomFields:
+ // • If on an Android device, reports the metrics to stats_write.
+ // • If mMockLoggedAtomFields is present, it will be overwritten with logged metrics, with one
+ // AtomFields element per call to stats_write.
+ void reportMetrics();
};
} // namespace android
diff --git a/libs/binder/rust/sys/lib.rs b/libs/binder/rust/sys/lib.rs
index 1d1a295..c5c847b 100644
--- a/libs/binder/rust/sys/lib.rs
+++ b/libs/binder/rust/sys/lib.rs
@@ -19,7 +19,20 @@
use std::error::Error;
use std::fmt;
-include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+#[cfg(not(target_os = "trusty"))]
+mod bindings {
+ include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+}
+
+// Trusty puts the full path to the auto-generated file in BINDGEN_INC_FILE
+// and builds it with warnings-as-errors, so we need to use #[allow(bad_style)]
+#[cfg(target_os = "trusty")]
+#[allow(bad_style)]
+mod bindings {
+ include!(env!("BINDGEN_INC_FILE"));
+}
+
+pub use bindings::*;
impl Error for android_c_interface_StatusCode {}
diff --git a/libs/binder/trusty/rust/binder_ndk_sys/rules.mk b/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
new file mode 100644
index 0000000..672d9b7
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
@@ -0,0 +1,38 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../..
+LIBBINDER_NDK_BINDGEN_FLAG_FILE := \
+ $(LIBBINDER_DIR)/rust/libbinder_ndk_bindgen_flags.txt
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LIBBINDER_DIR)/rust/sys/lib.rs
+
+MODULE_CRATE_NAME := binder_ndk_sys
+
+MODULE_LIBRARY_DEPS += \
+ $(LIBBINDER_DIR)/trusty \
+ $(LIBBINDER_DIR)/trusty/ndk \
+ trusty/user/base/lib/trusty-sys \
+
+MODULE_BINDGEN_SRC_HEADER := $(LIBBINDER_DIR)/rust/sys/BinderBindings.hpp
+
+# Add the flags from the flag file
+MODULE_BINDGEN_FLAGS += $(shell cat $(LIBBINDER_NDK_BINDGEN_FLAG_FILE))
+MODULE_SRCDEPS += $(LIBBINDER_NDK_BINDGEN_FLAG_FILE)
+
+include make/library.mk
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index 52af9d5..2eb6bd6 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -43,7 +43,7 @@
}
bool WindowInfo::frameContainsPoint(int32_t x, int32_t y) const {
- return x >= frameLeft && x < frameRight && y >= frameTop && y < frameBottom;
+ return x >= frame.left && x < frame.right && y >= frame.top && y < frame.bottom;
}
bool WindowInfo::supportsSplitTouch() const {
@@ -59,18 +59,15 @@
}
bool WindowInfo::overlaps(const WindowInfo* other) const {
- const bool nonEmpty = (frameRight - frameLeft > 0) || (frameBottom - frameTop > 0);
- return nonEmpty && frameLeft < other->frameRight && frameRight > other->frameLeft &&
- frameTop < other->frameBottom && frameBottom > other->frameTop;
+ return !frame.isEmpty() && frame.left < other->frame.right && frame.right > other->frame.left &&
+ frame.top < other->frame.bottom && frame.bottom > other->frame.top;
}
bool WindowInfo::operator==(const WindowInfo& info) const {
return info.token == token && info.id == id && info.name == name &&
- info.dispatchingTimeout == dispatchingTimeout && info.frameLeft == frameLeft &&
- info.frameTop == frameTop && info.frameRight == frameRight &&
- info.frameBottom == frameBottom && info.surfaceInset == surfaceInset &&
- info.globalScaleFactor == globalScaleFactor && info.transform == transform &&
- info.touchableRegion.hasSameRects(touchableRegion) &&
+ info.dispatchingTimeout == dispatchingTimeout && info.frame == frame &&
+ info.surfaceInset == surfaceInset && info.globalScaleFactor == globalScaleFactor &&
+ info.transform == transform && info.touchableRegion.hasSameRects(touchableRegion) &&
info.touchOcclusionMode == touchOcclusionMode && info.ownerPid == ownerPid &&
info.ownerUid == ownerUid && info.packageName == packageName &&
info.inputConfig == inputConfig && info.displayId == displayId &&
@@ -103,10 +100,7 @@
parcel->writeInt32(layoutParamsFlags.get()) ?:
parcel->writeInt32(
static_cast<std::underlying_type_t<WindowInfo::Type>>(layoutParamsType)) ?:
- parcel->writeInt32(frameLeft) ?:
- parcel->writeInt32(frameTop) ?:
- parcel->writeInt32(frameRight) ?:
- parcel->writeInt32(frameBottom) ?:
+ parcel->write(frame) ?:
parcel->writeInt32(surfaceInset) ?:
parcel->writeFloat(globalScaleFactor) ?:
parcel->writeFloat(alpha) ?:
@@ -155,10 +149,7 @@
// clang-format off
status = parcel->readInt32(&lpFlags) ?:
parcel->readInt32(&lpType) ?:
- parcel->readInt32(&frameLeft) ?:
- parcel->readInt32(&frameTop) ?:
- parcel->readInt32(&frameRight) ?:
- parcel->readInt32(&frameBottom) ?:
+ parcel->read(frame) ?:
parcel->readInt32(&surfaceInset) ?:
parcel->readFloat(&globalScaleFactor) ?:
parcel->readFloat(&alpha) ?:
diff --git a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp
index 3e37e48..4daa3be 100644
--- a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp
+++ b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp
@@ -178,10 +178,8 @@
windowInfo->name = mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes);
windowInfo->layoutParamsFlags = mFdp.PickValueInArray(kFlags);
windowInfo->layoutParamsType = mFdp.PickValueInArray(kType);
- windowInfo->frameLeft = mFdp.ConsumeIntegral<int32_t>();
- windowInfo->frameTop = mFdp.ConsumeIntegral<int32_t>();
- windowInfo->frameRight = mFdp.ConsumeIntegral<int32_t>();
- windowInfo->frameBottom = mFdp.ConsumeIntegral<int32_t>();
+ windowInfo->frame = Rect(mFdp.ConsumeIntegral<int32_t>(), mFdp.ConsumeIntegral<int32_t>(),
+ mFdp.ConsumeIntegral<int32_t>(), mFdp.ConsumeIntegral<int32_t>());
windowInfo->surfaceInset = mFdp.ConsumeIntegral<int32_t>();
windowInfo->alpha = mFdp.ConsumeFloatingPointInRange<float>(0, 1);
ui::Transform transform(mFdp.PickValueInArray(kOrientation));
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 7aa7068..8572c94 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -265,6 +265,7 @@
// Changes affecting child states.
static constexpr uint64_t AFFECTS_CHILDREN = layer_state_t::GEOMETRY_CHANGES |
layer_state_t::HIERARCHY_CHANGES | layer_state_t::eAlphaChanged |
+ 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 |
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index 7ff7387..bd2eb74 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -194,10 +194,7 @@
std::chrono::nanoseconds dispatchingTimeout = std::chrono::seconds(5);
/* These values are filled in by SurfaceFlinger. */
- int32_t frameLeft = -1;
- int32_t frameTop = -1;
- int32_t frameRight = -1;
- int32_t frameBottom = -1;
+ Rect frame = Rect::INVALID_RECT;
/*
* SurfaceFlinger consumes this value to shrink the computed frame. This is
diff --git a/libs/gui/tests/WindowInfo_test.cpp b/libs/gui/tests/WindowInfo_test.cpp
index 461fe4a..f2feaef 100644
--- a/libs/gui/tests/WindowInfo_test.cpp
+++ b/libs/gui/tests/WindowInfo_test.cpp
@@ -52,10 +52,7 @@
i.layoutParamsFlags = WindowInfo::Flag::SLIPPERY;
i.layoutParamsType = WindowInfo::Type::INPUT_METHOD;
i.dispatchingTimeout = 12s;
- i.frameLeft = 93;
- i.frameTop = 34;
- i.frameRight = 16;
- i.frameBottom = 19;
+ i.frame = Rect(93, 34, 16, 19);
i.surfaceInset = 17;
i.globalScaleFactor = 0.3;
i.alpha = 0.7;
@@ -85,10 +82,7 @@
ASSERT_EQ(i.layoutParamsFlags, i2.layoutParamsFlags);
ASSERT_EQ(i.layoutParamsType, i2.layoutParamsType);
ASSERT_EQ(i.dispatchingTimeout, i2.dispatchingTimeout);
- ASSERT_EQ(i.frameLeft, i2.frameLeft);
- ASSERT_EQ(i.frameTop, i2.frameTop);
- ASSERT_EQ(i.frameRight, i2.frameRight);
- ASSERT_EQ(i.frameBottom, i2.frameBottom);
+ ASSERT_EQ(i.frame, i2.frame);
ASSERT_EQ(i.surfaceInset, i2.surfaceInset);
ASSERT_EQ(i.globalScaleFactor, i2.globalScaleFactor);
ASSERT_EQ(i.alpha, i2.alpha);
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 757cde2..8656b26 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -139,6 +139,7 @@
"KeyCharacterMap.cpp",
"KeyLayoutMap.cpp",
"MotionPredictor.cpp",
+ "MotionPredictorMetricsManager.cpp",
"PrintTools.cpp",
"PropertyMap.cpp",
"TfLiteMotionPredictor.cpp",
@@ -152,9 +153,13 @@
header_libs: [
"flatbuffer_headers",
"jni_headers",
+ "libeigen",
"tensorflow_headers",
],
- export_header_lib_headers: ["jni_headers"],
+ export_header_lib_headers: [
+ "jni_headers",
+ "libeigen",
+ ],
generated_headers: [
"cxx-bridge-header",
@@ -206,6 +211,17 @@
target: {
android: {
+ export_shared_lib_headers: ["libbinder"],
+
+ shared_libs: [
+ "libutils",
+ "libbinder",
+ // Stats logging library and its dependencies.
+ "libstatslog_libinput",
+ "libstatsbootstrap",
+ "android.os.statsbootstrap_aidl-cpp",
+ ],
+
required: [
"motion_predictor_model_prebuilt",
"motion_predictor_model_config",
@@ -228,6 +244,43 @@
},
}
+// Use bootstrap version of stats logging library.
+// libinput is a bootstrap process (starts early in the boot process), and thus can't use the normal
+// `libstatslog` because that requires `libstatssocket`, which is only available later in the boot.
+cc_library {
+ name: "libstatslog_libinput",
+ generated_sources: ["statslog_libinput.cpp"],
+ generated_headers: ["statslog_libinput.h"],
+ export_generated_headers: ["statslog_libinput.h"],
+ shared_libs: [
+ "libbinder",
+ "libstatsbootstrap",
+ "libutils",
+ "android.os.statsbootstrap_aidl-cpp",
+ ],
+}
+
+genrule {
+ name: "statslog_libinput.h",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_libinput.h --module libinput" +
+ " --namespace android,stats,libinput --bootstrap",
+ out: [
+ "statslog_libinput.h",
+ ],
+}
+
+genrule {
+ name: "statslog_libinput.cpp",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_libinput.cpp --module libinput" +
+ " --namespace android,stats,libinput --importHeader statslog_libinput.h" +
+ " --bootstrap",
+ out: [
+ "statslog_libinput.cpp",
+ ],
+}
+
cc_defaults {
name: "libinput_fuzz_defaults",
cpp_std: "c++20",
diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index 0961a9d..b5a5e72 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -137,10 +137,7 @@
// Pass input event to the MetricsManager.
if (!mMetricsManager) {
- mMetricsManager =
- std::make_optional<MotionPredictorMetricsManager>(mModel->config()
- .predictionInterval,
- mModel->outputLength());
+ mMetricsManager.emplace(mModel->config().predictionInterval, mModel->outputLength());
}
mMetricsManager->onRecord(event);
diff --git a/libs/input/MotionPredictorMetricsManager.cpp b/libs/input/MotionPredictorMetricsManager.cpp
new file mode 100644
index 0000000..67b1032
--- /dev/null
+++ b/libs/input/MotionPredictorMetricsManager.cpp
@@ -0,0 +1,373 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MotionPredictorMetricsManager"
+
+#include <input/MotionPredictorMetricsManager.h>
+
+#include <algorithm>
+
+#include <android-base/logging.h>
+
+#include "Eigen/Core"
+#include "Eigen/Geometry"
+
+#ifdef __ANDROID__
+#include <statslog_libinput.h>
+#endif
+
+namespace android {
+namespace {
+
+inline constexpr int NANOS_PER_SECOND = 1'000'000'000; // nanoseconds per second
+inline constexpr int NANOS_PER_MILLIS = 1'000'000; // nanoseconds per millisecond
+
+// Velocity threshold at which we report "high-velocity" metrics, in pixels per second.
+// This value was selected from manual experimentation, as a threshold that separates "fast"
+// (semi-sloppy) handwriting from more careful medium to slow handwriting.
+inline constexpr float HIGH_VELOCITY_THRESHOLD = 1100.0;
+
+// Small value to add to the path length when computing scale-invariant error to avoid division by
+// zero.
+inline constexpr float PATH_LENGTH_EPSILON = 0.001;
+
+} // namespace
+
+MotionPredictorMetricsManager::MotionPredictorMetricsManager(nsecs_t predictionInterval,
+ size_t maxNumPredictions)
+ : mPredictionInterval(predictionInterval),
+ mMaxNumPredictions(maxNumPredictions),
+ mRecentGroundTruthPoints(maxNumPredictions + 1),
+ mAggregatedMetrics(maxNumPredictions),
+ mAtomFields(maxNumPredictions) {}
+
+void MotionPredictorMetricsManager::onRecord(const MotionEvent& inputEvent) {
+ // Convert MotionEvent to GroundTruthPoint.
+ const PointerCoords* coords = inputEvent.getRawPointerCoords(/*pointerIndex=*/0);
+ LOG_ALWAYS_FATAL_IF(coords == nullptr);
+ const GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f{coords->getY(),
+ coords->getX()},
+ .pressure =
+ inputEvent.getPressure(/*pointerIndex=*/0)},
+ .timestamp = inputEvent.getEventTime()};
+
+ // Handle event based on action type.
+ switch (inputEvent.getActionMasked()) {
+ case AMOTION_EVENT_ACTION_DOWN: {
+ clearStrokeData();
+ incorporateNewGroundTruth(groundTruthPoint);
+ break;
+ }
+ case AMOTION_EVENT_ACTION_MOVE: {
+ incorporateNewGroundTruth(groundTruthPoint);
+ break;
+ }
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_CANCEL: {
+ // Only expect meaningful predictions when given at least two input points.
+ if (mRecentGroundTruthPoints.size() >= 2) {
+ computeAtomFields();
+ reportMetrics();
+ break;
+ }
+ }
+ }
+}
+
+// Adds new predictions to mRecentPredictions and maintains the invariant that elements are
+// sorted in ascending order of targetTimestamp.
+void MotionPredictorMetricsManager::onPredict(const MotionEvent& predictionEvent) {
+ for (size_t i = 0; i < predictionEvent.getHistorySize() + 1; ++i) {
+ // Convert MotionEvent to PredictionPoint.
+ const PointerCoords* coords =
+ predictionEvent.getHistoricalRawPointerCoords(/*pointerIndex=*/0, i);
+ LOG_ALWAYS_FATAL_IF(coords == nullptr);
+ const nsecs_t targetTimestamp = predictionEvent.getHistoricalEventTime(i);
+ mRecentPredictions.push_back(
+ PredictionPoint{{.position = Eigen::Vector2f{coords->getY(), coords->getX()},
+ .pressure =
+ predictionEvent.getHistoricalPressure(/*pointerIndex=*/0,
+ i)},
+ .originTimestamp = mRecentGroundTruthPoints.back().timestamp,
+ .targetTimestamp = targetTimestamp});
+ }
+
+ std::sort(mRecentPredictions.begin(), mRecentPredictions.end());
+}
+
+void MotionPredictorMetricsManager::clearStrokeData() {
+ mRecentGroundTruthPoints.clear();
+ mRecentPredictions.clear();
+ std::fill(mAggregatedMetrics.begin(), mAggregatedMetrics.end(), AggregatedStrokeMetrics{});
+ std::fill(mAtomFields.begin(), mAtomFields.end(), AtomFields{});
+}
+
+void MotionPredictorMetricsManager::incorporateNewGroundTruth(
+ const GroundTruthPoint& groundTruthPoint) {
+ // Note: this removes the oldest point if `mRecentGroundTruthPoints` is already at capacity.
+ mRecentGroundTruthPoints.pushBack(groundTruthPoint);
+
+ // Remove outdated predictions – those that can never be matched with the current or any future
+ // ground truth points. We use fuzzy association for the timestamps here, because ground truth
+ // and prediction timestamps may not be perfectly synchronized.
+ const nsecs_t fuzzy_association_time_delta = mPredictionInterval / 4;
+ const auto firstCurrentIt =
+ std::find_if(mRecentPredictions.begin(), mRecentPredictions.end(),
+ [&groundTruthPoint,
+ fuzzy_association_time_delta](const PredictionPoint& prediction) {
+ return prediction.targetTimestamp >
+ groundTruthPoint.timestamp - fuzzy_association_time_delta;
+ });
+ mRecentPredictions.erase(mRecentPredictions.begin(), firstCurrentIt);
+
+ // Fuzzily match the new ground truth's timestamp to recent predictions' targetTimestamp and
+ // update the corresponding metrics.
+ for (const PredictionPoint& prediction : mRecentPredictions) {
+ if ((prediction.targetTimestamp >
+ groundTruthPoint.timestamp - fuzzy_association_time_delta) &&
+ (prediction.targetTimestamp <
+ groundTruthPoint.timestamp + fuzzy_association_time_delta)) {
+ updateAggregatedMetrics(prediction);
+ }
+ }
+}
+
+void MotionPredictorMetricsManager::updateAggregatedMetrics(
+ const PredictionPoint& predictionPoint) {
+ if (mRecentGroundTruthPoints.size() < 2) {
+ return;
+ }
+
+ const GroundTruthPoint& latestGroundTruthPoint = mRecentGroundTruthPoints.back();
+ const GroundTruthPoint& previousGroundTruthPoint =
+ mRecentGroundTruthPoints[mRecentGroundTruthPoints.size() - 2];
+ // Calculate prediction error vector.
+ const Eigen::Vector2f groundTruthTrajectory =
+ latestGroundTruthPoint.position - previousGroundTruthPoint.position;
+ const Eigen::Vector2f predictionTrajectory =
+ predictionPoint.position - previousGroundTruthPoint.position;
+ const Eigen::Vector2f predictionError = predictionTrajectory - groundTruthTrajectory;
+
+ // By default, prediction error counts fully as both off-trajectory and along-trajectory error.
+ // This serves as the fallback when the two most recent ground truth points are equal.
+ const float predictionErrorNorm = predictionError.norm();
+ float alongTrajectoryError = predictionErrorNorm;
+ float offTrajectoryError = predictionErrorNorm;
+ if (groundTruthTrajectory.squaredNorm() > 0) {
+ // Rotate the prediction error vector by the angle of the ground truth trajectory vector.
+ // This yields a vector whose first component is the along-trajectory error and whose
+ // second component is the off-trajectory error.
+ const float theta = std::atan2(groundTruthTrajectory[1], groundTruthTrajectory[0]);
+ const Eigen::Vector2f rotatedPredictionError = Eigen::Rotation2Df(-theta) * predictionError;
+ alongTrajectoryError = rotatedPredictionError[0];
+ offTrajectoryError = rotatedPredictionError[1];
+ }
+
+ // Compute the multiple of mPredictionInterval nearest to the amount of time into the
+ // future being predicted. This serves as the time bucket index into mAggregatedMetrics.
+ const float timestampDeltaFloat =
+ static_cast<float>(predictionPoint.targetTimestamp - predictionPoint.originTimestamp);
+ const size_t tIndex =
+ static_cast<size_t>(std::round(timestampDeltaFloat / mPredictionInterval - 1));
+
+ // Aggregate values into "general errors".
+ mAggregatedMetrics[tIndex].alongTrajectoryErrorSum += alongTrajectoryError;
+ mAggregatedMetrics[tIndex].alongTrajectorySumSquaredErrors +=
+ alongTrajectoryError * alongTrajectoryError;
+ mAggregatedMetrics[tIndex].offTrajectorySumSquaredErrors +=
+ offTrajectoryError * offTrajectoryError;
+ const float pressureError = predictionPoint.pressure - latestGroundTruthPoint.pressure;
+ mAggregatedMetrics[tIndex].pressureSumSquaredErrors += pressureError * pressureError;
+ ++mAggregatedMetrics[tIndex].generalErrorsCount;
+
+ // Aggregate values into high-velocity metrics, if we are in one of the last two time buckets
+ // and the velocity is above the threshold. Velocity here is measured in pixels per second.
+ const float velocity = groundTruthTrajectory.norm() /
+ (static_cast<float>(latestGroundTruthPoint.timestamp -
+ previousGroundTruthPoint.timestamp) /
+ NANOS_PER_SECOND);
+ if ((tIndex + 2 >= mMaxNumPredictions) && (velocity > HIGH_VELOCITY_THRESHOLD)) {
+ mAggregatedMetrics[tIndex].highVelocityAlongTrajectorySse +=
+ alongTrajectoryError * alongTrajectoryError;
+ mAggregatedMetrics[tIndex].highVelocityOffTrajectorySse +=
+ offTrajectoryError * offTrajectoryError;
+ ++mAggregatedMetrics[tIndex].highVelocityErrorsCount;
+ }
+
+ // Compute path length for scale-invariant errors.
+ float pathLength = 0;
+ for (size_t i = 1; i < mRecentGroundTruthPoints.size(); ++i) {
+ pathLength +=
+ (mRecentGroundTruthPoints[i].position - mRecentGroundTruthPoints[i - 1].position)
+ .norm();
+ }
+ // Avoid overweighting errors at the beginning of a stroke: compute the path length as if there
+ // were a full ground truth history by filling in missing segments with the average length.
+ // Note: the "- 1" is needed to translate from number of endpoints to number of segments.
+ pathLength *= static_cast<float>(mRecentGroundTruthPoints.capacity() - 1) /
+ (mRecentGroundTruthPoints.size() - 1);
+ pathLength += PATH_LENGTH_EPSILON; // Ensure path length is nonzero (>= PATH_LENGTH_EPSILON).
+
+ // Compute and aggregate scale-invariant errors.
+ const float scaleInvariantAlongTrajectoryError = alongTrajectoryError / pathLength;
+ const float scaleInvariantOffTrajectoryError = offTrajectoryError / pathLength;
+ mAggregatedMetrics[tIndex].scaleInvariantAlongTrajectorySse +=
+ scaleInvariantAlongTrajectoryError * scaleInvariantAlongTrajectoryError;
+ mAggregatedMetrics[tIndex].scaleInvariantOffTrajectorySse +=
+ scaleInvariantOffTrajectoryError * scaleInvariantOffTrajectoryError;
+ ++mAggregatedMetrics[tIndex].scaleInvariantErrorsCount;
+}
+
+void MotionPredictorMetricsManager::computeAtomFields() {
+ for (size_t i = 0; i < mAggregatedMetrics.size(); ++i) {
+ if (mAggregatedMetrics[i].generalErrorsCount == 0) {
+ // We have not received data corresponding to metrics for this time bucket.
+ continue;
+ }
+
+ mAtomFields[i].deltaTimeBucketMilliseconds =
+ static_cast<int>(mPredictionInterval / NANOS_PER_MILLIS * (i + 1));
+
+ // Note: we need the "* 1000"s below because we report values in integral milli-units.
+
+ { // General errors: reported for every time bucket.
+ const float alongTrajectoryErrorMean = mAggregatedMetrics[i].alongTrajectoryErrorSum /
+ mAggregatedMetrics[i].generalErrorsCount;
+ mAtomFields[i].alongTrajectoryErrorMeanMillipixels =
+ static_cast<int>(alongTrajectoryErrorMean * 1000);
+
+ const float alongTrajectoryMse = mAggregatedMetrics[i].alongTrajectorySumSquaredErrors /
+ mAggregatedMetrics[i].generalErrorsCount;
+ // Take the max with 0 to avoid negative values caused by numerical instability.
+ const float alongTrajectoryErrorVariance =
+ std::max(0.0f,
+ alongTrajectoryMse -
+ alongTrajectoryErrorMean * alongTrajectoryErrorMean);
+ const float alongTrajectoryErrorStd = std::sqrt(alongTrajectoryErrorVariance);
+ mAtomFields[i].alongTrajectoryErrorStdMillipixels =
+ static_cast<int>(alongTrajectoryErrorStd * 1000);
+
+ LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[i].offTrajectorySumSquaredErrors < 0,
+ "mAggregatedMetrics[%zu].offTrajectorySumSquaredErrors = %f should "
+ "not be negative",
+ i, mAggregatedMetrics[i].offTrajectorySumSquaredErrors);
+ const float offTrajectoryRmse =
+ std::sqrt(mAggregatedMetrics[i].offTrajectorySumSquaredErrors /
+ mAggregatedMetrics[i].generalErrorsCount);
+ mAtomFields[i].offTrajectoryRmseMillipixels =
+ static_cast<int>(offTrajectoryRmse * 1000);
+
+ LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[i].pressureSumSquaredErrors < 0,
+ "mAggregatedMetrics[%zu].pressureSumSquaredErrors = %f should not "
+ "be negative",
+ i, mAggregatedMetrics[i].pressureSumSquaredErrors);
+ const float pressureRmse = std::sqrt(mAggregatedMetrics[i].pressureSumSquaredErrors /
+ mAggregatedMetrics[i].generalErrorsCount);
+ mAtomFields[i].pressureRmseMilliunits = static_cast<int>(pressureRmse * 1000);
+ }
+
+ // High-velocity errors: reported only for last two time buckets.
+ // Check if we are in one of the last two time buckets, and there is high-velocity data.
+ if ((i + 2 >= mMaxNumPredictions) && (mAggregatedMetrics[i].highVelocityErrorsCount > 0)) {
+ LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[i].highVelocityAlongTrajectorySse < 0,
+ "mAggregatedMetrics[%zu].highVelocityAlongTrajectorySse = %f "
+ "should not be negative",
+ i, mAggregatedMetrics[i].highVelocityAlongTrajectorySse);
+ const float alongTrajectoryRmse =
+ std::sqrt(mAggregatedMetrics[i].highVelocityAlongTrajectorySse /
+ mAggregatedMetrics[i].highVelocityErrorsCount);
+ mAtomFields[i].highVelocityAlongTrajectoryRmse =
+ static_cast<int>(alongTrajectoryRmse * 1000);
+
+ LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[i].highVelocityOffTrajectorySse < 0,
+ "mAggregatedMetrics[%zu].highVelocityOffTrajectorySse = %f should "
+ "not be negative",
+ i, mAggregatedMetrics[i].highVelocityOffTrajectorySse);
+ const float offTrajectoryRmse =
+ std::sqrt(mAggregatedMetrics[i].highVelocityOffTrajectorySse /
+ mAggregatedMetrics[i].highVelocityErrorsCount);
+ mAtomFields[i].highVelocityOffTrajectoryRmse =
+ static_cast<int>(offTrajectoryRmse * 1000);
+ }
+
+ // Scale-invariant errors: reported only for the last time bucket, where the values
+ // represent an average across all time buckets.
+ if (i + 1 == mMaxNumPredictions) {
+ // Compute error averages.
+ float alongTrajectoryRmseSum = 0;
+ float offTrajectoryRmseSum = 0;
+ for (size_t j = 0; j < mAggregatedMetrics.size(); ++j) {
+ // If we have general errors (checked above), we should always also have
+ // scale-invariant errors.
+ LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantErrorsCount == 0,
+ "mAggregatedMetrics[%zu].scaleInvariantErrorsCount is 0", j);
+
+ LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse < 0,
+ "mAggregatedMetrics[%zu].scaleInvariantAlongTrajectorySse = %f "
+ "should not be negative",
+ j, mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse);
+ alongTrajectoryRmseSum +=
+ std::sqrt(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse /
+ mAggregatedMetrics[j].scaleInvariantErrorsCount);
+
+ LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse < 0,
+ "mAggregatedMetrics[%zu].scaleInvariantOffTrajectorySse = %f "
+ "should not be negative",
+ j, mAggregatedMetrics[j].scaleInvariantOffTrajectorySse);
+ offTrajectoryRmseSum +=
+ std::sqrt(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse /
+ mAggregatedMetrics[j].scaleInvariantErrorsCount);
+ }
+
+ const float averageAlongTrajectoryRmse =
+ alongTrajectoryRmseSum / mAggregatedMetrics.size();
+ mAtomFields.back().scaleInvariantAlongTrajectoryRmse =
+ static_cast<int>(averageAlongTrajectoryRmse * 1000);
+
+ const float averageOffTrajectoryRmse = offTrajectoryRmseSum / mAggregatedMetrics.size();
+ mAtomFields.back().scaleInvariantOffTrajectoryRmse =
+ static_cast<int>(averageOffTrajectoryRmse * 1000);
+ }
+ }
+}
+
+void MotionPredictorMetricsManager::reportMetrics() {
+ // Report one atom for each time bucket.
+ for (size_t i = 0; i < mAtomFields.size(); ++i) {
+ // Call stats_write logging function only on Android targets (not supported on host).
+#ifdef __ANDROID__
+ android::stats::libinput::
+ stats_write(android::stats::libinput::STYLUS_PREDICTION_METRICS_REPORTED,
+ /*stylus_vendor_id=*/0,
+ /*stylus_product_id=*/0, mAtomFields[i].deltaTimeBucketMilliseconds,
+ mAtomFields[i].alongTrajectoryErrorMeanMillipixels,
+ mAtomFields[i].alongTrajectoryErrorStdMillipixels,
+ mAtomFields[i].offTrajectoryRmseMillipixels,
+ mAtomFields[i].pressureRmseMilliunits,
+ mAtomFields[i].highVelocityAlongTrajectoryRmse,
+ mAtomFields[i].highVelocityOffTrajectoryRmse,
+ mAtomFields[i].scaleInvariantAlongTrajectoryRmse,
+ mAtomFields[i].scaleInvariantOffTrajectoryRmse);
+#endif
+ }
+
+ // Set mock atom fields, if available.
+ if (mMockLoggedAtomFields != nullptr) {
+ *mMockLoggedAtomFields = mAtomFields;
+ }
+}
+
+} // namespace android
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 86b996b..e7224ff 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -20,6 +20,7 @@
"InputPublisherAndConsumer_test.cpp",
"InputVerifier_test.cpp",
"MotionPredictor_test.cpp",
+ "MotionPredictorMetricsManager_test.cpp",
"RingBuffer_test.cpp",
"TfLiteMotionPredictor_test.cpp",
"TouchResampling_test.cpp",
@@ -52,13 +53,6 @@
undefined: true,
},
},
- target: {
- host: {
- sanitize: {
- address: true,
- },
- },
- },
shared_libs: [
"libbase",
"libbinder",
@@ -77,6 +71,21 @@
unit_test: true,
},
test_suites: ["device-tests"],
+ target: {
+ host: {
+ sanitize: {
+ address: true,
+ },
+ },
+ android: {
+ static_libs: [
+ // Stats logging library and its dependencies.
+ "libstatslog_libinput",
+ "libstatsbootstrap",
+ "android.os.statsbootstrap_aidl-cpp",
+ ],
+ },
+ },
}
// NOTE: This is a compile time test, and does not need to be
diff --git a/libs/input/tests/MotionPredictorMetricsManager_test.cpp b/libs/input/tests/MotionPredictorMetricsManager_test.cpp
new file mode 100644
index 0000000..b420a5a
--- /dev/null
+++ b/libs/input/tests/MotionPredictorMetricsManager_test.cpp
@@ -0,0 +1,972 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <input/MotionPredictor.h>
+
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <numeric>
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <input/InputEventBuilders.h>
+#include <utils/Timers.h> // for nsecs_t
+
+#include "Eigen/Core"
+#include "Eigen/Geometry"
+
+namespace android {
+namespace {
+
+using ::testing::FloatNear;
+using ::testing::Matches;
+
+using GroundTruthPoint = MotionPredictorMetricsManager::GroundTruthPoint;
+using PredictionPoint = MotionPredictorMetricsManager::PredictionPoint;
+using AtomFields = MotionPredictorMetricsManager::AtomFields;
+
+inline constexpr int NANOS_PER_MILLIS = 1'000'000;
+
+inline constexpr nsecs_t TEST_INITIAL_TIMESTAMP = 1'000'000'000;
+inline constexpr size_t TEST_MAX_NUM_PREDICTIONS = 5;
+inline constexpr nsecs_t TEST_PREDICTION_INTERVAL_NANOS = 12'500'000 / 3; // 1 / (240 hz)
+inline constexpr int NO_DATA_SENTINEL = MotionPredictorMetricsManager::NO_DATA_SENTINEL;
+
+// Parameters:
+// • arg: Eigen::Vector2f
+// • target: Eigen::Vector2f
+// • epsilon: float
+MATCHER_P2(Vector2fNear, target, epsilon, "") {
+ return Matches(FloatNear(target[0], epsilon))(arg[0]) &&
+ Matches(FloatNear(target[1], epsilon))(arg[1]);
+}
+
+// Parameters:
+// • arg: PredictionPoint
+// • target: PredictionPoint
+// • epsilon: float
+MATCHER_P2(PredictionPointNear, target, epsilon, "") {
+ if (!Matches(Vector2fNear(target.position, epsilon))(arg.position)) {
+ *result_listener << "Position mismatch. Actual: (" << arg.position[0] << ", "
+ << arg.position[1] << "), expected: (" << target.position[0] << ", "
+ << target.position[1] << ")";
+ return false;
+ }
+ if (!Matches(FloatNear(target.pressure, epsilon))(arg.pressure)) {
+ *result_listener << "Pressure mismatch. Actual: " << arg.pressure
+ << ", expected: " << target.pressure;
+ return false;
+ }
+ if (arg.originTimestamp != target.originTimestamp) {
+ *result_listener << "Origin timestamp mismatch. Actual: " << arg.originTimestamp
+ << ", expected: " << target.originTimestamp;
+ return false;
+ }
+ if (arg.targetTimestamp != target.targetTimestamp) {
+ *result_listener << "Target timestamp mismatch. Actual: " << arg.targetTimestamp
+ << ", expected: " << target.targetTimestamp;
+ return false;
+ }
+ return true;
+}
+
+// --- Mathematical helper functions. ---
+
+template <typename T>
+T average(std::vector<T> values) {
+ return std::accumulate(values.begin(), values.end(), T{}) / static_cast<T>(values.size());
+}
+
+template <typename T>
+T standardDeviation(std::vector<T> values) {
+ T mean = average(values);
+ T accumulator = {};
+ for (const T value : values) {
+ accumulator += value * value - mean * mean;
+ }
+ // Take the max with 0 to avoid negative values caused by numerical instability.
+ return std::sqrt(std::max(T{}, accumulator) / static_cast<T>(values.size()));
+}
+
+template <typename T>
+T rmse(std::vector<T> errors) {
+ T sse = {};
+ for (const T error : errors) {
+ sse += error * error;
+ }
+ return std::sqrt(sse / static_cast<T>(errors.size()));
+}
+
+TEST(MathematicalHelperFunctionTest, Average) {
+ std::vector<float> values{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ EXPECT_EQ(5.5f, average(values));
+}
+
+TEST(MathematicalHelperFunctionTest, StandardDeviation) {
+ // https://www.calculator.net/standard-deviation-calculator.html?numberinputs=10%2C+12%2C+23%2C+23%2C+16%2C+23%2C+21%2C+16
+ std::vector<float> values{10, 12, 23, 23, 16, 23, 21, 16};
+ EXPECT_FLOAT_EQ(4.8989794855664f, standardDeviation(values));
+}
+
+TEST(MathematicalHelperFunctionTest, Rmse) {
+ std::vector<float> errors{1, 5, 7, 7, 8, 20};
+ EXPECT_FLOAT_EQ(9.899494937f, rmse(errors));
+}
+
+// --- MotionEvent-related helper functions. ---
+
+// Creates a MotionEvent corresponding to the given GroundTruthPoint.
+MotionEvent makeMotionEvent(const GroundTruthPoint& groundTruthPoint) {
+ // Build single pointer of type STYLUS, with coordinates from groundTruthPoint.
+ PointerBuilder pointerBuilder =
+ PointerBuilder(/*id=*/0, ToolType::STYLUS)
+ .x(groundTruthPoint.position[1])
+ .y(groundTruthPoint.position[0])
+ .axis(AMOTION_EVENT_AXIS_PRESSURE, groundTruthPoint.pressure);
+ return MotionEventBuilder(/*action=*/AMOTION_EVENT_ACTION_MOVE,
+ /*source=*/AINPUT_SOURCE_CLASS_POINTER)
+ .eventTime(groundTruthPoint.timestamp)
+ .pointer(pointerBuilder)
+ .build();
+}
+
+// Creates a MotionEvent corresponding to the given sequence of PredictionPoints.
+MotionEvent makeMotionEvent(const std::vector<PredictionPoint>& predictionPoints) {
+ // Build single pointer of type STYLUS, with coordinates from first prediction point.
+ PointerBuilder pointerBuilder =
+ PointerBuilder(/*id=*/0, ToolType::STYLUS)
+ .x(predictionPoints[0].position[1])
+ .y(predictionPoints[0].position[0])
+ .axis(AMOTION_EVENT_AXIS_PRESSURE, predictionPoints[0].pressure);
+ MotionEvent predictionEvent =
+ MotionEventBuilder(
+ /*action=*/AMOTION_EVENT_ACTION_MOVE, /*source=*/AINPUT_SOURCE_CLASS_POINTER)
+ .eventTime(predictionPoints[0].targetTimestamp)
+ .pointer(pointerBuilder)
+ .build();
+ for (size_t i = 1; i < predictionPoints.size(); ++i) {
+ PointerCoords coords =
+ PointerBuilder(/*id=*/0, ToolType::STYLUS)
+ .x(predictionPoints[i].position[1])
+ .y(predictionPoints[i].position[0])
+ .axis(AMOTION_EVENT_AXIS_PRESSURE, predictionPoints[i].pressure)
+ .buildCoords();
+ predictionEvent.addSample(predictionPoints[i].targetTimestamp, &coords);
+ }
+ return predictionEvent;
+}
+
+// Creates a MotionEvent corresponding to a stylus lift (UP) ground truth event.
+MotionEvent makeLiftMotionEvent() {
+ return MotionEventBuilder(/*action=*/AMOTION_EVENT_ACTION_UP,
+ /*source=*/AINPUT_SOURCE_CLASS_POINTER)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS))
+ .build();
+}
+
+TEST(MakeMotionEventTest, MakeGroundTruthMotionEvent) {
+ const GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10.0f, 20.0f),
+ .pressure = 0.6f},
+ .timestamp = TEST_INITIAL_TIMESTAMP};
+ const MotionEvent groundTruthMotionEvent = makeMotionEvent(groundTruthPoint);
+
+ ASSERT_EQ(1u, groundTruthMotionEvent.getPointerCount());
+ // Note: a MotionEvent's "history size" is one less than its number of samples.
+ ASSERT_EQ(0u, groundTruthMotionEvent.getHistorySize());
+ EXPECT_EQ(groundTruthPoint.position[0], groundTruthMotionEvent.getRawPointerCoords(0)->getY());
+ EXPECT_EQ(groundTruthPoint.position[1], groundTruthMotionEvent.getRawPointerCoords(0)->getX());
+ EXPECT_EQ(groundTruthPoint.pressure,
+ groundTruthMotionEvent.getRawPointerCoords(0)->getAxisValue(
+ AMOTION_EVENT_AXIS_PRESSURE));
+ EXPECT_EQ(AMOTION_EVENT_ACTION_MOVE, groundTruthMotionEvent.getAction());
+}
+
+TEST(MakeMotionEventTest, MakePredictionMotionEvent) {
+ const nsecs_t originTimestamp = TEST_INITIAL_TIMESTAMP;
+ const std::vector<PredictionPoint>
+ predictionPoints{{{.position = Eigen::Vector2f(10.0f, 20.0f), .pressure = 0.6f},
+ .originTimestamp = originTimestamp,
+ .targetTimestamp = originTimestamp + 5 * NANOS_PER_MILLIS},
+ {{.position = Eigen::Vector2f(11.0f, 22.0f), .pressure = 0.5f},
+ .originTimestamp = originTimestamp,
+ .targetTimestamp = originTimestamp + 10 * NANOS_PER_MILLIS},
+ {{.position = Eigen::Vector2f(12.0f, 24.0f), .pressure = 0.4f},
+ .originTimestamp = originTimestamp,
+ .targetTimestamp = originTimestamp + 15 * NANOS_PER_MILLIS}};
+ const MotionEvent predictionMotionEvent = makeMotionEvent(predictionPoints);
+
+ ASSERT_EQ(1u, predictionMotionEvent.getPointerCount());
+ // Note: a MotionEvent's "history size" is one less than its number of samples.
+ ASSERT_EQ(predictionPoints.size(), predictionMotionEvent.getHistorySize() + 1);
+ for (size_t i = 0; i < predictionPoints.size(); ++i) {
+ SCOPED_TRACE(testing::Message() << "i = " << i);
+ const PointerCoords coords = *predictionMotionEvent.getHistoricalRawPointerCoords(
+ /*pointerIndex=*/0, /*historicalIndex=*/i);
+ EXPECT_EQ(predictionPoints[i].position[0], coords.getY());
+ EXPECT_EQ(predictionPoints[i].position[1], coords.getX());
+ EXPECT_EQ(predictionPoints[i].pressure, coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+ // Note: originTimestamp is discarded when converting PredictionPoint to MotionEvent.
+ EXPECT_EQ(predictionPoints[i].targetTimestamp,
+ predictionMotionEvent.getHistoricalEventTime(i));
+ EXPECT_EQ(AMOTION_EVENT_ACTION_MOVE, predictionMotionEvent.getAction());
+ }
+}
+
+TEST(MakeMotionEventTest, MakeLiftMotionEvent) {
+ const MotionEvent liftMotionEvent = makeLiftMotionEvent();
+ ASSERT_EQ(1u, liftMotionEvent.getPointerCount());
+ // Note: a MotionEvent's "history size" is one less than its number of samples.
+ ASSERT_EQ(0u, liftMotionEvent.getHistorySize());
+ EXPECT_EQ(AMOTION_EVENT_ACTION_UP, liftMotionEvent.getAction());
+}
+
+// --- Ground-truth-generation helper functions. ---
+
+std::vector<GroundTruthPoint> generateConstantGroundTruthPoints(
+ const GroundTruthPoint& groundTruthPoint, size_t numPoints) {
+ std::vector<GroundTruthPoint> groundTruthPoints;
+ nsecs_t timestamp = groundTruthPoint.timestamp;
+ for (size_t i = 0; i < numPoints; ++i) {
+ groundTruthPoints.emplace_back(groundTruthPoint);
+ groundTruthPoints.back().timestamp = timestamp;
+ timestamp += TEST_PREDICTION_INTERVAL_NANOS;
+ }
+ return groundTruthPoints;
+}
+
+// This function uses the coordinate system (y, x), with +y pointing downwards and +x pointing
+// rightwards. Angles are measured counterclockwise from down (+y).
+std::vector<GroundTruthPoint> generateCircularArcGroundTruthPoints(Eigen::Vector2f initialPosition,
+ float initialAngle,
+ float velocity,
+ float turningAngle,
+ size_t numPoints) {
+ std::vector<GroundTruthPoint> groundTruthPoints;
+ // Create first point.
+ if (numPoints > 0) {
+ groundTruthPoints.push_back({{.position = initialPosition, .pressure = 0.0f},
+ .timestamp = TEST_INITIAL_TIMESTAMP});
+ }
+ float trajectoryAngle = initialAngle; // measured counterclockwise from +y axis.
+ for (size_t i = 1; i < numPoints; ++i) {
+ const Eigen::Vector2f trajectory =
+ Eigen::Rotation2D(trajectoryAngle) * Eigen::Vector2f(1, 0);
+ groundTruthPoints.push_back(
+ {{.position = groundTruthPoints.back().position + velocity * trajectory,
+ .pressure = 0.0f},
+ .timestamp = groundTruthPoints.back().timestamp + TEST_PREDICTION_INTERVAL_NANOS});
+ trajectoryAngle += turningAngle;
+ }
+ return groundTruthPoints;
+}
+
+TEST(GenerateConstantGroundTruthPointsTest, BasicTest) {
+ const GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10, 20), .pressure = 0.3f},
+ .timestamp = TEST_INITIAL_TIMESTAMP};
+ const std::vector<GroundTruthPoint> groundTruthPoints =
+ generateConstantGroundTruthPoints(groundTruthPoint, /*numPoints=*/3);
+
+ ASSERT_EQ(3u, groundTruthPoints.size());
+ // First point.
+ EXPECT_EQ(groundTruthPoints[0].position, groundTruthPoint.position);
+ EXPECT_EQ(groundTruthPoints[0].pressure, groundTruthPoint.pressure);
+ EXPECT_EQ(groundTruthPoints[0].timestamp, groundTruthPoint.timestamp);
+ // Second point.
+ EXPECT_EQ(groundTruthPoints[1].position, groundTruthPoint.position);
+ EXPECT_EQ(groundTruthPoints[1].pressure, groundTruthPoint.pressure);
+ EXPECT_GT(groundTruthPoints[1].timestamp, groundTruthPoints[0].timestamp);
+ // Third point.
+ EXPECT_EQ(groundTruthPoints[2].position, groundTruthPoint.position);
+ EXPECT_EQ(groundTruthPoints[2].pressure, groundTruthPoint.pressure);
+ EXPECT_GT(groundTruthPoints[2].timestamp, groundTruthPoints[1].timestamp);
+}
+
+TEST(GenerateCircularArcGroundTruthTest, StraightLineUpwards) {
+ const std::vector<GroundTruthPoint> groundTruthPoints = generateCircularArcGroundTruthPoints(
+ /*initialPosition=*/Eigen::Vector2f(0, 0),
+ /*initialAngle=*/M_PI,
+ /*velocity=*/1.0f,
+ /*turningAngle=*/0.0f,
+ /*numPoints=*/3);
+
+ ASSERT_EQ(3u, groundTruthPoints.size());
+ EXPECT_THAT(groundTruthPoints[0].position, Vector2fNear(Eigen::Vector2f(0, 0), 1e-6));
+ EXPECT_THAT(groundTruthPoints[1].position, Vector2fNear(Eigen::Vector2f(-1, 0), 1e-6));
+ EXPECT_THAT(groundTruthPoints[2].position, Vector2fNear(Eigen::Vector2f(-2, 0), 1e-6));
+ // Check that timestamps are increasing between consecutive ground truth points.
+ EXPECT_GT(groundTruthPoints[1].timestamp, groundTruthPoints[0].timestamp);
+ EXPECT_GT(groundTruthPoints[2].timestamp, groundTruthPoints[1].timestamp);
+}
+
+TEST(GenerateCircularArcGroundTruthTest, CounterclockwiseSquare) {
+ // Generate points in a counterclockwise unit square starting pointing right.
+ const std::vector<GroundTruthPoint> groundTruthPoints = generateCircularArcGroundTruthPoints(
+ /*initialPosition=*/Eigen::Vector2f(10, 100),
+ /*initialAngle=*/M_PI_2,
+ /*velocity=*/1.0f,
+ /*turningAngle=*/M_PI_2,
+ /*numPoints=*/5);
+
+ ASSERT_EQ(5u, groundTruthPoints.size());
+ EXPECT_THAT(groundTruthPoints[0].position, Vector2fNear(Eigen::Vector2f(10, 100), 1e-6));
+ EXPECT_THAT(groundTruthPoints[1].position, Vector2fNear(Eigen::Vector2f(10, 101), 1e-6));
+ EXPECT_THAT(groundTruthPoints[2].position, Vector2fNear(Eigen::Vector2f(9, 101), 1e-6));
+ EXPECT_THAT(groundTruthPoints[3].position, Vector2fNear(Eigen::Vector2f(9, 100), 1e-6));
+ EXPECT_THAT(groundTruthPoints[4].position, Vector2fNear(Eigen::Vector2f(10, 100), 1e-6));
+}
+
+// --- Prediction-generation helper functions. ---
+
+// Creates a sequence of predictions with values equal to those of the given GroundTruthPoint.
+std::vector<PredictionPoint> generateConstantPredictions(const GroundTruthPoint& groundTruthPoint) {
+ std::vector<PredictionPoint> predictions;
+ nsecs_t predictionTimestamp = groundTruthPoint.timestamp + TEST_PREDICTION_INTERVAL_NANOS;
+ for (size_t j = 0; j < TEST_MAX_NUM_PREDICTIONS; ++j) {
+ predictions.push_back(PredictionPoint{{.position = groundTruthPoint.position,
+ .pressure = groundTruthPoint.pressure},
+ .originTimestamp = groundTruthPoint.timestamp,
+ .targetTimestamp = predictionTimestamp});
+ predictionTimestamp += TEST_PREDICTION_INTERVAL_NANOS;
+ }
+ return predictions;
+}
+
+// Generates TEST_MAX_NUM_PREDICTIONS predictions from the given most recent two ground truth points
+// by linear extrapolation of position and pressure. The interval between consecutive predictions'
+// timestamps is TEST_PREDICTION_INTERVAL_NANOS.
+std::vector<PredictionPoint> generatePredictionsByLinearExtrapolation(
+ const GroundTruthPoint& firstGroundTruth, const GroundTruthPoint& secondGroundTruth) {
+ // Precompute deltas.
+ const Eigen::Vector2f trajectory = secondGroundTruth.position - firstGroundTruth.position;
+ const float deltaPressure = secondGroundTruth.pressure - firstGroundTruth.pressure;
+ // Compute predictions.
+ std::vector<PredictionPoint> predictions;
+ Eigen::Vector2f predictionPosition = secondGroundTruth.position;
+ float predictionPressure = secondGroundTruth.pressure;
+ nsecs_t predictionTargetTimestamp = secondGroundTruth.timestamp;
+ for (size_t i = 0; i < TEST_MAX_NUM_PREDICTIONS; ++i) {
+ predictionPosition += trajectory;
+ predictionPressure += deltaPressure;
+ predictionTargetTimestamp += TEST_PREDICTION_INTERVAL_NANOS;
+ predictions.push_back(
+ PredictionPoint{{.position = predictionPosition, .pressure = predictionPressure},
+ .originTimestamp = secondGroundTruth.timestamp,
+ .targetTimestamp = predictionTargetTimestamp});
+ }
+ return predictions;
+}
+
+TEST(GeneratePredictionsTest, GenerateConstantPredictions) {
+ const GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10, 20), .pressure = 0.3f},
+ .timestamp = TEST_INITIAL_TIMESTAMP};
+ const std::vector<PredictionPoint> predictionPoints =
+ generateConstantPredictions(groundTruthPoint);
+
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, predictionPoints.size());
+ for (size_t i = 0; i < predictionPoints.size(); ++i) {
+ SCOPED_TRACE(testing::Message() << "i = " << i);
+ EXPECT_THAT(predictionPoints[i].position, Vector2fNear(groundTruthPoint.position, 1e-6));
+ EXPECT_THAT(predictionPoints[i].pressure, FloatNear(groundTruthPoint.pressure, 1e-6));
+ EXPECT_EQ(predictionPoints[i].originTimestamp, groundTruthPoint.timestamp);
+ EXPECT_EQ(predictionPoints[i].targetTimestamp,
+ groundTruthPoint.timestamp +
+ static_cast<nsecs_t>(i + 1) * TEST_PREDICTION_INTERVAL_NANOS);
+ }
+}
+
+TEST(GeneratePredictionsTest, LinearExtrapolationFromTwoPoints) {
+ const nsecs_t initialTimestamp = TEST_INITIAL_TIMESTAMP;
+ const std::vector<PredictionPoint> predictionPoints = generatePredictionsByLinearExtrapolation(
+ GroundTruthPoint{{.position = Eigen::Vector2f(100, 200), .pressure = 0.9f},
+ .timestamp = initialTimestamp},
+ GroundTruthPoint{{.position = Eigen::Vector2f(105, 190), .pressure = 0.8f},
+ .timestamp = initialTimestamp + TEST_PREDICTION_INTERVAL_NANOS});
+
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, predictionPoints.size());
+ const nsecs_t originTimestamp = initialTimestamp + TEST_PREDICTION_INTERVAL_NANOS;
+ EXPECT_THAT(predictionPoints[0],
+ PredictionPointNear(PredictionPoint{{.position = Eigen::Vector2f(110, 180),
+ .pressure = 0.7f},
+ .originTimestamp = originTimestamp,
+ .targetTimestamp = originTimestamp +
+ TEST_PREDICTION_INTERVAL_NANOS},
+ 0.001));
+ EXPECT_THAT(predictionPoints[1],
+ PredictionPointNear(PredictionPoint{{.position = Eigen::Vector2f(115, 170),
+ .pressure = 0.6f},
+ .originTimestamp = originTimestamp,
+ .targetTimestamp = originTimestamp +
+ 2 * TEST_PREDICTION_INTERVAL_NANOS},
+ 0.001));
+ EXPECT_THAT(predictionPoints[2],
+ PredictionPointNear(PredictionPoint{{.position = Eigen::Vector2f(120, 160),
+ .pressure = 0.5f},
+ .originTimestamp = originTimestamp,
+ .targetTimestamp = originTimestamp +
+ 3 * TEST_PREDICTION_INTERVAL_NANOS},
+ 0.001));
+ EXPECT_THAT(predictionPoints[3],
+ PredictionPointNear(PredictionPoint{{.position = Eigen::Vector2f(125, 150),
+ .pressure = 0.4f},
+ .originTimestamp = originTimestamp,
+ .targetTimestamp = originTimestamp +
+ 4 * TEST_PREDICTION_INTERVAL_NANOS},
+ 0.001));
+ EXPECT_THAT(predictionPoints[4],
+ PredictionPointNear(PredictionPoint{{.position = Eigen::Vector2f(130, 140),
+ .pressure = 0.3f},
+ .originTimestamp = originTimestamp,
+ .targetTimestamp = originTimestamp +
+ 5 * TEST_PREDICTION_INTERVAL_NANOS},
+ 0.001));
+}
+
+// Generates predictions by linear extrapolation for each consecutive pair of ground truth points
+// (see the comment for the above function for further explanation). Returns a vector of vectors of
+// prediction points, where the first index is the source ground truth index, and the second is the
+// prediction target index.
+//
+// The returned vector has size equal to the input vector, and the first element of the returned
+// vector is always empty.
+std::vector<std::vector<PredictionPoint>> generateAllPredictionsByLinearExtrapolation(
+ const std::vector<GroundTruthPoint>& groundTruthPoints) {
+ std::vector<std::vector<PredictionPoint>> allPredictions;
+ allPredictions.emplace_back();
+ for (size_t i = 1; i < groundTruthPoints.size(); ++i) {
+ allPredictions.push_back(generatePredictionsByLinearExtrapolation(groundTruthPoints[i - 1],
+ groundTruthPoints[i]));
+ }
+ return allPredictions;
+}
+
+TEST(GeneratePredictionsTest, GenerateAllPredictions) {
+ const nsecs_t initialTimestamp = TEST_INITIAL_TIMESTAMP;
+ std::vector<GroundTruthPoint>
+ groundTruthPoints{GroundTruthPoint{{.position = Eigen::Vector2f(0, 0),
+ .pressure = 0.5f},
+ .timestamp = initialTimestamp},
+ GroundTruthPoint{{.position = Eigen::Vector2f(1, -1),
+ .pressure = 0.51f},
+ .timestamp = initialTimestamp +
+ 2 * TEST_PREDICTION_INTERVAL_NANOS},
+ GroundTruthPoint{{.position = Eigen::Vector2f(2, -2),
+ .pressure = 0.52f},
+ .timestamp = initialTimestamp +
+ 3 * TEST_PREDICTION_INTERVAL_NANOS}};
+
+ const std::vector<std::vector<PredictionPoint>> allPredictions =
+ generateAllPredictionsByLinearExtrapolation(groundTruthPoints);
+
+ // Check format of allPredictions data.
+ ASSERT_EQ(groundTruthPoints.size(), allPredictions.size());
+ EXPECT_TRUE(allPredictions[0].empty());
+ EXPECT_EQ(TEST_MAX_NUM_PREDICTIONS, allPredictions[1].size());
+ EXPECT_EQ(TEST_MAX_NUM_PREDICTIONS, allPredictions[2].size());
+
+ // Check positions of predictions generated from first pair of ground truth points.
+ EXPECT_THAT(allPredictions[1][0].position, Vector2fNear(Eigen::Vector2f(2, -2), 1e-9));
+ EXPECT_THAT(allPredictions[1][1].position, Vector2fNear(Eigen::Vector2f(3, -3), 1e-9));
+ EXPECT_THAT(allPredictions[1][2].position, Vector2fNear(Eigen::Vector2f(4, -4), 1e-9));
+ EXPECT_THAT(allPredictions[1][3].position, Vector2fNear(Eigen::Vector2f(5, -5), 1e-9));
+ EXPECT_THAT(allPredictions[1][4].position, Vector2fNear(Eigen::Vector2f(6, -6), 1e-9));
+
+ // Check pressures of predictions generated from first pair of ground truth points.
+ EXPECT_FLOAT_EQ(0.52f, allPredictions[1][0].pressure);
+ EXPECT_FLOAT_EQ(0.53f, allPredictions[1][1].pressure);
+ EXPECT_FLOAT_EQ(0.54f, allPredictions[1][2].pressure);
+ EXPECT_FLOAT_EQ(0.55f, allPredictions[1][3].pressure);
+ EXPECT_FLOAT_EQ(0.56f, allPredictions[1][4].pressure);
+}
+
+// --- Prediction error helper functions. ---
+
+struct GeneralPositionErrors {
+ float alongTrajectoryErrorMean;
+ float alongTrajectoryErrorStd;
+ float offTrajectoryRmse;
+};
+
+// Inputs:
+// • Vector of ground truth points
+// • Vector of vectors of prediction points, where the first index is the source ground truth
+// index, and the second is the prediction target index.
+//
+// Returns a vector of GeneralPositionErrors, indexed by prediction time delta bucket.
+std::vector<GeneralPositionErrors> computeGeneralPositionErrors(
+ const std::vector<GroundTruthPoint>& groundTruthPoints,
+ const std::vector<std::vector<PredictionPoint>>& predictionPoints) {
+ // Aggregate errors by time bucket (prediction target index).
+ std::vector<GeneralPositionErrors> generalPostitionErrors;
+ for (size_t predictionTargetIndex = 0; predictionTargetIndex < TEST_MAX_NUM_PREDICTIONS;
+ ++predictionTargetIndex) {
+ std::vector<float> alongTrajectoryErrors;
+ std::vector<float> alongTrajectorySquaredErrors;
+ std::vector<float> offTrajectoryErrors;
+ for (size_t sourceGroundTruthIndex = 1; sourceGroundTruthIndex < groundTruthPoints.size();
+ ++sourceGroundTruthIndex) {
+ const size_t targetGroundTruthIndex =
+ sourceGroundTruthIndex + predictionTargetIndex + 1;
+ // Only include errors for points with a ground truth value.
+ if (targetGroundTruthIndex < groundTruthPoints.size()) {
+ const Eigen::Vector2f trajectory =
+ (groundTruthPoints[targetGroundTruthIndex].position -
+ groundTruthPoints[targetGroundTruthIndex - 1].position)
+ .normalized();
+ const Eigen::Vector2f orthogonalTrajectory =
+ Eigen::Rotation2Df(M_PI_2) * trajectory;
+ const Eigen::Vector2f positionError =
+ predictionPoints[sourceGroundTruthIndex][predictionTargetIndex].position -
+ groundTruthPoints[targetGroundTruthIndex].position;
+ alongTrajectoryErrors.push_back(positionError.dot(trajectory));
+ alongTrajectorySquaredErrors.push_back(alongTrajectoryErrors.back() *
+ alongTrajectoryErrors.back());
+ offTrajectoryErrors.push_back(positionError.dot(orthogonalTrajectory));
+ }
+ }
+ generalPostitionErrors.push_back(
+ {.alongTrajectoryErrorMean = average(alongTrajectoryErrors),
+ .alongTrajectoryErrorStd = standardDeviation(alongTrajectoryErrors),
+ .offTrajectoryRmse = rmse(offTrajectoryErrors)});
+ }
+ return generalPostitionErrors;
+}
+
+// Inputs:
+// • Vector of ground truth points
+// • Vector of vectors of prediction points, where the first index is the source ground truth
+// index, and the second is the prediction target index.
+//
+// Returns a vector of pressure RMSEs, indexed by prediction time delta bucket.
+std::vector<float> computePressureRmses(
+ const std::vector<GroundTruthPoint>& groundTruthPoints,
+ const std::vector<std::vector<PredictionPoint>>& predictionPoints) {
+ // Aggregate errors by time bucket (prediction target index).
+ std::vector<float> pressureRmses;
+ for (size_t predictionTargetIndex = 0; predictionTargetIndex < TEST_MAX_NUM_PREDICTIONS;
+ ++predictionTargetIndex) {
+ std::vector<float> pressureErrors;
+ for (size_t sourceGroundTruthIndex = 1; sourceGroundTruthIndex < groundTruthPoints.size();
+ ++sourceGroundTruthIndex) {
+ const size_t targetGroundTruthIndex =
+ sourceGroundTruthIndex + predictionTargetIndex + 1;
+ // Only include errors for points with a ground truth value.
+ if (targetGroundTruthIndex < groundTruthPoints.size()) {
+ pressureErrors.push_back(
+ predictionPoints[sourceGroundTruthIndex][predictionTargetIndex].pressure -
+ groundTruthPoints[targetGroundTruthIndex].pressure);
+ }
+ }
+ pressureRmses.push_back(rmse(pressureErrors));
+ }
+ return pressureRmses;
+}
+
+TEST(ErrorComputationHelperTest, ComputeGeneralPositionErrorsSimpleTest) {
+ std::vector<GroundTruthPoint> groundTruthPoints =
+ generateConstantGroundTruthPoints(GroundTruthPoint{{.position = Eigen::Vector2f(0, 0),
+ .pressure = 0.0f},
+ .timestamp = TEST_INITIAL_TIMESTAMP},
+ /*numPoints=*/TEST_MAX_NUM_PREDICTIONS + 2);
+ groundTruthPoints[3].position = Eigen::Vector2f(1, 0);
+ groundTruthPoints[4].position = Eigen::Vector2f(1, 1);
+ groundTruthPoints[5].position = Eigen::Vector2f(1, 3);
+ groundTruthPoints[6].position = Eigen::Vector2f(2, 3);
+
+ std::vector<std::vector<PredictionPoint>> predictionPoints =
+ generateAllPredictionsByLinearExtrapolation(groundTruthPoints);
+
+ // The generated predictions look like:
+ //
+ // | Source | Target Ground Truth Index |
+ // | Index | 2 | 3 | 4 | 5 | 6 |
+ // |------------|--------|--------|--------|--------|--------|
+ // | 1 | (0, 0) | (0, 0) | (0, 0) | (0, 0) | (0, 0) |
+ // | 2 | | (0, 0) | (0, 0) | (0, 0) | (0, 0) |
+ // | 3 | | | (2, 0) | (3, 0) | (4, 0) |
+ // | 4 | | | | (1, 2) | (1, 3) |
+ // | 5 | | | | | (1, 5) |
+ // |---------------------------------------------------------|
+ // | Actual Ground Truth Values |
+ // | Position | (0, 0) | (1, 0) | (1, 1) | (1, 3) | (2, 3) |
+ // | Previous | (0, 0) | (0, 0) | (1, 0) | (1, 1) | (1, 3) |
+ //
+ // Note: this table organizes prediction targets by target ground truth index. Metrics are
+ // aggregated across points with the same prediction time bucket index, which is different.
+ // Each down-right diagonal from this table gives us points from a unique time bucket.
+
+ // Initialize expected prediction errors from the table above. The first time bucket corresponds
+ // to the long diagonal of the table, and subsequent time buckets step up-right from there.
+ const std::vector<std::vector<float>> expectedAlongTrajectoryErrors{{0, -1, -1, -1, -1},
+ {-1, -1, -3, -1},
+ {-1, -3, 2},
+ {-3, -2},
+ {-2}};
+ const std::vector<std::vector<float>> expectedOffTrajectoryErrors{{0, 0, 1, 0, 2},
+ {0, 1, 2, 0},
+ {1, 1, 3},
+ {1, 3},
+ {3}};
+
+ std::vector<GeneralPositionErrors> generalPositionErrors =
+ computeGeneralPositionErrors(groundTruthPoints, predictionPoints);
+
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, generalPositionErrors.size());
+ for (size_t i = 0; i < generalPositionErrors.size(); ++i) {
+ SCOPED_TRACE(testing::Message() << "i = " << i);
+ EXPECT_FLOAT_EQ(average(expectedAlongTrajectoryErrors[i]),
+ generalPositionErrors[i].alongTrajectoryErrorMean);
+ EXPECT_FLOAT_EQ(standardDeviation(expectedAlongTrajectoryErrors[i]),
+ generalPositionErrors[i].alongTrajectoryErrorStd);
+ EXPECT_FLOAT_EQ(rmse(expectedOffTrajectoryErrors[i]),
+ generalPositionErrors[i].offTrajectoryRmse);
+ }
+}
+
+TEST(ErrorComputationHelperTest, ComputePressureRmsesSimpleTest) {
+ // Generate ground truth points with pressures {0.0, 0.0, 0.0, 0.0, 0.5, 0.5, 0.5}.
+ // (We need TEST_MAX_NUM_PREDICTIONS + 2 to test all prediction time buckets.)
+ std::vector<GroundTruthPoint> groundTruthPoints =
+ generateConstantGroundTruthPoints(GroundTruthPoint{{.position = Eigen::Vector2f(0, 0),
+ .pressure = 0.0f},
+ .timestamp = TEST_INITIAL_TIMESTAMP},
+ /*numPoints=*/TEST_MAX_NUM_PREDICTIONS + 2);
+ for (size_t i = 4; i < groundTruthPoints.size(); ++i) {
+ groundTruthPoints[i].pressure = 0.5f;
+ }
+
+ std::vector<std::vector<PredictionPoint>> predictionPoints =
+ generateAllPredictionsByLinearExtrapolation(groundTruthPoints);
+
+ std::vector<float> pressureRmses = computePressureRmses(groundTruthPoints, predictionPoints);
+
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, pressureRmses.size());
+ EXPECT_FLOAT_EQ(rmse(std::vector<float>{0.0f, 0.0f, -0.5f, 0.5f, 0.0f}), pressureRmses[0]);
+ EXPECT_FLOAT_EQ(rmse(std::vector<float>{0.0f, -0.5f, -0.5f, 1.0f}), pressureRmses[1]);
+ EXPECT_FLOAT_EQ(rmse(std::vector<float>{-0.5f, -0.5f, -0.5f}), pressureRmses[2]);
+ EXPECT_FLOAT_EQ(rmse(std::vector<float>{-0.5f, -0.5f}), pressureRmses[3]);
+ EXPECT_FLOAT_EQ(rmse(std::vector<float>{-0.5f}), pressureRmses[4]);
+}
+
+// --- MotionPredictorMetricsManager tests. ---
+
+// Helper function that instantiates a MetricsManager with the given mock logged AtomFields. Takes
+// vectors of ground truth and prediction points of the same length, and passes these points to the
+// MetricsManager. The format of these vectors is expected to be:
+// • groundTruthPoints: chronologically-ordered ground truth points, with at least 2 elements.
+// • predictionPoints: the first index points to a vector of predictions corresponding to the
+// source ground truth point with the same index.
+// - The first element should be empty, because there are not expected to be predictions until
+// we have received 2 ground truth points.
+// - The last element may be empty, because there will be no future ground truth points to
+// associate with those predictions (if not empty, it will be ignored).
+// - To test all prediction buckets, there should be at least TEST_MAX_NUM_PREDICTIONS non-empty
+// prediction sets (that is, excluding the first and last). Thus, groundTruthPoints and
+// predictionPoints should have size at least TEST_MAX_NUM_PREDICTIONS + 2.
+//
+// The passed-in outAtomFields will contain the logged AtomFields when the function returns.
+//
+// This function returns void so that it can use test assertions.
+void runMetricsManager(const std::vector<GroundTruthPoint>& groundTruthPoints,
+ const std::vector<std::vector<PredictionPoint>>& predictionPoints,
+ std::vector<AtomFields>& outAtomFields) {
+ MotionPredictorMetricsManager metricsManager(TEST_PREDICTION_INTERVAL_NANOS,
+ TEST_MAX_NUM_PREDICTIONS);
+ metricsManager.setMockLoggedAtomFields(&outAtomFields);
+
+ // Validate structure of groundTruthPoints and predictionPoints.
+ ASSERT_EQ(predictionPoints.size(), groundTruthPoints.size());
+ ASSERT_GE(groundTruthPoints.size(), 2u);
+ ASSERT_EQ(predictionPoints[0].size(), 0u);
+ for (size_t i = 1; i + 1 < predictionPoints.size(); ++i) {
+ SCOPED_TRACE(testing::Message() << "i = " << i);
+ ASSERT_EQ(predictionPoints[i].size(), TEST_MAX_NUM_PREDICTIONS);
+ }
+
+ // Pass ground truth points and predictions (for all except first and last ground truth).
+ for (size_t i = 0; i < groundTruthPoints.size(); ++i) {
+ metricsManager.onRecord(makeMotionEvent(groundTruthPoints[i]));
+ if ((i > 0) && (i + 1 < predictionPoints.size())) {
+ metricsManager.onPredict(makeMotionEvent(predictionPoints[i]));
+ }
+ }
+ // Send a stroke-end event to trigger the logging call.
+ metricsManager.onRecord(makeLiftMotionEvent());
+}
+
+// Vacuous test:
+// • Input: no prediction data.
+// • Expectation: no metrics should be logged.
+TEST(MotionPredictorMetricsManagerTest, NoPredictions) {
+ std::vector<AtomFields> mockLoggedAtomFields;
+ MotionPredictorMetricsManager metricsManager(TEST_PREDICTION_INTERVAL_NANOS,
+ TEST_MAX_NUM_PREDICTIONS);
+ metricsManager.setMockLoggedAtomFields(&mockLoggedAtomFields);
+
+ metricsManager.onRecord(makeMotionEvent(
+ GroundTruthPoint{{.position = Eigen::Vector2f(0, 0), .pressure = 0}, .timestamp = 0}));
+ metricsManager.onRecord(makeLiftMotionEvent());
+
+ // Check that mockLoggedAtomFields is still empty (as it was initialized empty), ensuring that
+ // no metrics were logged.
+ EXPECT_EQ(0u, mockLoggedAtomFields.size());
+}
+
+// Perfect predictions test:
+// • Input: constant input events, perfect predictions matching the input events.
+// • Expectation: all error metrics should be zero, or NO_DATA_SENTINEL for "unreported" metrics.
+// (For example, scale-invariant errors are only reported for the final time bucket.)
+TEST(MotionPredictorMetricsManagerTest, ConstantGroundTruthPerfectPredictions) {
+ GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10.0f, 20.0f), .pressure = 0.6f},
+ .timestamp = TEST_INITIAL_TIMESTAMP};
+
+ // Generate ground truth and prediction points as described by the runMetricsManager comment.
+ std::vector<GroundTruthPoint> groundTruthPoints;
+ std::vector<std::vector<PredictionPoint>> predictionPoints;
+ for (size_t i = 0; i < TEST_MAX_NUM_PREDICTIONS + 2; ++i) {
+ groundTruthPoints.push_back(groundTruthPoint);
+ predictionPoints.push_back(i > 0 ? generateConstantPredictions(groundTruthPoint)
+ : std::vector<PredictionPoint>{});
+ groundTruthPoint.timestamp += TEST_PREDICTION_INTERVAL_NANOS;
+ }
+
+ std::vector<AtomFields> atomFields;
+ runMetricsManager(groundTruthPoints, predictionPoints, atomFields);
+
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, atomFields.size());
+ // Check that errors are all zero, or NO_DATA_SENTINEL for unreported metrics.
+ for (size_t i = 0; i < atomFields.size(); ++i) {
+ SCOPED_TRACE(testing::Message() << "i = " << i);
+ const AtomFields& atom = atomFields[i];
+ const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
+ EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
+ // General errors: reported for every time bucket.
+ EXPECT_EQ(0, atom.alongTrajectoryErrorMeanMillipixels);
+ EXPECT_EQ(0, atom.alongTrajectoryErrorStdMillipixels);
+ EXPECT_EQ(0, atom.offTrajectoryRmseMillipixels);
+ EXPECT_EQ(0, atom.pressureRmseMilliunits);
+ // High-velocity errors: reported only for the last two time buckets.
+ // However, this data has zero velocity, so these metrics should all be NO_DATA_SENTINEL.
+ EXPECT_EQ(NO_DATA_SENTINEL, atom.highVelocityAlongTrajectoryRmse);
+ EXPECT_EQ(NO_DATA_SENTINEL, atom.highVelocityOffTrajectoryRmse);
+ // Scale-invariant errors: reported only for the last time bucket.
+ if (i + 1 == atomFields.size()) {
+ EXPECT_EQ(0, atom.scaleInvariantAlongTrajectoryRmse);
+ EXPECT_EQ(0, atom.scaleInvariantOffTrajectoryRmse);
+ } else {
+ EXPECT_EQ(NO_DATA_SENTINEL, atom.scaleInvariantAlongTrajectoryRmse);
+ EXPECT_EQ(NO_DATA_SENTINEL, atom.scaleInvariantOffTrajectoryRmse);
+ }
+ }
+}
+
+TEST(MotionPredictorMetricsManagerTest, QuadraticPressureLinearPredictions) {
+ // Generate ground truth points.
+ //
+ // Ground truth pressures are a quadratically increasing function from some initial value.
+ const float initialPressure = 0.5f;
+ const float quadraticCoefficient = 0.01f;
+ std::vector<GroundTruthPoint> groundTruthPoints;
+ nsecs_t timestamp = TEST_INITIAL_TIMESTAMP;
+ // As described in the runMetricsManager comment, we should have TEST_MAX_NUM_PREDICTIONS + 2
+ // ground truth points.
+ for (size_t i = 0; i < TEST_MAX_NUM_PREDICTIONS + 2; ++i) {
+ const float pressure = initialPressure + quadraticCoefficient * static_cast<float>(i * i);
+ groundTruthPoints.push_back(
+ GroundTruthPoint{{.position = Eigen::Vector2f(0, 0), .pressure = pressure},
+ .timestamp = timestamp});
+ timestamp += TEST_PREDICTION_INTERVAL_NANOS;
+ }
+
+ // Note: the first index is the source ground truth index, and the second is the prediction
+ // target index.
+ std::vector<std::vector<PredictionPoint>> predictionPoints =
+ generateAllPredictionsByLinearExtrapolation(groundTruthPoints);
+
+ const std::vector<float> pressureErrors =
+ computePressureRmses(groundTruthPoints, predictionPoints);
+
+ // Run test.
+ std::vector<AtomFields> atomFields;
+ runMetricsManager(groundTruthPoints, predictionPoints, atomFields);
+
+ // Check logged metrics match expectations.
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, atomFields.size());
+ for (size_t i = 0; i < atomFields.size(); ++i) {
+ SCOPED_TRACE(testing::Message() << "i = " << i);
+ const AtomFields& atom = atomFields[i];
+ // Check time bucket delta matches expectation based on index and prediction interval.
+ const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
+ EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
+ // Check pressure error matches expectation.
+ EXPECT_NEAR(static_cast<int>(1000 * pressureErrors[i]), atom.pressureRmseMilliunits, 1);
+ }
+}
+
+TEST(MotionPredictorMetricsManagerTest, QuadraticPositionLinearPredictionsGeneralErrors) {
+ // Generate ground truth points.
+ //
+ // Each component of the ground truth positions are an independent quadratically increasing
+ // function from some initial value.
+ const Eigen::Vector2f initialPosition(200, 300);
+ const Eigen::Vector2f quadraticCoefficients(-2, 3);
+ std::vector<GroundTruthPoint> groundTruthPoints;
+ nsecs_t timestamp = TEST_INITIAL_TIMESTAMP;
+ // As described in the runMetricsManager comment, we should have TEST_MAX_NUM_PREDICTIONS + 2
+ // ground truth points.
+ for (size_t i = 0; i < TEST_MAX_NUM_PREDICTIONS + 2; ++i) {
+ const Eigen::Vector2f position =
+ initialPosition + quadraticCoefficients * static_cast<float>(i * i);
+ groundTruthPoints.push_back(
+ GroundTruthPoint{{.position = position, .pressure = 0.5}, .timestamp = timestamp});
+ timestamp += TEST_PREDICTION_INTERVAL_NANOS;
+ }
+
+ // Note: the first index is the source ground truth index, and the second is the prediction
+ // target index.
+ std::vector<std::vector<PredictionPoint>> predictionPoints =
+ generateAllPredictionsByLinearExtrapolation(groundTruthPoints);
+
+ std::vector<GeneralPositionErrors> generalPositionErrors =
+ computeGeneralPositionErrors(groundTruthPoints, predictionPoints);
+
+ // Run test.
+ std::vector<AtomFields> atomFields;
+ runMetricsManager(groundTruthPoints, predictionPoints, atomFields);
+
+ // Check logged metrics match expectations.
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, atomFields.size());
+ for (size_t i = 0; i < atomFields.size(); ++i) {
+ SCOPED_TRACE(testing::Message() << "i = " << i);
+ const AtomFields& atom = atomFields[i];
+ // Check time bucket delta matches expectation based on index and prediction interval.
+ const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
+ EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
+ // Check general position errors match expectation.
+ EXPECT_NEAR(static_cast<int>(1000 * generalPositionErrors[i].alongTrajectoryErrorMean),
+ atom.alongTrajectoryErrorMeanMillipixels, 1);
+ EXPECT_NEAR(static_cast<int>(1000 * generalPositionErrors[i].alongTrajectoryErrorStd),
+ atom.alongTrajectoryErrorStdMillipixels, 1);
+ EXPECT_NEAR(static_cast<int>(1000 * generalPositionErrors[i].offTrajectoryRmse),
+ atom.offTrajectoryRmseMillipixels, 1);
+ }
+}
+
+// Counterclockwise regular octagonal section test:
+// • Input – ground truth: constantly-spaced input events starting at a trajectory pointing exactly
+// rightwards, and rotating by 45° counterclockwise after each input.
+// • Input – predictions: simple linear extrapolations of previous two ground truth points.
+//
+// The code below uses the following terminology to distinguish references to ground truth events:
+// • Source ground truth: the most recent ground truth point received at the time the prediction
+// was made.
+// • Target ground truth: the ground truth event that the prediction was attempting to match.
+TEST(MotionPredictorMetricsManagerTest, CounterclockwiseOctagonGroundTruthLinearPredictions) {
+ // Select a stroke velocity that exceeds the high-velocity threshold of 1100 px/sec.
+ // For an input rate of 240 hz, 1100 px/sec * (1/240) sec/input ≈ 4.58 pixels per input.
+ const float strokeVelocity = 10; // pixels per input
+
+ // As described in the runMetricsManager comment, we should have TEST_MAX_NUM_PREDICTIONS + 2
+ // ground truth points.
+ std::vector<GroundTruthPoint> groundTruthPoints = generateCircularArcGroundTruthPoints(
+ /*initialPosition=*/Eigen::Vector2f(100, 100),
+ /*initialAngle=*/M_PI_2,
+ /*velocity=*/strokeVelocity,
+ /*turningAngle=*/-M_PI_4,
+ /*numPoints=*/TEST_MAX_NUM_PREDICTIONS + 2);
+
+ std::vector<std::vector<PredictionPoint>> predictionPoints =
+ generateAllPredictionsByLinearExtrapolation(groundTruthPoints);
+
+ std::vector<GeneralPositionErrors> generalPositionErrors =
+ computeGeneralPositionErrors(groundTruthPoints, predictionPoints);
+
+ // Run test.
+ std::vector<AtomFields> atomFields;
+ runMetricsManager(groundTruthPoints, predictionPoints, atomFields);
+
+ // Check logged metrics match expectations.
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, atomFields.size());
+ for (size_t i = 0; i < atomFields.size(); ++i) {
+ SCOPED_TRACE(testing::Message() << "i = " << i);
+ const AtomFields& atom = atomFields[i];
+ const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
+ EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
+
+ // General errors: reported for every time bucket.
+ EXPECT_NEAR(static_cast<int>(1000 * generalPositionErrors[i].alongTrajectoryErrorMean),
+ atom.alongTrajectoryErrorMeanMillipixels, 1);
+ // We allow for some floating point error in standard deviation (0.02 pixels).
+ EXPECT_NEAR(1000 * generalPositionErrors[i].alongTrajectoryErrorStd,
+ atom.alongTrajectoryErrorStdMillipixels, 20);
+ // All position errors are equal, so the standard deviation should be approximately zero.
+ EXPECT_NEAR(0, atom.alongTrajectoryErrorStdMillipixels, 20);
+ // Absolute value for RMSE, since it must be non-negative.
+ EXPECT_NEAR(static_cast<int>(1000 * generalPositionErrors[i].offTrajectoryRmse),
+ atom.offTrajectoryRmseMillipixels, 1);
+
+ // High-velocity errors: reported only for the last two time buckets.
+ //
+ // Since our input stroke velocity is chosen to be above the high-velocity threshold, all
+ // data contributes to high-velocity errors, and thus high-velocity errors should be equal
+ // to general errors (where reported).
+ //
+ // As above, use absolute value for RMSE, since it must be non-negative.
+ if (i + 2 >= atomFields.size()) {
+ EXPECT_NEAR(static_cast<int>(
+ 1000 * std::abs(generalPositionErrors[i].alongTrajectoryErrorMean)),
+ atom.highVelocityAlongTrajectoryRmse, 1);
+ EXPECT_NEAR(static_cast<int>(1000 *
+ std::abs(generalPositionErrors[i].offTrajectoryRmse)),
+ atom.highVelocityOffTrajectoryRmse, 1);
+ } else {
+ EXPECT_EQ(NO_DATA_SENTINEL, atom.highVelocityAlongTrajectoryRmse);
+ EXPECT_EQ(NO_DATA_SENTINEL, atom.highVelocityOffTrajectoryRmse);
+ }
+
+ // Scale-invariant errors: reported only for the last time bucket, where the reported value
+ // is the aggregation across all time buckets.
+ //
+ // The MetricsManager stores mMaxNumPredictions recent ground truth segments. Our ground
+ // truth segments here all have a length of strokeVelocity, so we can convert general errors
+ // to scale-invariant errors by dividing by `strokeVelocty * TEST_MAX_NUM_PREDICTIONS`.
+ //
+ // As above, use absolute value for RMSE, since it must be non-negative.
+ if (i + 1 == atomFields.size()) {
+ const float pathLength = strokeVelocity * TEST_MAX_NUM_PREDICTIONS;
+ std::vector<float> alongTrajectoryAbsoluteErrors;
+ std::vector<float> offTrajectoryAbsoluteErrors;
+ for (size_t j = 0; j < TEST_MAX_NUM_PREDICTIONS; ++j) {
+ alongTrajectoryAbsoluteErrors.push_back(
+ std::abs(generalPositionErrors[j].alongTrajectoryErrorMean));
+ offTrajectoryAbsoluteErrors.push_back(
+ std::abs(generalPositionErrors[j].offTrajectoryRmse));
+ }
+ EXPECT_NEAR(static_cast<int>(1000 * average(alongTrajectoryAbsoluteErrors) /
+ pathLength),
+ atom.scaleInvariantAlongTrajectoryRmse, 1);
+ EXPECT_NEAR(static_cast<int>(1000 * average(offTrajectoryAbsoluteErrors) / pathLength),
+ atom.scaleInvariantOffTrajectoryRmse, 1);
+ } else {
+ EXPECT_EQ(NO_DATA_SENTINEL, atom.scaleInvariantAlongTrajectoryRmse);
+ EXPECT_EQ(NO_DATA_SENTINEL, atom.scaleInvariantOffTrajectoryRmse);
+ }
+ }
+}
+
+} // namespace
+} // namespace android
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index a8e92c6..3e1ac33 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -68,13 +68,11 @@
ftl::Future<FenceResult> RenderEngine::drawLayers(const DisplaySettings& display,
const std::vector<LayerSettings>& layers,
const std::shared_ptr<ExternalTexture>& buffer,
- const bool useFramebufferCache,
base::unique_fd&& bufferFence) {
const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
std::future<FenceResult> resultFuture = resultPromise->get_future();
updateProtectedContext(layers, buffer);
- drawLayersInternal(std::move(resultPromise), display, layers, buffer, useFramebufferCache,
- std::move(bufferFence));
+ drawLayersInternal(std::move(resultPromise), display, layers, buffer, std::move(bufferFence));
return resultFuture;
}
diff --git a/libs/renderengine/benchmark/RenderEngineBench.cpp b/libs/renderengine/benchmark/RenderEngineBench.cpp
index ce2c7d7..a7f1df9 100644
--- a/libs/renderengine/benchmark/RenderEngineBench.cpp
+++ b/libs/renderengine/benchmark/RenderEngineBench.cpp
@@ -104,10 +104,6 @@
return std::pair<uint32_t, uint32_t>(width, height);
}
-// This value doesn't matter, as it's not read. TODO(b/199918329): Once we remove
-// GLESRenderEngine we can remove this, too.
-static constexpr const bool kUseFrameBufferCache = false;
-
static std::unique_ptr<RenderEngine> createRenderEngine(RenderEngine::RenderEngineType type) {
auto args = RenderEngineCreationArgs::Builder()
.setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
@@ -168,10 +164,7 @@
};
auto layers = std::vector<LayerSettings>{layer};
- sp<Fence> waitFence =
- re.drawLayers(display, layers, texture, kUseFrameBufferCache, base::unique_fd())
- .get()
- .value();
+ sp<Fence> waitFence = re.drawLayers(display, layers, texture, base::unique_fd()).get().value();
waitFence->waitForever(LOG_TAG);
return texture;
}
@@ -200,10 +193,8 @@
// This loop starts and stops the timer.
for (auto _ : benchState) {
- sp<Fence> waitFence = re.drawLayers(display, layers, outputBuffer, kUseFrameBufferCache,
- base::unique_fd())
- .get()
- .value();
+ sp<Fence> waitFence =
+ re.drawLayers(display, layers, outputBuffer, base::unique_fd()).get().value();
waitFence->waitForever(LOG_TAG);
}
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 80aa4c6..b992d82 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -145,9 +145,6 @@
// @param layers The layers to draw onto the display, in Z-order.
// @param buffer The buffer which will be drawn to. This buffer will be
// ready once drawFence fires.
- // @param useFramebufferCache True if the framebuffer cache should be used.
- // If an implementation does not cache output framebuffers, then this
- // parameter does nothing.
// @param bufferFence Fence signalling that the buffer is ready to be drawn
// to.
// @return A future object of FenceResult indicating whether drawing was
@@ -155,7 +152,6 @@
virtual ftl::Future<FenceResult> drawLayers(const DisplaySettings& display,
const std::vector<LayerSettings>& layers,
const std::shared_ptr<ExternalTexture>& buffer,
- const bool useFramebufferCache,
base::unique_fd&& bufferFence);
// Clean-up method that should be called on the main thread after the
@@ -244,8 +240,7 @@
virtual void drawLayersInternal(
const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
const DisplaySettings& display, const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
- base::unique_fd&& bufferFence) = 0;
+ const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) = 0;
};
struct RenderEngineCreationArgs {
diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h
index 7881190..160006d 100644
--- a/libs/renderengine/include/renderengine/mock/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h
@@ -42,14 +42,14 @@
MOCK_METHOD1(useProtectedContext, void(bool));
MOCK_METHOD0(cleanupPostRender, void());
MOCK_CONST_METHOD0(canSkipPostRenderCleanup, bool());
- MOCK_METHOD5(drawLayers,
+ MOCK_METHOD4(drawLayers,
ftl::Future<FenceResult>(const DisplaySettings&, const std::vector<LayerSettings>&,
- const std::shared_ptr<ExternalTexture>&, const bool,
+ const std::shared_ptr<ExternalTexture>&,
base::unique_fd&&));
- MOCK_METHOD6(drawLayersInternal,
+ MOCK_METHOD5(drawLayersInternal,
void(const std::shared_ptr<std::promise<FenceResult>>&&, const DisplaySettings&,
const std::vector<LayerSettings>&, const std::shared_ptr<ExternalTexture>&,
- const bool, base::unique_fd&&));
+ base::unique_fd&&));
MOCK_METHOD0(getContextPriority, int());
MOCK_METHOD0(supportsBackgroundBlur, bool());
MOCK_METHOD1(onActiveDisplaySizeChanged, void(ui::Size));
diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp
index 90dcae4..02d1e1e 100644
--- a/libs/renderengine/skia/AutoBackendTexture.cpp
+++ b/libs/renderengine/skia/AutoBackendTexture.cpp
@@ -24,6 +24,7 @@
#include <include/gpu/ganesh/SkImageGanesh.h>
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
+#include <include/gpu/vk/GrVkTypes.h>
#include <android/hardware_buffer.h>
#include "ColorSpaces.h"
#include "log/log_main.h"
diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp
index f6b9183..e1887a8 100644
--- a/libs/renderengine/skia/Cache.cpp
+++ b/libs/renderengine/skia/Cache.cpp
@@ -29,8 +29,6 @@
namespace android::renderengine::skia {
namespace {
-// Warming shader cache, not framebuffer cache.
-constexpr bool kUseFrameBufferCache = false;
// clang-format off
// Any non-identity matrix will do.
@@ -116,8 +114,7 @@
caster.geometry.positionTransform = transform;
auto layers = std::vector<LayerSettings>{layer, caster};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
- base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
}
@@ -154,8 +151,7 @@
for (auto alpha : {half(.2f), half(1.0f)}) {
layer.alpha = alpha;
auto layers = std::vector<LayerSettings>{layer};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
- base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
}
}
@@ -183,8 +179,7 @@
for (float roundedCornersRadius : {0.0f, 50.f}) {
layer.geometry.roundedCornersRadius = {roundedCornersRadius, roundedCornersRadius};
auto layers = std::vector<LayerSettings>{layer};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
- base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
}
}
@@ -207,8 +202,7 @@
for (int radius : {9, 60}) {
layer.backgroundBlurRadius = radius;
auto layers = std::vector<LayerSettings>{layer};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
- base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
}
@@ -254,8 +248,7 @@
for (float alpha : {0.5f, 1.f}) {
layer.alpha = alpha;
auto layers = std::vector<LayerSettings>{layer};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
- base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
}
}
@@ -292,7 +285,7 @@
};
auto layers = std::vector<LayerSettings>{layer};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
static void drawHolePunchLayer(SkiaRenderEngine* renderengine, const DisplaySettings& display,
@@ -321,7 +314,7 @@
};
auto layers = std::vector<LayerSettings>{layer};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
//
@@ -436,9 +429,7 @@
};
auto layers = std::vector<LayerSettings>{layer};
// call get() to make it synchronous
- renderengine
- ->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd())
- .get();
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd()).get();
const nsecs_t timeAfter = systemTime();
const float compileTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 4dd5590..3d0d827 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -650,8 +650,7 @@
void SkiaRenderEngine::drawLayersInternal(
const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
const DisplaySettings& display, const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& buffer, const bool /*useFramebufferCache*/,
- base::unique_fd&& bufferFence) {
+ const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) {
ATRACE_FORMAT("%s for %s", __func__, display.namePlusId.c_str());
std::lock_guard<std::mutex> lock(mRenderingMutex);
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index d4fc959..3db0c1b 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -136,7 +136,6 @@
const DisplaySettings& display,
const std::vector<LayerSettings>& layers,
const std::shared_ptr<ExternalTexture>& buffer,
- const bool useFramebufferCache,
base::unique_fd&& bufferFence) override final;
void dump(std::string& result) override final;
diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
index 7bf2b0c..5c9820c 100644
--- a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
@@ -111,9 +111,9 @@
constexpr int kSampleCount = 1;
constexpr bool kMipmapped = false;
constexpr SkSurfaceProps* kProps = nullptr;
- sk_sp<SkSurface> surface =
- SkSurfaces::RenderTarget(context, skgpu::Budgeted::kYes, scaledInfo, kSampleCount,
- kTopLeft_GrSurfaceOrigin, kProps, kMipmapped);
+ sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(context, skgpu::Budgeted::kYes, scaledInfo,
+ kSampleCount, kTopLeft_GrSurfaceOrigin,
+ kProps, kMipmapped, input->isProtected());
LOG_ALWAYS_FATAL_IF(!surface, "%s: Failed to create surface for blurring!", __func__);
sk_sp<SkImage> tmpBlur = makeImage(surface.get(), &blurBuilder);
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 5eec305..7dde716 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -490,7 +490,7 @@
void invokeDraw(const renderengine::DisplaySettings& settings,
const std::vector<renderengine::LayerSettings>& layers) {
ftl::Future<FenceResult> future =
- mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd());
+ mRE->drawLayers(settings, layers, mBuffer, base::unique_fd());
ASSERT_TRUE(future.valid());
auto result = future.get();
@@ -1610,8 +1610,7 @@
layer.geometry.boundaries = fullscreenRect().toFloatRect();
BufferSourceVariant<ForceOpaqueBufferVariant>::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
layers.push_back(layer);
- ftl::Future<FenceResult> future =
- mRE->drawLayers(settings, layers, nullptr, true, base::unique_fd());
+ ftl::Future<FenceResult> future = mRE->drawLayers(settings, layers, nullptr, base::unique_fd());
ASSERT_TRUE(future.valid());
auto result = future.get();
@@ -2263,14 +2262,14 @@
layers.push_back(layer);
ftl::Future<FenceResult> futureOne =
- mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd());
+ mRE->drawLayers(settings, layers, mBuffer, base::unique_fd());
ASSERT_TRUE(futureOne.valid());
auto resultOne = futureOne.get();
ASSERT_TRUE(resultOne.ok());
auto fenceOne = resultOne.value();
ftl::Future<FenceResult> futureTwo =
- mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd(fenceOne->dup()));
+ mRE->drawLayers(settings, layers, mBuffer, base::unique_fd(fenceOne->dup()));
ASSERT_TRUE(futureTwo.valid());
auto resultTwo = futureTwo.get();
ASSERT_TRUE(resultTwo.ok());
diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
index 281a502..475dc15 100644
--- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp
+++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
@@ -143,11 +143,11 @@
.WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
ftl::Future<FenceResult> future =
- mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence));
+ mThreadedRE->drawLayers(settings, layers, buffer, std::move(bufferFence));
ASSERT_TRUE(future.valid());
auto result = future.get();
ASSERT_TRUE(result.ok());
@@ -176,11 +176,11 @@
.WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
ftl::Future<FenceResult> future =
- mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence));
+ mThreadedRE->drawLayers(settings, layers, buffer, std::move(bufferFence));
ASSERT_TRUE(future.valid());
auto result = future.get();
ASSERT_TRUE(result.ok());
@@ -204,11 +204,11 @@
.WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
ftl::Future<FenceResult> future =
- mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence));
+ mThreadedRE->drawLayers(settings, layers, buffer, std::move(bufferFence));
ASSERT_TRUE(future.valid());
auto result = future.get();
ASSERT_TRUE(result.ok());
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index 3bc9e6d..2cb66cb 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -243,29 +243,27 @@
void RenderEngineThreaded::drawLayersInternal(
const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
const DisplaySettings& display, const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
- base::unique_fd&& bufferFence) {
+ const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) {
resultPromise->set_value(Fence::NO_FENCE);
return;
}
ftl::Future<FenceResult> RenderEngineThreaded::drawLayers(
const DisplaySettings& display, const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
- base::unique_fd&& bufferFence) {
+ const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) {
ATRACE_CALL();
const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
std::future<FenceResult> resultFuture = resultPromise->get_future();
int fd = bufferFence.release();
{
std::lock_guard lock(mThreadMutex);
- mFunctionCalls.push([resultPromise, display, layers, buffer, useFramebufferCache,
- fd](renderengine::RenderEngine& instance) {
- ATRACE_NAME("REThreaded::drawLayers");
- instance.updateProtectedContext(layers, buffer);
- instance.drawLayersInternal(std::move(resultPromise), display, layers, buffer,
- useFramebufferCache, base::unique_fd(fd));
- });
+ mFunctionCalls.push(
+ [resultPromise, display, layers, buffer, fd](renderengine::RenderEngine& instance) {
+ ATRACE_NAME("REThreaded::drawLayers");
+ instance.updateProtectedContext(layers, buffer);
+ instance.drawLayersInternal(std::move(resultPromise), display, layers, buffer,
+ base::unique_fd(fd));
+ });
}
mCondition.notify_one();
return resultFuture;
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index a7ecab2..43ec011 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -55,7 +55,6 @@
ftl::Future<FenceResult> drawLayers(const DisplaySettings& display,
const std::vector<LayerSettings>& layers,
const std::shared_ptr<ExternalTexture>& buffer,
- const bool useFramebufferCache,
base::unique_fd&& bufferFence) override;
int getContextPriority() override;
@@ -72,7 +71,7 @@
const DisplaySettings& display,
const std::vector<LayerSettings>& layers,
const std::shared_ptr<ExternalTexture>& buffer,
- const bool useFramebufferCache, base::unique_fd&& bufferFence) override;
+ base::unique_fd&& bufferFence) override;
private:
void threadMain(CreateInstanceFactory factory);
diff --git a/libs/sensor/Sensor.cpp b/libs/sensor/Sensor.cpp
index b6ea77d..9127b37 100644
--- a/libs/sensor/Sensor.cpp
+++ b/libs/sensor/Sensor.cpp
@@ -74,7 +74,7 @@
if (hwSensor.maxDelay > INT_MAX) {
// Max delay is declared as a 64 bit integer for 64 bit architectures. But it should
// always fit in a 32 bit integer, log error and cap it to INT_MAX.
- ALOGE("Sensor maxDelay overflow error %s %" PRId64, mName.string(),
+ ALOGE("Sensor maxDelay overflow error %s %" PRId64, mName.c_str(),
static_cast<int64_t>(hwSensor.maxDelay));
mMaxDelay = INT_MAX;
} else {
@@ -335,7 +335,7 @@
if (actualReportingMode != expectedReportingMode) {
ALOGE("Reporting Mode incorrect: sensor %s handle=%#010" PRIx32 " type=%" PRId32 " "
"actual=%d expected=%d",
- mName.string(), mHandle, mType, actualReportingMode, expectedReportingMode);
+ mName.c_str(), mHandle, mType, actualReportingMode, expectedReportingMode);
}
}
@@ -613,7 +613,7 @@
const String8& string8) {
uint32_t len = static_cast<uint32_t>(string8.length());
FlattenableUtils::write(buffer, size, len);
- memcpy(static_cast<char*>(buffer), string8.string(), len);
+ memcpy(static_cast<char*>(buffer), string8.c_str(), len);
FlattenableUtils::advance(buffer, size, len);
size -= FlattenableUtils::align<4>(buffer);
}
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index c2da5ba..ee1f3cb 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -72,9 +72,6 @@
"include-filter": "android.view.cts.TouchDelegateTest"
},
{
- "include-filter": "android.view.cts.VelocityTrackerTest"
- },
- {
"include-filter": "android.view.cts.VerifyInputEventTest"
},
{
@@ -218,9 +215,6 @@
"include-filter": "android.view.cts.TouchDelegateTest"
},
{
- "include-filter": "android.view.cts.VelocityTrackerTest"
- },
- {
"include-filter": "android.view.cts.VerifyInputEventTest"
},
{
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 6dd785a..188d5f0 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -185,10 +185,7 @@
mInfo.token = mClientChannel->getConnectionToken();
mInfo.name = "FakeWindowHandle";
mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
- mInfo.frameLeft = mFrame.left;
- mInfo.frameTop = mFrame.top;
- mInfo.frameRight = mFrame.right;
- mInfo.frameBottom = mFrame.bottom;
+ mInfo.frame = mFrame;
mInfo.globalScaleFactor = 1.0;
mInfo.touchableRegion.clear();
mInfo.addTouchableRegion(mFrame);
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 2923a3c..630542f 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -3011,8 +3011,8 @@
"hasToken=%s, applicationInfo.name=%s, applicationInfo.token=%s\n",
isTouchedWindow ? "[TOUCHED] " : "", info->packageName.c_str(),
info->ownerUid.toString().c_str(), info->id,
- toString(info->touchOcclusionMode).c_str(), info->alpha, info->frameLeft,
- info->frameTop, info->frameRight, info->frameBottom,
+ toString(info->touchOcclusionMode).c_str(), info->alpha, info->frame.left,
+ info->frame.top, info->frame.right, info->frame.bottom,
dumpRegion(info->touchableRegion).c_str(), info->name.c_str(),
info->inputConfig.string().c_str(), toString(info->token != nullptr),
info->applicationInfo.name.c_str(),
@@ -5644,9 +5644,9 @@
i, windowInfo->name.c_str(), windowInfo->id,
windowInfo->displayId,
windowInfo->inputConfig.string().c_str(),
- windowInfo->alpha, windowInfo->frameLeft,
- windowInfo->frameTop, windowInfo->frameRight,
- windowInfo->frameBottom, windowInfo->globalScaleFactor,
+ windowInfo->alpha, windowInfo->frame.left,
+ windowInfo->frame.top, windowInfo->frame.right,
+ windowInfo->frame.bottom, windowInfo->globalScaleFactor,
windowInfo->applicationInfo.name.c_str(),
binderToString(windowInfo->applicationInfo.token).c_str());
dump += dumpRegion(windowInfo->touchableRegion);
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 6d9cd87..4dd40cd 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -1131,10 +1131,7 @@
mInfo.name = name;
mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
mInfo.alpha = 1.0;
- mInfo.frameLeft = 0;
- mInfo.frameTop = 0;
- mInfo.frameRight = WIDTH;
- mInfo.frameBottom = HEIGHT;
+ mInfo.frame = Rect(0, 0, WIDTH, HEIGHT);
mInfo.transform.set(0, 0);
mInfo.globalScaleFactor = 1.0;
mInfo.touchableRegion.clear();
@@ -1215,10 +1212,7 @@
void setApplicationToken(sp<IBinder> token) { mInfo.applicationInfo.token = token; }
void setFrame(const Rect& frame, const ui::Transform& displayTransform = ui::Transform()) {
- mInfo.frameLeft = frame.left;
- mInfo.frameTop = frame.top;
- mInfo.frameRight = frame.right;
- mInfo.frameBottom = frame.bottom;
+ mInfo.frame = frame;
mInfo.touchableRegion.clear();
mInfo.addTouchableRegion(frame);
diff --git a/services/sensorservice/RecentEventLogger.cpp b/services/sensorservice/RecentEventLogger.cpp
index d7ca6e1..47fa8b3 100644
--- a/services/sensorservice/RecentEventLogger.cpp
+++ b/services/sensorservice/RecentEventLogger.cpp
@@ -83,7 +83,7 @@
}
buffer.append("\n");
}
- return std::string(buffer.string());
+ return std::string(buffer.c_str());
}
/**
diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp
index 10ca990..3155b4c 100644
--- a/services/sensorservice/SensorDevice.cpp
+++ b/services/sensorservice/SensorDevice.cpp
@@ -298,7 +298,7 @@
result.appendFormat("}, selected = %.2f ms\n", info.bestBatchParams.mTBatch / 1e6f);
}
- return result.string();
+ return result.c_str();
}
/**
diff --git a/services/sensorservice/SensorDirectConnection.cpp b/services/sensorservice/SensorDirectConnection.cpp
index 4fff8bb..555b80a 100644
--- a/services/sensorservice/SensorDirectConnection.cpp
+++ b/services/sensorservice/SensorDirectConnection.cpp
@@ -63,7 +63,7 @@
void SensorService::SensorDirectConnection::dump(String8& result) const {
Mutex::Autolock _l(mConnectionLock);
result.appendFormat("\tPackage %s, HAL channel handle %d, total sensor activated %zu\n",
- String8(mOpPackageName).string(), getHalChannelHandle(), mActivated.size());
+ String8(mOpPackageName).c_str(), getHalChannelHandle(), mActivated.size());
for (auto &i : mActivated) {
result.appendFormat("\t\tSensor %#08x, rate %d\n", i.first, i.second);
}
@@ -79,7 +79,7 @@
void SensorService::SensorDirectConnection::dump(ProtoOutputStream* proto) const {
using namespace service::SensorDirectConnectionProto;
Mutex::Autolock _l(mConnectionLock);
- proto->write(PACKAGE_NAME, std::string(String8(mOpPackageName).string()));
+ proto->write(PACKAGE_NAME, std::string(String8(mOpPackageName).c_str()));
proto->write(HAL_CHANNEL_HANDLE, getHalChannelHandle());
proto->write(NUM_SENSOR_ACTIVATED, int(mActivated.size()));
for (auto &i : mActivated) {
diff --git a/services/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp
index dc5070c..d469ff4 100644
--- a/services/sensorservice/SensorEventConnection.cpp
+++ b/services/sensorservice/SensorEventConnection.cpp
@@ -90,12 +90,12 @@
result.append("NORMAL\n");
}
result.appendFormat("\t %s | WakeLockRefCount %d | uid %d | cache size %d | "
- "max cache size %d\n", mPackageName.string(), mWakeLockRefCount, mUid, mCacheSize,
+ "max cache size %d\n", mPackageName.c_str(), mWakeLockRefCount, mUid, mCacheSize,
mMaxCacheSize);
for (auto& it : mSensorInfo) {
const FlushInfo& flushInfo = it.second;
result.appendFormat("\t %s 0x%08x | status: %s | pending flush events %d \n",
- mService->getSensorName(it.first).string(),
+ mService->getSensorName(it.first).c_str(),
it.first,
flushInfo.mFirstFlushPending ? "First flush pending" :
"active",
@@ -131,7 +131,7 @@
} else {
proto->write(OPERATING_MODE, OP_MODE_NORMAL);
}
- proto->write(PACKAGE_NAME, std::string(mPackageName.string()));
+ proto->write(PACKAGE_NAME, std::string(mPackageName.c_str()));
proto->write(WAKE_LOCK_REF_COUNT, int32_t(mWakeLockRefCount));
proto->write(UID, int32_t(mUid));
proto->write(CACHE_SIZE, int32_t(mCacheSize));
@@ -848,7 +848,7 @@
if (numBytesRead == sizeof(sensors_event_t)) {
if (!mDataInjectionMode) {
ALOGE("Data injected in normal mode, dropping event"
- "package=%s uid=%d", mPackageName.string(), mUid);
+ "package=%s uid=%d", mPackageName.c_str(), mUid);
// Unregister call backs.
return 0;
}
diff --git a/services/sensorservice/SensorList.cpp b/services/sensorservice/SensorList.cpp
index daff4d0..7e30206 100644
--- a/services/sensorservice/SensorList.cpp
+++ b/services/sensorservice/SensorList.cpp
@@ -150,12 +150,12 @@
"%#010x) %-25s | %-15s | ver: %" PRId32 " | type: %20s(%" PRId32
") | perm: %s | flags: 0x%08x\n",
s.getHandle(),
- s.getName().string(),
- s.getVendor().string(),
+ s.getName().c_str(),
+ s.getVendor().c_str(),
s.getVersion(),
- s.getStringType().string(),
+ s.getStringType().c_str(),
s.getType(),
- s.getRequiredPermission().size() ? s.getRequiredPermission().string() : "n/a",
+ s.getRequiredPermission().size() ? s.getRequiredPermission().c_str() : "n/a",
static_cast<int>(s.getFlags()));
result.append("\t");
@@ -224,7 +224,7 @@
}
return true;
});
- return std::string(result.string());
+ return std::string(result.c_str());
}
/**
@@ -241,13 +241,13 @@
forEachSensor([&proto] (const Sensor& s) -> bool {
const uint64_t token = proto->start(SENSORS);
proto->write(HANDLE, s.getHandle());
- proto->write(NAME, std::string(s.getName().string()));
- proto->write(VENDOR, std::string(s.getVendor().string()));
+ proto->write(NAME, std::string(s.getName().c_str()));
+ proto->write(VENDOR, std::string(s.getVendor().c_str()));
proto->write(VERSION, s.getVersion());
- proto->write(STRING_TYPE, std::string(s.getStringType().string()));
+ proto->write(STRING_TYPE, std::string(s.getStringType().c_str()));
proto->write(TYPE, s.getType());
proto->write(REQUIRED_PERMISSION, std::string(s.getRequiredPermission().size() ?
- s.getRequiredPermission().string() : ""));
+ s.getRequiredPermission().c_str() : ""));
proto->write(FLAGS, int(s.getFlags()));
switch (s.getReportingMode()) {
case AREPORTING_MODE_CONTINUOUS:
diff --git a/services/sensorservice/SensorRegistrationInfo.h b/services/sensorservice/SensorRegistrationInfo.h
index a34a65b..dc9e821 100644
--- a/services/sensorservice/SensorRegistrationInfo.h
+++ b/services/sensorservice/SensorRegistrationInfo.h
@@ -93,7 +93,7 @@
using namespace service::SensorRegistrationInfoProto;
proto->write(TIMESTAMP_SEC, int64_t(mRealtimeSec));
proto->write(SENSOR_HANDLE, mSensorHandle);
- proto->write(PACKAGE_NAME, std::string(mPackageName.string()));
+ proto->write(PACKAGE_NAME, std::string(mPackageName.c_str()));
proto->write(PID, int32_t(mPid));
proto->write(UID, int32_t(mUid));
proto->write(SAMPLING_RATE_US, mSamplingRateUs);
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index cfafc69..9e6f563 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -629,7 +629,7 @@
i.second->setFormat("mask_data");
}
// if there is events and sensor does not need special permission.
- result.appendFormat("%s: ", s->getSensor().getName().string());
+ result.appendFormat("%s: ", s->getSensor().getName().c_str());
result.append(i.second->dump().c_str());
}
}
@@ -640,7 +640,7 @@
int handle = mActiveSensors.keyAt(i);
if (dev.isSensorActive(handle)) {
result.appendFormat("%s (handle=0x%08x, connections=%zu)\n",
- getSensorName(handle).string(),
+ getSensorName(handle).c_str(),
handle,
mActiveSensors.valueAt(i)->getNumConnections());
}
@@ -656,14 +656,14 @@
result.appendFormat(" NORMAL\n");
break;
case RESTRICTED:
- result.appendFormat(" RESTRICTED : %s\n", mAllowListedPackage.string());
+ result.appendFormat(" RESTRICTED : %s\n", mAllowListedPackage.c_str());
break;
case DATA_INJECTION:
- result.appendFormat(" DATA_INJECTION : %s\n", mAllowListedPackage.string());
+ result.appendFormat(" DATA_INJECTION : %s\n", mAllowListedPackage.c_str());
break;
case REPLAY_DATA_INJECTION:
result.appendFormat(" REPLAY_DATA_INJECTION : %s\n",
- mAllowListedPackage.string());
+ mAllowListedPackage.c_str());
break;
default:
result.appendFormat(" UNKNOWN\n");
@@ -705,7 +705,7 @@
} while(startIndex != currentIndex);
}
}
- write(fd, result.string(), result.size());
+ write(fd, result.c_str(), result.size());
return NO_ERROR;
}
@@ -753,7 +753,7 @@
"normal" : "mask_data");
const uint64_t mToken = proto.start(service::SensorEventsProto::RECENT_EVENTS_LOGS);
proto.write(service::SensorEventsProto::RecentEventsLog::NAME,
- std::string(s->getSensor().getName().string()));
+ std::string(s->getSensor().getName().c_str()));
i.second->dump(&proto);
proto.end(mToken);
}
@@ -767,7 +767,7 @@
if (dev.isSensorActive(handle)) {
token = proto.start(ACTIVE_SENSORS);
proto.write(service::ActiveSensorProto::NAME,
- std::string(getSensorName(handle).string()));
+ std::string(getSensorName(handle).c_str()));
proto.write(service::ActiveSensorProto::HANDLE, handle);
proto.write(service::ActiveSensorProto::NUM_CONNECTIONS,
int(mActiveSensors.valueAt(i)->getNumConnections()));
@@ -785,11 +785,11 @@
break;
case RESTRICTED:
proto.write(OPERATING_MODE, OP_MODE_RESTRICTED);
- proto.write(WHITELISTED_PACKAGE, std::string(mAllowListedPackage.string()));
+ proto.write(WHITELISTED_PACKAGE, std::string(mAllowListedPackage.c_str()));
break;
case DATA_INJECTION:
proto.write(OPERATING_MODE, OP_MODE_DATA_INJECTION);
- proto.write(WHITELISTED_PACKAGE, std::string(mAllowListedPackage.string()));
+ proto.write(WHITELISTED_PACKAGE, std::string(mAllowListedPackage.c_str()));
break;
default:
proto.write(OPERATING_MODE, OP_MODE_UNKNOWN);
@@ -932,8 +932,8 @@
PermissionController pc;
uid = pc.getPackageUid(packageName, 0);
if (uid <= 0) {
- ALOGE("Unknown package: '%s'", String8(packageName).string());
- dprintf(err, "Unknown package: '%s'\n", String8(packageName).string());
+ ALOGE("Unknown package: '%s'", String8(packageName).c_str());
+ dprintf(err, "Unknown package: '%s'\n", String8(packageName).c_str());
return BAD_VALUE;
}
@@ -958,7 +958,7 @@
if (args[2] == String16("active")) {
active = true;
} else if ((args[2] != String16("idle"))) {
- ALOGE("Expected active or idle but got: '%s'", String8(args[2]).string());
+ ALOGE("Expected active or idle but got: '%s'", String8(args[2]).c_str());
return BAD_VALUE;
}
@@ -2217,10 +2217,10 @@
!isAudioServerOrSystemServerUid(IPCThreadState::self()->getCallingUid())) {
if (!mHtRestricted) {
ALOGI("Permitting access to HT sensor type outside system (%s)",
- String8(opPackageName).string());
+ String8(opPackageName).c_str());
} else {
- ALOGW("%s %s a sensor (%s) as a non-system client", String8(opPackageName).string(),
- operation, sensor.getName().string());
+ ALOGW("%s %s a sensor (%s) as a non-system client", String8(opPackageName).c_str(),
+ operation, sensor.getName().c_str());
return false;
}
}
@@ -2253,8 +2253,8 @@
}
if (!canAccess) {
- ALOGE("%s %s a sensor (%s) without holding %s", String8(opPackageName).string(),
- operation, sensor.getName().string(), sensor.getRequiredPermission().string());
+ ALOGE("%s %s a sensor (%s) without holding %s", String8(opPackageName).c_str(),
+ operation, sensor.getName().c_str(), sensor.getRequiredPermission().c_str());
}
return canAccess;
@@ -2434,7 +2434,7 @@
}
bool SensorService::isAllowListedPackage(const String8& packageName) {
- return (packageName.contains(mAllowListedPackage.string()));
+ return (packageName.contains(mAllowListedPackage.c_str()));
}
bool SensorService::isOperationRestrictedLocked(const String16& opPackageName) {
diff --git a/services/sensorservice/hidl/utils.cpp b/services/sensorservice/hidl/utils.cpp
index 5fa594d..d338d02 100644
--- a/services/sensorservice/hidl/utils.cpp
+++ b/services/sensorservice/hidl/utils.cpp
@@ -32,8 +32,8 @@
SensorInfo dst;
const String8& name = src.getName();
const String8& vendor = src.getVendor();
- dst.name = hidl_string{name.string(), name.size()};
- dst.vendor = hidl_string{vendor.string(), vendor.size()};
+ dst.name = hidl_string{name.c_str(), name.size()};
+ dst.vendor = hidl_string{vendor.c_str(), vendor.size()};
dst.version = src.getVersion();
dst.sensorHandle = src.getHandle();
dst.type = static_cast<::android::hardware::sensors::V1_0::SensorType>(
diff --git a/services/sensorservice/tests/sensorservicetest.cpp b/services/sensorservice/tests/sensorservicetest.cpp
index 1baf397..92956d6 100644
--- a/services/sensorservice/tests/sensorservicetest.cpp
+++ b/services/sensorservice/tests/sensorservicetest.cpp
@@ -116,7 +116,7 @@
Sensor const* accelerometer = mgr.getDefaultSensor(Sensor::TYPE_ACCELEROMETER);
printf("accelerometer=%p (%s)\n",
- accelerometer, accelerometer->getName().string());
+ accelerometer, accelerometer->getName().c_str());
sStartTime = systemTime();
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 0b11e74..78c23da 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -1315,17 +1315,9 @@
});
const nsecs_t renderEngineStart = systemTime();
- // Only use the framebuffer cache when rendering to an internal display
- // TODO(b/173560331): This is only to help mitigate memory leaks from virtual displays because
- // right now we don't have a concrete eviction policy for output buffers: GLESRenderEngine
- // bounds its framebuffer cache but Skia RenderEngine has no current policy. The best fix is
- // probably to encapsulate the output buffer into a structure that dispatches resource cleanup
- // over to RenderEngine, in which case this flag can be removed from the drawLayers interface.
- const bool useFramebufferCache = outputState.layerFilter.toInternalDisplay;
-
auto fenceResult = renderEngine
.drawLayers(clientCompositionDisplay, clientRenderEngineLayers, tex,
- useFramebufferCache, std::move(fd))
+ std::move(fd))
.get();
if (mClientCompositionRequestCache && fenceStatus(fenceResult) != NO_ERROR) {
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 4fe6927..22db247 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -327,10 +327,6 @@
? outputState.dataspace
: layerFEState->dataspace;
- // re-get HdrRenderType after the dataspace gets changed.
- hdrRenderType =
- getHdrRenderType(state.dataspace, pixelFormat, layerFEState->desiredHdrSdrRatio);
-
// Override the dataspace transfer from 170M to sRGB if the device configuration requests this.
// We do this here instead of in buffer info so that dumpsys can still report layers that are
// using the 170M transfer. Also we only do this if the colorspace is not agnostic for the
@@ -342,6 +338,10 @@
(state.dataspace & HAL_DATASPACE_RANGE_MASK) | HAL_DATASPACE_TRANSFER_SRGB);
}
+ // re-get HdrRenderType after the dataspace gets changed.
+ hdrRenderType =
+ getHdrRenderType(state.dataspace, pixelFormat, layerFEState->desiredHdrSdrRatio);
+
// For hdr content, treat the white point as the display brightness - HDR content should not be
// boosted or dimmed.
// If the layer explicitly requests to disable dimming, then don't dim either.
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index 7547be9..579c6ba 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -275,11 +275,9 @@
bufferFence.reset(texture->getReadyFence()->dup());
}
- constexpr bool kUseFramebufferCache = false;
-
auto fenceResult = renderEngine
.drawLayers(displaySettings, layerSettings, texture->get(),
- kUseFramebufferCache, std::move(bufferFence))
+ std::move(bufferFence))
.get();
if (fenceStatus(fenceResult) == NO_ERROR) {
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index ebf9a2b..ee6998a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -3421,10 +3421,10 @@
.WillRepeatedly(Return());
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
- EXPECT_CALL(mRenderEngine, drawLayers(_, IsEmpty(), _, false, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, IsEmpty(), _, _))
.WillRepeatedly([&](const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) -> ftl::Future<FenceResult> {
return ftl::yield<FenceResult>(Fence::NO_FENCE);
});
@@ -3452,10 +3452,10 @@
}));
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
- EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
.WillRepeatedly([&](const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) -> ftl::Future<FenceResult> {
return ftl::yield<FenceResult>(Fence::NO_FENCE);
});
@@ -3486,10 +3486,10 @@
}));
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
- EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, true, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
.WillRepeatedly([&](const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) -> ftl::Future<FenceResult> {
return ftl::yield<FenceResult>(Fence::NO_FENCE);
});
@@ -3515,7 +3515,7 @@
.WillRepeatedly(Return());
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
- EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
.Times(2)
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
@@ -3545,7 +3545,7 @@
.WillRepeatedly(Return());
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
- EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
EXPECT_CALL(mOutput, setExpensiveRenderingExpected(false));
@@ -3581,10 +3581,10 @@
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_))
.WillOnce(Return(mOutputBuffer))
.WillOnce(Return(otherOutputBuffer));
- EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
.WillRepeatedly([&](const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) -> ftl::Future<FenceResult> {
return ftl::yield<FenceResult>(Fence::NO_FENCE);
});
@@ -3616,9 +3616,9 @@
.WillRepeatedly(Return());
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
- EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
- EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r3), _, false, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r3), _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
verify().execute().expectAFenceWasReturned();
@@ -3695,7 +3695,7 @@
struct ExpectDisplaySettingsState
: public CallOrderStateMachineHelper<TestType, ExpectDisplaySettingsState> {
auto thenExpectDisplaySettingsUsed(renderengine::DisplaySettings settings) {
- EXPECT_CALL(getInstance()->mRenderEngine, drawLayers(settings, _, _, false, _))
+ EXPECT_CALL(getInstance()->mRenderEngine, drawLayers(settings, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
return nextState<ExecuteState>();
}
@@ -3948,11 +3948,11 @@
EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
.WillRepeatedly(Return());
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillRepeatedly([&](const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>&,
const std::shared_ptr<renderengine::ExternalTexture>&,
- const bool, base::unique_fd&&) -> ftl::Future<FenceResult> {
+ base::unique_fd&&) -> ftl::Future<FenceResult> {
return ftl::yield<FenceResult>(Fence::NO_FENCE);
});
}
@@ -3987,7 +3987,7 @@
EXPECT_CALL(*mRenderSurface, setProtected(true));
// Must happen after setting the protected content state.
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
base::unique_fd fd;
@@ -4046,7 +4046,7 @@
InSequence seq;
EXPECT_CALL(mOutput, setExpensiveRenderingExpected(true));
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
base::unique_fd fd;
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
index bd030d0..d61d7ba 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
@@ -353,7 +353,7 @@
const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
const std::vector<renderengine::LayerSettings>& layers,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) -> ftl::Future<FenceResult> {
EXPECT_EQ(mOutputState.framebufferSpace.getContent(), displaySettings.physicalDisplay);
EXPECT_EQ(mOutputState.layerStackSpace.getContent(), displaySettings.clip);
@@ -369,7 +369,7 @@
.WillOnce(Return(clientComp1));
EXPECT_CALL(*layerFE2, prepareClientComposition(ClientCompositionTargetSettingsSecureEq(false)))
.WillOnce(Return(clientComp2));
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
mOutputState.isSecure = false;
cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
expectReadyBuffer(cachedSet);
@@ -402,7 +402,7 @@
const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
const std::vector<renderengine::LayerSettings>& layers,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) -> ftl::Future<FenceResult> {
EXPECT_EQ(mOutputState.framebufferSpace.getContent(), displaySettings.physicalDisplay);
EXPECT_EQ(mOutputState.layerStackSpace.getContent(), displaySettings.clip);
@@ -419,7 +419,7 @@
.WillOnce(Return(clientComp1));
EXPECT_CALL(*layerFE2, prepareClientComposition(ClientCompositionTargetSettingsSecureEq(true)))
.WillOnce(Return(clientComp2));
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
mOutputState.isSecure = true;
cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
expectReadyBuffer(cachedSet);
@@ -452,7 +452,7 @@
const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) -> ftl::Future<FenceResult> {
EXPECT_EQ(mOutputState.displayBrightnessNits, displaySettings.targetLuminanceNits);
return ftl::yield<FenceResult>(Fence::NO_FENCE);
@@ -466,7 +466,7 @@
prepareClientComposition(ClientCompositionTargetSettingsWhitePointEq(
mOutputState.displayBrightnessNits)))
.WillOnce(Return(clientComp2));
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
mOutputState.isSecure = true;
cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
expectReadyBuffer(cachedSet);
@@ -502,7 +502,7 @@
const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) -> ftl::Future<FenceResult> {
EXPECT_EQ(mOutputState.displayBrightnessNits, displaySettings.targetLuminanceNits);
return ftl::yield<FenceResult>(Fence::NO_FENCE);
@@ -516,7 +516,7 @@
prepareClientComposition(ClientCompositionTargetSettingsWhitePointEq(
mOutputState.displayBrightnessNits)))
.WillOnce(Return(clientComp2));
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
mOutputState.isSecure = true;
cachedSet.render(mRenderEngine, mTexturePool, mOutputState, false);
expectReadyBuffer(cachedSet);
@@ -551,7 +551,7 @@
const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
const std::vector<renderengine::LayerSettings>& layers,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) -> ftl::Future<FenceResult> {
EXPECT_EQ(mOutputState.framebufferSpace.getContent(), displaySettings.physicalDisplay);
EXPECT_EQ(mOutputState.layerStackSpace.getContent(), displaySettings.clip);
@@ -566,7 +566,7 @@
EXPECT_CALL(*layerFE1, prepareClientComposition(_)).WillOnce(Return(clientComp1));
EXPECT_CALL(*layerFE2, prepareClientComposition(_)).WillOnce(Return(clientComp2));
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
expectReadyBuffer(cachedSet);
@@ -815,7 +815,7 @@
const auto drawLayers = [&](const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>& layers,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) -> ftl::Future<FenceResult> {
// If the highlight layer is enabled, it will increase the size by 1.
// We're interested in the third layer either way.
@@ -839,7 +839,7 @@
return ftl::yield<FenceResult>(Fence::NO_FENCE);
};
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
}
@@ -875,7 +875,7 @@
const auto drawLayers = [&](const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>& layers,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) -> ftl::Future<FenceResult> {
// If the highlight layer is enabled, it will increase the size by 1.
// We're interested in the third layer either way.
@@ -900,7 +900,7 @@
return ftl::yield<FenceResult>(Fence::NO_FENCE);
};
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
}
@@ -1026,7 +1026,7 @@
const auto drawLayers = [&](const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>& layers,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) -> ftl::Future<FenceResult> {
// If the highlight layer is enabled, it will increase the size by 1.
// We're interested in the third layer either way.
@@ -1039,7 +1039,7 @@
return ftl::yield<FenceResult>(Fence::NO_FENCE);
};
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
}
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index 778a0a8..00590e6 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -168,7 +168,7 @@
void FlattenerTest::expectAllLayersFlattened(const std::vector<const LayerState*>& layers) {
// layers would be flattened but the buffer would not be overridden
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
@@ -419,7 +419,7 @@
// caleed for Layer2 and Layer3
layerState1->resetFramesSinceBufferUpdate();
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
@@ -442,7 +442,7 @@
layerState1->incrementFramesSinceBufferUpdate();
mTime += 200ms;
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
@@ -494,7 +494,7 @@
// called for Layer1 and Layer2
layerState3->resetFramesSinceBufferUpdate();
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
@@ -508,7 +508,7 @@
EXPECT_EQ(nullptr, overrideBuffer5);
// Layers 1 and 2 will be flattened a new drawFrame would be called for Layer4 and Layer5
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
@@ -537,7 +537,7 @@
layerState3->incrementFramesSinceBufferUpdate();
mTime += 200ms;
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
@@ -592,7 +592,7 @@
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
// This will render a CachedSet.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
@@ -603,7 +603,7 @@
// This time we merge the CachedSet in, so we have a new hash, and we should
// only have two sets.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -656,7 +656,7 @@
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
// This will render a CachedSet.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
@@ -667,7 +667,7 @@
// This time we merge the CachedSet in, so we have a new hash, and we should
// only have two sets.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -728,7 +728,7 @@
// This will render a CachedSet of layer 0. Though it is just one layer, it satisfies the
// exception that there would be a hole punch above it.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
@@ -736,7 +736,7 @@
EXPECT_EQ(nullptr, overrideBuffer0);
// This time we merge the CachedSet in and we should still have only two sets.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -798,7 +798,7 @@
// This will render a CachedSet of layer 0. Though it is just one layer, it satisfies the
// exception that there would be a hole punch above it.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
@@ -806,7 +806,7 @@
EXPECT_EQ(nullptr, overrideBuffer0);
// This time we merge the CachedSet in and we should still have only two sets.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -849,7 +849,7 @@
layerState3->resetFramesSinceBufferUpdate();
// layers would be flattened but the buffer would not be overridden
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
@@ -894,7 +894,7 @@
layerState1->resetFramesSinceBufferUpdate();
// layers would be flattened but the buffer would not be overridden
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillRepeatedly(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
@@ -947,7 +947,7 @@
layerState1->resetFramesSinceBufferUpdate();
// layers would be flattened but the buffer would not be overridden
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
@@ -995,7 +995,7 @@
layerStateWithBlurBehind->resetFramesSinceBufferUpdate();
// layers would be flattened but the buffer would not be overridden
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
@@ -1037,7 +1037,7 @@
// Mark the layers inactive
mTime += 200ms;
// layers would be flattened but the buffer would not be overridden
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
@@ -1050,7 +1050,7 @@
// Simulate attempting to render prior to merging the new cached set with the layer stack.
// Here we should not try to re-render.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
// We provide the override buffer now that it's rendered
@@ -1097,14 +1097,14 @@
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
for (size_t i = 0; i < kMaxDeferRenderAttempts; i++) {
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
mFlattener->renderCachedSets(mOutputState,
std::chrono::steady_clock::now() -
(kCachedSetRenderDuration + 10ms),
true);
}
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState,
std::chrono::steady_clock::now() -
@@ -1139,7 +1139,7 @@
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
// This will render a CachedSet.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
@@ -1150,7 +1150,7 @@
// This time we merge the CachedSet in, so we have a new hash, and we should
// only have two sets.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -1189,7 +1189,7 @@
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
// This will render a CachedSet.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
@@ -1200,7 +1200,7 @@
// This time we merge the CachedSet in, so we have a new hash, and we should
// only have two sets.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -1239,7 +1239,7 @@
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
// This will render a CachedSet.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
@@ -1250,7 +1250,7 @@
// This time we merge the CachedSet in, so we have a new hash, and we should
// only have two sets.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -1289,7 +1289,7 @@
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
// This will render a CachedSet.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
@@ -1300,7 +1300,7 @@
// This time we merge the CachedSet in, so we have a new hash, and we should
// only have two sets.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -1342,7 +1342,7 @@
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
// This will render a CachedSet.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
@@ -1354,7 +1354,7 @@
// This time we merge the CachedSet in, so we have a new hash, and we should
// only have two sets.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -1394,7 +1394,7 @@
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
// This will render a CachedSet.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
.WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
@@ -1405,7 +1405,7 @@
// This time we merge the CachedSet in, so we have a new hash, and we should
// only have two sets.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
index ab4c15d..962dc09 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -60,9 +60,9 @@
return;
}
}
- if (traversalPath.hasRelZLoop()) {
- LOG_ALWAYS_FATAL("Found relative z loop layerId:%d", traversalPath.invalidRelativeRootId);
- }
+
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(traversalPath.hasRelZLoop(), "Found relative z loop layerId:%d",
+ traversalPath.invalidRelativeRootId);
for (auto& [child, childVariant] : mChildren) {
ScopedAddToTraversalPath addChildToTraversalPath(traversalPath, child->mLayer->id,
childVariant);
@@ -104,9 +104,7 @@
[child](const std::pair<LayerHierarchy*, Variant>& x) {
return x.first == child;
});
- if (it == mChildren.end()) {
- LOG_ALWAYS_FATAL("Could not find child!");
- }
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mChildren.end(), "Could not find child!");
mChildren.erase(it);
}
@@ -119,11 +117,8 @@
[hierarchy](std::pair<LayerHierarchy*, Variant>& child) {
return child.first == hierarchy;
});
- if (it == mChildren.end()) {
- LOG_ALWAYS_FATAL("Could not find child!");
- } else {
- it->second = variant;
- }
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mChildren.end(), "Could not find child!");
+ it->second = variant;
}
const RequestedLayerState* LayerHierarchy::getLayer() const {
@@ -422,9 +417,8 @@
LayerHierarchy* LayerHierarchyBuilder::getHierarchyFromId(uint32_t layerId, bool crashOnFailure) {
auto it = mLayerIdToHierarchy.find(layerId);
if (it == mLayerIdToHierarchy.end()) {
- if (crashOnFailure) {
- LOG_ALWAYS_FATAL("Could not find hierarchy for layer id %d", layerId);
- }
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(crashOnFailure, "Could not find hierarchy for layer id %d",
+ layerId);
return nullptr;
};
@@ -460,7 +454,7 @@
}
LayerHierarchy::TraversalPath LayerHierarchy::TraversalPath::getMirrorRoot() const {
- LOG_ALWAYS_FATAL_IF(!isClone(), "Cannot get mirror root of a non cloned node");
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(!isClone(), "Cannot get mirror root of a non cloned node");
TraversalPath mirrorRootPath = *this;
mirrorRootPath.id = mirrorRootId;
return mirrorRootPath;
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
index 1712137..a826ec1 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -45,11 +45,11 @@
for (auto& newLayer : newLayers) {
RequestedLayerState& layer = *newLayer.get();
auto [it, inserted] = mIdToLayer.try_emplace(layer.id, References{.owner = layer});
- if (!inserted) {
- LOG_ALWAYS_FATAL("Duplicate layer id found. New layer: %s Existing layer: %s",
- layer.getDebugString().c_str(),
- it->second.owner.getDebugString().c_str());
- }
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(!inserted,
+ "Duplicate layer id found. New layer: %s Existing layer: "
+ "%s",
+ layer.getDebugString().c_str(),
+ it->second.owner.getDebugString().c_str());
mAddedLayers.push_back(newLayer.get());
mChangedLayers.push_back(newLayer.get());
layer.parentId = linkLayer(layer.parentId, layer.id);
@@ -85,14 +85,15 @@
}
}
-void LayerLifecycleManager::onHandlesDestroyed(const std::vector<uint32_t>& destroyedHandles,
- bool ignoreUnknownHandles) {
+void LayerLifecycleManager::onHandlesDestroyed(
+ const std::vector<std::pair<uint32_t, std::string /* debugName */>>& destroyedHandles,
+ bool ignoreUnknownHandles) {
std::vector<uint32_t> layersToBeDestroyed;
- for (const auto& layerId : destroyedHandles) {
+ for (const auto& [layerId, name] : destroyedHandles) {
auto it = mIdToLayer.find(layerId);
if (it == mIdToLayer.end()) {
- LOG_ALWAYS_FATAL_IF(!ignoreUnknownHandles, "%s Layerid not found %d", __func__,
- layerId);
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(!ignoreUnknownHandles, "%s Layerid not found %s[%d]",
+ __func__, name.c_str(), layerId);
continue;
}
RequestedLayerState& layer = it->second.owner;
@@ -113,10 +114,8 @@
for (size_t i = 0; i < layersToBeDestroyed.size(); i++) {
uint32_t layerId = layersToBeDestroyed[i];
auto it = mIdToLayer.find(layerId);
- if (it == mIdToLayer.end()) {
- LOG_ALWAYS_FATAL("%s Layer with id %d not found", __func__, layerId);
- continue;
- }
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mIdToLayer.end(), "%s Layer with id %d not found",
+ __func__, layerId);
RequestedLayerState& layer = it->second.owner;
@@ -135,11 +134,9 @@
auto& references = it->second.references;
for (uint32_t linkedLayerId : references) {
RequestedLayerState* linkedLayer = getLayerFromId(linkedLayerId);
- if (!linkedLayer) {
- LOG_ALWAYS_FATAL("%s Layerid reference %d not found for %d", __func__,
- linkedLayerId, layer.id);
- continue;
- };
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(!linkedLayer,
+ "%s Layerid reference %d not found for %d", __func__,
+ linkedLayerId, layer.id);
if (linkedLayer->parentId == layer.id) {
linkedLayer->parentId = UNASSIGNED_LAYER_ID;
if (linkedLayer->canBeDestroyed()) {
@@ -191,17 +188,17 @@
RequestedLayerState* layer = getLayerFromId(layerId);
if (layer == nullptr) {
- LOG_ALWAYS_FATAL_IF(!ignoreUnknownLayers, "%s Layer with layerid=%d not found",
- __func__, layerId);
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(!ignoreUnknownLayers,
+ "%s Layer with layerid=%d not found", __func__,
+ layerId);
continue;
}
- if (!layer->handleAlive) {
- LOG_ALWAYS_FATAL("%s Layer's with layerid=%d) is not alive. Possible out of "
- "order LayerLifecycleManager updates",
- __func__, layerId);
- continue;
- }
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(!layer->handleAlive,
+ "%s Layer's with layerid=%d) is not alive. Possible "
+ "out of "
+ "order LayerLifecycleManager updates",
+ __func__, layerId);
if (layer->changes.get() == 0) {
mChangedLayers.push_back(layer);
@@ -241,7 +238,7 @@
RequestedLayerState* bgColorLayer = getLayerFromId(layer->bgColorLayerId);
layer->bgColorLayerId = UNASSIGNED_LAYER_ID;
bgColorLayer->parentId = unlinkLayer(bgColorLayer->parentId, bgColorLayer->id);
- onHandlesDestroyed({bgColorLayer->id});
+ onHandlesDestroyed({{bgColorLayer->id, bgColorLayer->debugName}});
} else if (layer->bgColorLayerId != UNASSIGNED_LAYER_ID) {
RequestedLayerState* bgColorLayer = getLayerFromId(layer->bgColorLayerId);
bgColorLayer->color = layer->bgColor;
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
index 48571bf..9aff78e 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
@@ -47,7 +47,8 @@
// Ignore unknown handles when iteroping with legacy front end. In the old world, we
// would create child layers which are not necessary with the new front end. This means
// we will get notified for handle changes that don't exist in the new front end.
- void onHandlesDestroyed(const std::vector<uint32_t>&, bool ignoreUnknownHandles = false);
+ void onHandlesDestroyed(const std::vector<std::pair<uint32_t, std::string /* debugName */>>&,
+ bool ignoreUnknownHandles = false);
// Detaches the layer from its relative parent to prevent a loop in the
// layer hierarchy. This overrides the RequestedLayerState and leaves
diff --git a/services/surfaceflinger/FrontEnd/LayerLog.h b/services/surfaceflinger/FrontEnd/LayerLog.h
index 4943483..3845dfe 100644
--- a/services/surfaceflinger/FrontEnd/LayerLog.h
+++ b/services/surfaceflinger/FrontEnd/LayerLog.h
@@ -16,6 +16,8 @@
#pragma once
+#include "Tracing/TransactionTracing.h"
+
// Uncomment to trace layer updates for a single layer
// #define LOG_LAYER 1
@@ -27,3 +29,17 @@
#endif
#define LLOGD(LAYER_ID, x, ...) ALOGD("[%d] %s " x, (LAYER_ID), __func__, ##__VA_ARGS__);
+
+#define LLOG_ALWAYS_FATAL_WITH_TRACE(...) \
+ do { \
+ TransactionTraceWriter::getInstance().invoke(__func__, /* overwrite= */ false); \
+ LOG_ALWAYS_FATAL(##__VA_ARGS__); \
+ } while (false)
+
+#define LLOG_ALWAYS_FATAL_WITH_TRACE_IF(cond, ...) \
+ do { \
+ if (__predict_false(cond)) { \
+ TransactionTraceWriter::getInstance().invoke(__func__, /* overwrite= */ false); \
+ } \
+ LOG_ALWAYS_FATAL_IF(cond, ##__VA_ARGS__); \
+ } while (false)
\ No newline at end of file
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 159d0f0..341defd 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -178,12 +178,7 @@
info.touchableRegion.clear();
}
- const Rect roundedFrameInDisplay =
- getInputBoundsInDisplaySpace(snapshot, inputBounds, screenToDisplay);
- info.frameLeft = roundedFrameInDisplay.left;
- info.frameTop = roundedFrameInDisplay.top;
- info.frameRight = roundedFrameInDisplay.right;
- info.frameBottom = roundedFrameInDisplay.bottom;
+ info.frame = getInputBoundsInDisplaySpace(snapshot, inputBounds, screenToDisplay);
ui::Transform inputToLayer;
inputToLayer.set(inputBounds.left, inputBounds.top);
@@ -523,12 +518,9 @@
const Args& args, const LayerHierarchy& hierarchy,
LayerHierarchy::TraversalPath& traversalPath, const LayerSnapshot& parentSnapshot,
int depth) {
- if (depth > 50) {
- TransactionTraceWriter::getInstance().invoke("layer_builder_stack_overflow_",
- /*overwrite=*/false);
- LOG_ALWAYS_FATAL("Cycle detected in LayerSnapshotBuilder. See "
- "builder_stack_overflow_transactions.winscope");
- }
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(depth > 50,
+ "Cycle detected in LayerSnapshotBuilder. See "
+ "builder_stack_overflow_transactions.winscope");
const RequestedLayerState* layer = hierarchy.getLayer();
LayerSnapshot* snapshot = getSnapshot(traversalPath);
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index a5d5563..8a39cd6 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -150,7 +150,7 @@
const bool hadSideStream = sidebandStream != nullptr;
const layer_state_t& clientState = resolvedComposerState.state;
- const bool hadBlur = hasBlur();
+ const bool hadSomethingToDraw = hasSomethingToDraw();
uint64_t clientChanges = what | layer_state_t::diff(clientState);
layer_state_t::merge(clientState);
what = clientChanges;
@@ -228,11 +228,10 @@
RequestedLayerState::Changes::VisibleRegion;
}
}
- if (what & (layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBlurRegionsChanged)) {
- if (hadBlur != hasBlur()) {
- changes |= RequestedLayerState::Changes::Visibility |
- RequestedLayerState::Changes::VisibleRegion;
- }
+
+ if (hadSomethingToDraw != hasSomethingToDraw()) {
+ changes |= RequestedLayerState::Changes::Visibility |
+ RequestedLayerState::Changes::VisibleRegion;
}
if (clientChanges & layer_state_t::HIERARCHY_CHANGES)
changes |= RequestedLayerState::Changes::Hierarchy;
@@ -579,6 +578,12 @@
return externalTexture && externalTexture->getUsage() & GRALLOC_USAGE_PROTECTED;
}
+bool RequestedLayerState::hasSomethingToDraw() const {
+ return externalTexture != nullptr || sidebandStream != nullptr || shadowRadius > 0.f ||
+ backgroundBlurRadius > 0 || blurRegions.size() > 0 ||
+ (color.r >= 0.0_hf && color.g >= 0.0_hf && color.b >= 0.0_hf);
+}
+
void RequestedLayerState::clearChanges() {
what = 0;
changes.clear();
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
index 8eff22b..09f33de 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -87,6 +87,7 @@
bool backpressureEnabled() const;
bool isSimpleBufferUpdate(const layer_state_t&) const;
bool isProtected() const;
+ bool hasSomethingToDraw() const;
// Layer serial number. This gives layers an explicit ordering, so we
// have a stable sort order when their layer stack and Z-order are
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
index ca7c3c2..d3d9509 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
@@ -22,6 +22,7 @@
#include <cutils/trace.h>
#include <utils/Log.h>
#include <utils/Trace.h>
+#include "FrontEnd/LayerLog.h"
#include "TransactionHandler.h"
@@ -87,8 +88,8 @@
}
auto it = mPendingTransactionQueues.find(flushState.queueWithUnsignaledBuffer);
- LOG_ALWAYS_FATAL_IF(it == mPendingTransactionQueues.end(),
- "Could not find queue with unsignaled buffer!");
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mPendingTransactionQueues.end(),
+ "Could not find queue with unsignaled buffer!");
auto& queue = it->second;
popTransactionFromPending(transactions, flushState, queue);
diff --git a/services/surfaceflinger/FrontEnd/Update.h b/services/surfaceflinger/FrontEnd/Update.h
index e1449b6..e5cca8f 100644
--- a/services/surfaceflinger/FrontEnd/Update.h
+++ b/services/surfaceflinger/FrontEnd/Update.h
@@ -46,7 +46,7 @@
std::vector<LayerCreatedState> layerCreatedStates;
std::vector<std::unique_ptr<frontend::RequestedLayerState>> newLayers;
std::vector<LayerCreationArgs> layerCreationArgs;
- std::vector<uint32_t> destroyedHandles;
+ std::vector<std::pair<uint32_t, std::string /* debugName */>> destroyedHandles;
};
} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 30dce11..057fa70 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -2389,11 +2389,7 @@
info.touchableRegion.clear();
}
- const Rect roundedFrameInDisplay = getInputBoundsInDisplaySpace(inputBounds, screenToDisplay);
- info.frameLeft = roundedFrameInDisplay.left;
- info.frameTop = roundedFrameInDisplay.top;
- info.frameRight = roundedFrameInDisplay.right;
- info.frameBottom = roundedFrameInDisplay.bottom;
+ info.frame = getInputBoundsInDisplaySpace(inputBounds, screenToDisplay);
ui::Transform inputToLayer;
inputToLayer.set(inputBounds.left, inputBounds.top);
diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp
index 1c7581b..341f041 100644
--- a/services/surfaceflinger/LayerProtoHelper.cpp
+++ b/services/surfaceflinger/LayerProtoHelper.cpp
@@ -185,8 +185,8 @@
static_assert(std::is_same_v<U, int32_t>);
proto->set_layout_params_type(static_cast<U>(inputInfo.layoutParamsType));
- LayerProtoHelper::writeToProto({inputInfo.frameLeft, inputInfo.frameTop, inputInfo.frameRight,
- inputInfo.frameBottom},
+ LayerProtoHelper::writeToProto({inputInfo.frame.left, inputInfo.frame.top,
+ inputInfo.frame.right, inputInfo.frame.bottom},
[&]() { return proto->mutable_frame(); });
LayerProtoHelper::writeToProto(inputInfo.touchableRegion,
[&]() { return proto->mutable_touchable_region(); });
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 1ef381c..d978cf6 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -134,6 +134,7 @@
#include "FrontEnd/LayerCreationArgs.h"
#include "FrontEnd/LayerHandle.h"
#include "FrontEnd/LayerLifecycleManager.h"
+#include "FrontEnd/LayerLog.h"
#include "FrontEnd/LayerSnapshot.h"
#include "HdrLayerInfoReporter.h"
#include "Layer.h"
@@ -487,7 +488,7 @@
mIgnoreHdrCameraLayers = ignore_hdr_camera_layers(false);
mLayerLifecycleManagerEnabled =
- base::GetBoolProperty("persist.debug.sf.enable_layer_lifecycle_manager"s, false);
+ base::GetBoolProperty("persist.debug.sf.enable_layer_lifecycle_manager"s, true);
mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled ||
base::GetBoolProperty("persist.debug.sf.enable_legacy_frontend"s, false);
}
@@ -863,30 +864,32 @@
ALOGE("Run StartPropertySetThread failed!");
}
- if (mTransactionTracing) {
- TransactionTraceWriter::getInstance().setWriterFunction([&](const std::string& prefix,
- bool overwrite) {
- auto writeFn = [&]() {
- const std::string filename =
- TransactionTracing::DIR_NAME + prefix + TransactionTracing::FILE_NAME;
- if (!overwrite && access(filename.c_str(), F_OK) == 0) {
- ALOGD("TransactionTraceWriter: file=%s already exists", filename.c_str());
- return;
- }
- mTransactionTracing->flush();
- mTransactionTracing->writeToFile(filename);
- };
- if (std::this_thread::get_id() == mMainThreadId) {
- writeFn();
- } else {
- mScheduler->schedule(writeFn).get();
- }
- });
- }
-
+ initTransactionTraceWriter();
ALOGV("Done initializing");
}
+void SurfaceFlinger::initTransactionTraceWriter() {
+ if (!mTransactionTracing) {
+ return;
+ }
+ TransactionTraceWriter::getInstance().setWriterFunction(
+ [&](const std::string& filename, bool overwrite) {
+ auto writeFn = [&]() {
+ if (!overwrite && access(filename.c_str(), F_OK) == 0) {
+ ALOGD("TransactionTraceWriter: file=%s already exists", filename.c_str());
+ return;
+ }
+ mTransactionTracing->flush();
+ mTransactionTracing->writeToFile(filename);
+ };
+ if (std::this_thread::get_id() == mMainThreadId) {
+ writeFn();
+ } else {
+ mScheduler->schedule(writeFn).get();
+ }
+ });
+}
+
void SurfaceFlinger::readPersistentProperties() {
Mutex::Autolock _l(mStateLock);
@@ -2618,6 +2621,23 @@
constexpr bool kCursorOnly = false;
const auto layers = moveSnapshotsToCompositionArgs(refreshArgs, kCursorOnly);
+ if (mLayerLifecycleManagerEnabled && !refreshArgs.updatingGeometryThisFrame) {
+ for (const auto& [token, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
+ auto compositionDisplay = display->getCompositionDisplay();
+ if (!compositionDisplay->getState().isEnabled) continue;
+ for (auto outputLayer : compositionDisplay->getOutputLayersOrderedByZ()) {
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(outputLayer->getLayerFE().getCompositionState() ==
+ nullptr,
+ "Output layer %s for display %s %" PRIu64
+ " has a null "
+ "snapshot.",
+ outputLayer->getLayerFE().getDebugName(),
+ compositionDisplay->getName().c_str(),
+ compositionDisplay->getId().value);
+ }
+ }
+ }
+
mCompositionEngine->present(refreshArgs);
moveSnapshotsFromCompositionArgs(refreshArgs, layers);
@@ -5525,7 +5545,7 @@
void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t layerId) {
{
std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
- mDestroyedHandles.emplace_back(layerId);
+ mDestroyedHandles.emplace_back(layerId, layer->getDebugName());
}
mTransactionHandler.onLayerDestroyed(layerId);
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 4d17fa7..7b64489 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -1134,7 +1134,7 @@
ui::Rotation getPhysicalDisplayOrientation(DisplayId, bool isPrimary) const
REQUIRES(mStateLock);
void traverseLegacyLayers(const LayerVector::Visitor& visitor) const;
-
+ void initTransactionTraceWriter();
sp<StartPropertySetThread> mStartPropertySetThread;
surfaceflinger::Factory& mFactory;
pid_t mPid;
@@ -1425,7 +1425,7 @@
frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}};
frontend::LayerSnapshotBuilder mLayerSnapshotBuilder;
- std::vector<uint32_t> mDestroyedHandles;
+ std::vector<std::pair<uint32_t, std::string>> mDestroyedHandles;
std::vector<std::unique_ptr<frontend::RequestedLayerState>> mNewLayers;
std::vector<LayerCreationArgs> mNewLayerArgs;
// These classes do not store any client state but help with managing transaction callbacks
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp
index 7e330b9..bc69191 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.cpp
+++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp
@@ -111,7 +111,7 @@
update.createdLayers = std::move(newUpdate.layerCreationArgs);
newUpdate.layerCreationArgs.clear();
update.destroyedLayerHandles.reserve(newUpdate.destroyedHandles.size());
- for (uint32_t handle : newUpdate.destroyedHandles) {
+ for (auto& [handle, _] : newUpdate.destroyedHandles) {
update.destroyedLayerHandles.push_back(handle);
}
mPendingUpdates.emplace_back(update);
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.h b/services/surfaceflinger/Tracing/TransactionTracing.h
index 31bca5f..422b5f3 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.h
+++ b/services/surfaceflinger/Tracing/TransactionTracing.h
@@ -73,12 +73,16 @@
static constexpr auto TRACING_VERSION = 1;
private:
+ friend class TransactionTraceWriter;
friend class TransactionTracingTest;
friend class SurfaceFlinger;
static constexpr auto DIR_NAME = "/data/misc/wmtrace/";
static constexpr auto FILE_NAME = "transactions_trace.winscope";
static constexpr auto FILE_PATH = "/data/misc/wmtrace/transactions_trace.winscope";
+ static std::string getFilePath(const std::string& prefix) {
+ return DIR_NAME + prefix + FILE_NAME;
+ }
mutable std::mutex mTraceLock;
TransactionRingBuffer<proto::TransactionTraceFile, proto::TransactionTraceEntry> mBuffer
@@ -138,10 +142,16 @@
public:
void setWriterFunction(
- std::function<void(const std::string& prefix, bool overwrite)> function) {
+ std::function<void(const std::string& filename, bool overwrite)> function) {
mWriterFunction = std::move(function);
}
- void invoke(const std::string& prefix, bool overwrite) { mWriterFunction(prefix, overwrite); }
+ void invoke(const std::string& prefix, bool overwrite) {
+ mWriterFunction(TransactionTracing::getFilePath(prefix), overwrite);
+ }
+ /* pass in a complete file path for testing */
+ void invokeForTest(const std::string& filename, bool overwrite) {
+ mWriterFunction(filename, overwrite);
+ }
};
} // namespace android
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
index 519ef44..321b8ba 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
@@ -109,11 +109,11 @@
ALOGV(" destroyedHandles=%d", entry.destroyed_layers(j));
}
- std::vector<uint32_t> destroyedHandles;
+ std::vector<std::pair<uint32_t, std::string>> destroyedHandles;
destroyedHandles.reserve((size_t)entry.destroyed_layer_handles_size());
for (int j = 0; j < entry.destroyed_layer_handles_size(); j++) {
ALOGV(" destroyedHandles=%d", entry.destroyed_layer_handles(j));
- destroyedHandles.push_back(entry.destroyed_layer_handles(j));
+ destroyedHandles.push_back({entry.destroyed_layer_handles(j), ""});
}
bool displayChanged = entry.displays_changed();
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 3dd33b9..7d8796f 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -66,6 +66,7 @@
// option to false temporarily.
address: true,
},
+ static_libs: ["libc++fs"],
srcs: [
":libsurfaceflinger_mock_sources",
":libsurfaceflinger_sources",
@@ -128,6 +129,7 @@
"TransactionFrameTracerTest.cpp",
"TransactionProtoParserTest.cpp",
"TransactionSurfaceFrameTest.cpp",
+ "TransactionTraceWriterTest.cpp",
"TransactionTracingTest.cpp",
"TunnelModeEnabledReporterTest.cpp",
"StrongTypingTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index bdbf243..8e13c0d 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -328,7 +328,7 @@
.WillRepeatedly([&](const renderengine::DisplaySettings& displaySettings,
const std::vector<renderengine::LayerSettings>&,
const std::shared_ptr<renderengine::ExternalTexture>&,
- const bool, base::unique_fd&&) -> std::future<FenceResult> {
+ base::unique_fd&&) -> std::future<FenceResult> {
EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
displaySettings.physicalDisplay);
@@ -379,7 +379,7 @@
.WillRepeatedly([&](const renderengine::DisplaySettings& displaySettings,
const std::vector<renderengine::LayerSettings>&,
const std::shared_ptr<renderengine::ExternalTexture>&,
- const bool, base::unique_fd&&) -> std::future<FenceResult> {
+ base::unique_fd&&) -> std::future<FenceResult> {
EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
displaySettings.physicalDisplay);
@@ -578,7 +578,7 @@
EXPECT_CALL(*test->mRenderEngine, drawLayers)
.WillOnce([&](const renderengine::DisplaySettings& displaySettings,
const std::vector<renderengine::LayerSettings>& layerSettings,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) -> std::future<FenceResult> {
EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
@@ -627,7 +627,7 @@
EXPECT_CALL(*test->mRenderEngine, drawLayers)
.WillOnce([&](const renderengine::DisplaySettings& displaySettings,
const std::vector<renderengine::LayerSettings>& layerSettings,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) -> std::future<FenceResult> {
EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
@@ -709,7 +709,7 @@
EXPECT_CALL(*test->mRenderEngine, drawLayers)
.WillOnce([&](const renderengine::DisplaySettings& displaySettings,
const std::vector<renderengine::LayerSettings>& layerSettings,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) -> std::future<FenceResult> {
EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index f64ba2a..902f2b9 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -170,7 +170,7 @@
mLifecycleManager.applyTransactions(transactions);
}
- void destroyLayerHandle(uint32_t id) { mLifecycleManager.onHandlesDestroyed({id}); }
+ void destroyLayerHandle(uint32_t id) { mLifecycleManager.onHandlesDestroyed({{id, "test"}}); }
void updateAndVerify(LayerHierarchyBuilder& hierarchyBuilder) {
if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
index 97ef5a2..d65277a 100644
--- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
@@ -84,7 +84,7 @@
layers.emplace_back(rootLayer(2));
layers.emplace_back(rootLayer(3));
lifecycleManager.addLayers(std::move(layers));
- lifecycleManager.onHandlesDestroyed({1, 2, 3});
+ lifecycleManager.onHandlesDestroyed({{1, "1"}, {2, "2"}, {3, "3"}});
EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
lifecycleManager.commitChanges();
EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
@@ -133,7 +133,7 @@
layers.emplace_back(rootLayer(1));
layers.emplace_back(rootLayer(2));
lifecycleManager.addLayers(std::move(layers));
- lifecycleManager.onHandlesDestroyed({1});
+ lifecycleManager.onHandlesDestroyed({{1, "1"}});
lifecycleManager.commitChanges();
SCOPED_TRACE("layerWithoutHandleIsDestroyed");
@@ -149,7 +149,7 @@
layers.emplace_back(rootLayer(1));
layers.emplace_back(rootLayer(2));
lifecycleManager.addLayers(std::move(layers));
- lifecycleManager.onHandlesDestroyed({1});
+ lifecycleManager.onHandlesDestroyed({{1, "1"}});
lifecycleManager.commitChanges();
listener->expectLayersAdded({1, 2});
listener->expectLayersDestroyed({1});
@@ -173,7 +173,7 @@
listener->expectLayersAdded({});
listener->expectLayersDestroyed({});
- lifecycleManager.onHandlesDestroyed({3});
+ lifecycleManager.onHandlesDestroyed({{3, "3"}});
lifecycleManager.commitChanges();
listener->expectLayersAdded({});
listener->expectLayersDestroyed({3});
@@ -194,7 +194,7 @@
listener->expectLayersDestroyed({});
lifecycleManager.applyTransactions(reparentLayerTransaction(3, UNASSIGNED_LAYER_ID));
- lifecycleManager.onHandlesDestroyed({3});
+ lifecycleManager.onHandlesDestroyed({{3, "3"}});
lifecycleManager.commitChanges();
listener->expectLayersAdded({});
listener->expectLayersDestroyed({3});
@@ -215,7 +215,7 @@
listener->expectLayersDestroyed({});
lifecycleManager.applyTransactions(reparentLayerTransaction(3, UNASSIGNED_LAYER_ID));
- lifecycleManager.onHandlesDestroyed({3, 4});
+ lifecycleManager.onHandlesDestroyed({{3, "3"}, {4, "4"}});
lifecycleManager.commitChanges();
listener->expectLayersAdded({});
listener->expectLayersDestroyed({3, 4});
@@ -376,7 +376,7 @@
transactions.back().states.front().layerId = 1;
transactions.emplace_back();
lifecycleManager.applyTransactions(transactions);
- lifecycleManager.onHandlesDestroyed({1});
+ lifecycleManager.onHandlesDestroyed({{1, "1"}});
ASSERT_EQ(lifecycleManager.getLayers().size(), 0u);
ASSERT_EQ(lifecycleManager.getDestroyedLayers().size(), 2u);
@@ -389,4 +389,52 @@
listener->expectLayersDestroyed({1, bgLayerId});
}
+TEST_F(LayerLifecycleManagerTest, blurSetsVisibilityChangeFlag) {
+ // clear default color on layer so we start with a layer that does not draw anything.
+ setColor(1, {-1.f, -1.f, -1.f});
+ mLifecycleManager.commitChanges();
+
+ // layer has something to draw
+ setBackgroundBlurRadius(1, 2);
+ EXPECT_TRUE(
+ mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+ mLifecycleManager.commitChanges();
+
+ // layer still has something to draw, so visibility shouldn't change
+ setBackgroundBlurRadius(1, 3);
+ EXPECT_FALSE(
+ mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+ mLifecycleManager.commitChanges();
+
+ // layer has nothing to draw
+ setBackgroundBlurRadius(1, 0);
+ EXPECT_TRUE(
+ mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+ mLifecycleManager.commitChanges();
+}
+
+TEST_F(LayerLifecycleManagerTest, colorSetsVisibilityChangeFlag) {
+ // clear default color on layer so we start with a layer that does not draw anything.
+ setColor(1, {-1.f, -1.f, -1.f});
+ mLifecycleManager.commitChanges();
+
+ // layer has something to draw
+ setColor(1, {2.f, 3.f, 4.f});
+ EXPECT_TRUE(
+ mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+ mLifecycleManager.commitChanges();
+
+ // layer still has something to draw, so visibility shouldn't change
+ setColor(1, {0.f, 0.f, 0.f});
+ EXPECT_FALSE(
+ mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+ mLifecycleManager.commitChanges();
+
+ // layer has nothing to draw
+ setColor(1, {-1.f, -1.f, -1.f});
+ EXPECT_TRUE(
+ mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+ mLifecycleManager.commitChanges();
+}
+
} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 84c3775..c8eda12 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -349,16 +349,22 @@
}
TEST_F(LayerSnapshotTest, blurUpdatesWhenAlphaChanges) {
- static constexpr int blurRadius = 42;
- setBackgroundBlurRadius(1221, blurRadius);
+ int blurRadius = 42;
+ setBackgroundBlurRadius(1221, static_cast<uint32_t>(blurRadius));
UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
EXPECT_EQ(getSnapshot({.id = 1221})->backgroundBlurRadius, blurRadius);
+ blurRadius = 21;
+ setBackgroundBlurRadius(1221, static_cast<uint32_t>(blurRadius));
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot({.id = 1221})->backgroundBlurRadius, blurRadius);
+
static constexpr float alpha = 0.5;
setAlpha(12, alpha);
UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
- EXPECT_EQ(getSnapshot({.id = 1221})->backgroundBlurRadius, blurRadius * alpha);
+ EXPECT_EQ(getSnapshot({.id = 1221})->backgroundBlurRadius,
+ static_cast<int>(static_cast<float>(blurRadius) * alpha));
}
// Display Mirroring Tests
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 02fa415..8458aa3 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -651,6 +651,11 @@
auto fromHandle(const sp<IBinder>& handle) { return LayerHandle::getLayer(handle); }
+ auto initTransactionTraceWriter() {
+ mFlinger->mTransactionTracing.emplace();
+ return mFlinger->initTransactionTraceWriter();
+ }
+
~TestableSurfaceFlinger() {
// All these pointer and container clears help ensure that GMock does
// not report a leaked object, since the SurfaceFlinger instance may
@@ -664,6 +669,7 @@
mFlinger->mCompositionEngine->setHwComposer(std::unique_ptr<HWComposer>());
mFlinger->mRenderEngine = std::unique_ptr<renderengine::RenderEngine>();
mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get());
+ mFlinger->mTransactionTracing.reset();
}
/* ------------------------------------------------------------------------
diff --git a/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp
new file mode 100644
index 0000000..379135e
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "TransactionTraceWriterTest"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+#include <filesystem>
+
+#include "TestableSurfaceFlinger.h"
+
+namespace android {
+
+class TransactionTraceWriterTest : public testing::Test {
+protected:
+ std::string mFilename = "/data/local/tmp/testfile_transaction_trace.winscope";
+
+ void SetUp() { mFlinger.initTransactionTraceWriter(); }
+ void TearDown() { std::filesystem::remove(mFilename); }
+
+ void verifyTraceFile() {
+ std::fstream file(mFilename, std::ios::in);
+ ASSERT_TRUE(file.is_open());
+ std::string line;
+ char magicNumber[8];
+ file.read(magicNumber, 8);
+ EXPECT_EQ("\tTNXTRAC", std::string(magicNumber, magicNumber + 8));
+ }
+
+ TestableSurfaceFlinger mFlinger;
+};
+
+TEST_F(TransactionTraceWriterTest, canWriteToFile) {
+ TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ true);
+ EXPECT_EQ(access(mFilename.c_str(), F_OK), 0);
+ verifyTraceFile();
+}
+
+TEST_F(TransactionTraceWriterTest, canOverwriteFile) {
+ std::string testLine = "test";
+ {
+ std::ofstream file(mFilename, std::ios::out);
+ file << testLine;
+ }
+ TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ true);
+ verifyTraceFile();
+}
+
+TEST_F(TransactionTraceWriterTest, doNotOverwriteFile) {
+ std::string testLine = "test";
+ {
+ std::ofstream file(mFilename, std::ios::out);
+ file << testLine;
+ }
+ TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ false);
+ {
+ std::fstream file(mFilename, std::ios::in);
+ ASSERT_TRUE(file.is_open());
+ std::string line;
+ std::getline(file, line);
+ EXPECT_EQ(line, testLine);
+ }
+}
+} // namespace android
\ No newline at end of file