Merge changes from topic "revert-21018993-VQPVABITMR"
* changes:
Revert "Disable new touchpad stack for Sony gamepads"
Revert "Enable new touchpad stack by default"
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index fdee3e5..d77b458 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -2761,6 +2761,11 @@
}
}
+static bool IsConsentlessBugreportAllowed(const Dumpstate::DumpOptions& options) {
+ // only BUGREPORT_TELEPHONY does not allow using consentless bugreport
+ return !options.telephony_only;
+}
+
static void SetOptionsFromMode(Dumpstate::BugreportMode mode, Dumpstate::DumpOptions* options,
bool is_screenshot_requested) {
// Modify com.android.shell.BugreportProgressService#isDefaultScreenshotRequired as well for
@@ -3332,9 +3337,12 @@
android::String16 package(calling_package.c_str());
if (ics != nullptr) {
MYLOGD("Checking user consent via incidentcompanion service\n");
+ int flags = 0x1; // IncidentManager.FLAG_CONFIRMATION_DIALOG
+ if (IsConsentlessBugreportAllowed(*options_)) {
+ flags |= 0x2; // IncidentManager.FLAG_ALLOW_CONSENTLESS_BUGREPORT
+ }
android::interface_cast<android::os::IIncidentCompanion>(ics)->authorizeReport(
- calling_uid, package, String16(), String16(),
- 0x1 /* FLAG_CONFIRMATION_DIALOG */, consent_callback_.get());
+ calling_uid, package, String16(), String16(), flags, consent_callback_.get());
} else {
MYLOGD("Unable to check user consent; incidentcompanion service unavailable\n");
}
diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp
index ff73c94..e54f9d3 100644
--- a/cmds/lshal/ListCommand.cpp
+++ b/cmds/lshal/ListCommand.cpp
@@ -353,8 +353,16 @@
return false;
}
+ auto vintfFqInstance = vintf::FqInstance::from(fqInstance.string());
+ if (!vintfFqInstance.has_value()) {
+ err() << "Unable to convert " << fqInstance.string() << " to vintf::FqInstance"
+ << std::endl;
+ return false;
+ }
+
std::string e;
- if (!manifest->insertInstance(fqInstance, entry.transport, arch, vintf::HalFormat::HIDL, &e)) {
+ if (!manifest->insertInstance(*vintfFqInstance, entry.transport, arch, vintf::HalFormat::HIDL,
+ &e)) {
err() << "Warning: Cannot insert '" << fqInstance.string() << ": " << e << std::endl;
return false;
}
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index bdd5172..a737bd3 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -167,6 +167,12 @@
}
prebuilt_etc {
+ name: "android.hardware.telephony.satellite.prebuilt.xml",
+ src: "android.hardware.telephony.satellite.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
name: "android.hardware.usb.accessory.prebuilt.xml",
src: "android.hardware.usb.accessory.xml",
defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/data/etc/android.hardware.telephony.satellite.xml b/data/etc/android.hardware.telephony.satellite.xml
new file mode 100644
index 0000000..5966cba
--- /dev/null
+++ b/data/etc/android.hardware.telephony.satellite.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<!-- Feature for devices that support Satellite communication via Satellite HAL APIs. -->
+<permissions>
+ <feature name="android.hardware.telephony.satellite" />
+</permissions>
diff --git a/include/android/configuration.h b/include/android/configuration.h
index 88019ae..46c7dfe 100644
--- a/include/android/configuration.h
+++ b/include/android/configuration.h
@@ -471,10 +471,36 @@
*/
ACONFIGURATION_COLOR_MODE = 0x10000,
/**
+ * Bit mask for
+ * <a href="/guide/topics/resources/providing-resources.html#GrammaticalInflectionQualifier">grammatical gender</a>
+ * configuration.
+ */
+ ACONFIGURATION_GRAMMATICAL_GENDER = 0x20000,
+ /**
* Constant used to to represent MNC (Mobile Network Code) zero.
* 0 cannot be used, since it is used to represent an undefined MNC.
*/
ACONFIGURATION_MNC_ZERO = 0xffff,
+
+ /**
+ * <a href="/guide/topics/resources/providing-resources.html#GrammaticalInflectionQualifier">Grammatical gender</a>: not specified.
+ */
+ ACONFIGURATION_GRAMMATICAL_GENDER_ANY = 0,
+
+ /**
+ * <a href="/guide/topics/resources/providing-resources.html#GrammaticalInflectionQualifier">Grammatical gender</a>: neuter.
+ */
+ ACONFIGURATION_GRAMMATICAL_GENDER_NEUTER = 1,
+
+ /**
+ * <a href="/guide/topics/resources/providing-resources.html#GrammaticalInflectionQualifier">Grammatical gender</a>: feminine.
+ */
+ ACONFIGURATION_GRAMMATICAL_GENDER_FEMININE = 2,
+
+ /**
+ * <a href="/guide/topics/resources/providing-resources.html#GrammaticalInflectionQualifier">Grammatical gender</a>: masculine.
+ */
+ ACONFIGURATION_GRAMMATICAL_GENDER_MASCULINE = 3,
};
/**
@@ -726,6 +752,24 @@
void AConfiguration_setLayoutDirection(AConfiguration* config, int32_t value) __INTRODUCED_IN(17);
/**
+ * Return the configuration's grammatical gender, or ACONFIGURATION_GRAMMATICAL_GENDER_ANY if
+ * not set.
+ *
+ * Available since API level 34.
+ */
+int32_t AConfiguration_getGrammaticalGender(AConfiguration* config)
+ __INTRODUCED_IN(__ANDROID_API_U__);
+
+/**
+ * Set the configuration's grammatical gender to one of the
+ * ACONFIGURATION_GRAMMATICAL_GENDER_* constants.
+ *
+ * Available since API level 34.
+ */
+void AConfiguration_setGrammaticalGender(AConfiguration* config, int32_t value)
+ __INTRODUCED_IN(__ANDROID_API_U__);
+
+/**
* Perform a diff between two configurations. Returns a bit mask of
* ACONFIGURATION_* constants, each bit set meaning that configuration element
* is different between them.
diff --git a/include/input/Input.h b/include/input/Input.h
index 62d84e1..e281675 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -1132,6 +1132,7 @@
TYPE_ZOOM_OUT = 1019,
TYPE_GRAB = 1020,
TYPE_GRABBING = 1021,
+ TYPE_HANDWRITING = 1022,
TYPE_SPOT_HOVER = 2000,
TYPE_SPOT_TOUCH = 2001,
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 09933d3..66d3435 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -214,6 +214,12 @@
std::string layoutType;
};
+// The version of the Universal Stylus Initiative (USI) protocol supported by the input device.
+struct InputDeviceUsiVersion {
+ int32_t majorVersion = -1;
+ int32_t minorVersion = -1;
+};
+
/*
* Describes the characteristics and capabilities of an input device.
*/
@@ -235,7 +241,7 @@
void initialize(int32_t id, int32_t generation, int32_t controllerNumber,
const InputDeviceIdentifier& identifier, const std::string& alias,
- bool isExternal, bool hasMic);
+ bool isExternal, bool hasMic, int32_t associatedDisplayId);
inline int32_t getId() const { return mId; }
inline int32_t getControllerNumber() const { return mControllerNumber; }
@@ -295,8 +301,12 @@
std::vector<InputDeviceLightInfo> getLights();
- inline void setSupportsUsi(bool supportsUsi) { mSupportsUsi = supportsUsi; }
- inline bool supportsUsi() const { return mSupportsUsi; }
+ inline void setUsiVersion(std::optional<InputDeviceUsiVersion> usiVersion) {
+ mUsiVersion = std::move(usiVersion);
+ }
+ inline std::optional<InputDeviceUsiVersion> getUsiVersion() const { return mUsiVersion; }
+
+ inline int32_t getAssociatedDisplayId() const { return mAssociatedDisplayId; }
private:
int32_t mId;
@@ -310,8 +320,8 @@
uint32_t mSources;
int32_t mKeyboardType;
std::shared_ptr<KeyCharacterMap> mKeyCharacterMap;
- // Whether this device supports the Universal Stylus Initiative (USI) protocol for styluses.
- bool mSupportsUsi;
+ std::optional<InputDeviceUsiVersion> mUsiVersion;
+ int32_t mAssociatedDisplayId;
bool mHasVibrator;
bool mHasBattery;
diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h
index e24344b..02bc201 100644
--- a/include/input/PrintTools.h
+++ b/include/input/PrintTools.h
@@ -16,18 +16,35 @@
#pragma once
+#include <bitset>
#include <map>
#include <optional>
#include <set>
#include <string>
+#include <vector>
namespace android {
+template <size_t N>
+std::string bitsetToString(const std::bitset<N>& bitset) {
+ return bitset.to_string();
+}
+
template <typename T>
inline std::string constToString(const T& v) {
return std::to_string(v);
}
+template <>
+inline std::string constToString(const bool& value) {
+ return value ? "true" : "false";
+}
+
+template <>
+inline std::string constToString(const std::vector<bool>::reference& value) {
+ return value ? "true" : "false";
+}
+
inline std::string constToString(const std::string& s) {
return s;
}
@@ -70,6 +87,19 @@
return out;
}
+/**
+ * Convert a vector to a string. The values of the vector should be of a type supported by
+ * constToString.
+ */
+template <typename T>
+std::string dumpVector(std::vector<T> values) {
+ std::string dump = constToString(values[0]);
+ for (size_t i = 1; i < values.size(); i++) {
+ dump += ", " + constToString(values[i]);
+ }
+ return dump;
+}
+
const char* toString(bool value);
/**
@@ -81,4 +111,4 @@
*/
std::string addLinePrefix(std::string str, const std::string& prefix);
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h
index 62c3ae1..da97c3e 100644
--- a/include/input/VelocityTracker.h
+++ b/include/input/VelocityTracker.h
@@ -51,33 +51,24 @@
static const size_t MAX_DEGREE = 4;
// Estimator time base.
- nsecs_t time;
+ nsecs_t time = 0;
// Polynomial coefficients describing motion.
- float coeff[MAX_DEGREE + 1];
+ std::array<float, MAX_DEGREE + 1> coeff{};
// Polynomial degree (number of coefficients), or zero if no information is
// available.
- uint32_t degree;
+ uint32_t degree = 0;
// Confidence (coefficient of determination), between 0 (no fit) and 1 (perfect fit).
- float confidence;
-
- inline void clear() {
- time = 0;
- degree = 0;
- confidence = 0;
- for (size_t i = 0; i <= MAX_DEGREE; i++) {
- coeff[i] = 0;
- }
- }
+ float confidence = 0;
};
/*
* Contains all available velocity data from a VelocityTracker.
*/
struct ComputedVelocity {
- inline std::optional<float> getVelocity(int32_t axis, uint32_t id) const {
+ inline std::optional<float> getVelocity(int32_t axis, int32_t id) const {
const auto& axisVelocities = mVelocities.find(axis);
if (axisVelocities == mVelocities.end()) {
return {};
@@ -91,7 +82,7 @@
return axisIdVelocity->second;
}
- inline void addVelocity(int32_t axis, uint32_t id, float velocity) {
+ inline void addVelocity(int32_t axis, int32_t id, float velocity) {
mVelocities[axis][id] = velocity;
}
@@ -112,19 +103,13 @@
// Resets the velocity tracker state.
void clear();
- // Resets the velocity tracker state for specific pointers.
+ // Resets the velocity tracker state for a specific pointer.
// Call this method when some pointers have changed and may be reusing
// an id that was assigned to a different pointer earlier.
- void clearPointers(BitSet32 idBits);
+ void clearPointer(int32_t pointerId);
- // Adds movement information for a set of pointers.
- // The idBits bitfield specifies the pointer ids of the pointers whose data points
- // are included in the movement.
- // The positions map contains a mapping of an axis to positions array.
- // The positions arrays contain information for each pointer in order by increasing id.
- // Each array's size should be equal to the number of one bits in idBits.
- void addMovement(nsecs_t eventTime, BitSet32 idBits,
- const std::map<int32_t, std::vector<float>>& positions);
+ // Adds movement information for a pointer for a specific axis
+ void addMovement(nsecs_t eventTime, int32_t pointerId, int32_t axis, float position);
// Adds movement information for all pointers in a MotionEvent, including historical samples.
void addMovement(const MotionEvent* event);
@@ -132,7 +117,7 @@
// Returns the velocity of the specified pointer id and axis in position units per second.
// Returns empty optional if there is insufficient movement information for the pointer, or if
// the given axis is not supported for velocity tracking.
- std::optional<float> getVelocity(int32_t axis, uint32_t id) const;
+ std::optional<float> getVelocity(int32_t axis, int32_t pointerId) const;
// Returns a ComputedVelocity instance with all available velocity data, using the given units
// (reference: units == 1 means "per millisecond"), and clamping each velocity between
@@ -142,15 +127,15 @@
// Gets an estimator for the recent movements of the specified pointer id for the given axis.
// Returns false and clears the estimator if there is no information available
// about the pointer.
- bool getEstimator(int32_t axis, uint32_t id, Estimator* outEstimator) const;
+ std::optional<Estimator> getEstimator(int32_t axis, int32_t pointerId) const;
// Gets the active pointer id, or -1 if none.
- inline int32_t getActivePointerId() const { return mActivePointerId; }
+ inline int32_t getActivePointerId() const { return mActivePointerId.value_or(-1); }
private:
nsecs_t mLastEventTime;
BitSet32 mCurrentPointerIdBits;
- int32_t mActivePointerId;
+ std::optional<int32_t> mActivePointerId;
// An override strategy passed in the constructor to be used for all axes.
// This strategy will apply to all axes, unless the default strategy is specified here.
@@ -182,10 +167,9 @@
public:
virtual ~VelocityTrackerStrategy() { }
- virtual void clearPointers(BitSet32 idBits) = 0;
- virtual void addMovement(nsecs_t eventTime, BitSet32 idBits,
- const std::vector<float>& positions) = 0;
- virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const = 0;
+ virtual void clearPointer(int32_t pointerId) = 0;
+ virtual void addMovement(nsecs_t eventTime, int32_t pointerId, float position) = 0;
+ virtual std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const = 0;
};
@@ -194,29 +178,28 @@
*/
class LeastSquaresVelocityTrackerStrategy : public VelocityTrackerStrategy {
public:
- enum Weighting {
+ enum class Weighting {
// No weights applied. All data points are equally reliable.
- WEIGHTING_NONE,
+ NONE,
// Weight by time delta. Data points clustered together are weighted less.
- WEIGHTING_DELTA,
+ DELTA,
// Weight such that points within a certain horizon are weighed more than those
// outside of that horizon.
- WEIGHTING_CENTRAL,
+ CENTRAL,
// Weight such that points older than a certain amount are weighed less.
- WEIGHTING_RECENT,
+ RECENT,
};
// Degree must be no greater than Estimator::MAX_DEGREE.
- LeastSquaresVelocityTrackerStrategy(uint32_t degree, Weighting weighting = WEIGHTING_NONE);
- virtual ~LeastSquaresVelocityTrackerStrategy();
+ LeastSquaresVelocityTrackerStrategy(uint32_t degree, Weighting weighting = Weighting::NONE);
+ ~LeastSquaresVelocityTrackerStrategy() override;
- virtual void clearPointers(BitSet32 idBits);
- void addMovement(nsecs_t eventTime, BitSet32 idBits,
- const std::vector<float>& positions) override;
- virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
+ void clearPointer(int32_t pointerId) override;
+ void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override;
+ std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override;
private:
// Sample horizon.
@@ -229,18 +212,15 @@
struct Movement {
nsecs_t eventTime;
- BitSet32 idBits;
- float positions[MAX_POINTERS];
-
- inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; }
+ float position;
};
- float chooseWeight(uint32_t index) const;
+ float chooseWeight(int32_t pointerId, uint32_t index) const;
const uint32_t mDegree;
const Weighting mWeighting;
- uint32_t mIndex;
- Movement mMovements[HISTORY_SIZE];
+ std::map<int32_t /*pointerId*/, size_t /*positionInArray*/> mIndex;
+ std::map<int32_t /*pointerId*/, std::array<Movement, HISTORY_SIZE>> mMovements;
};
@@ -251,12 +231,11 @@
public:
// Degree must be 1 or 2.
IntegratingVelocityTrackerStrategy(uint32_t degree);
- ~IntegratingVelocityTrackerStrategy();
+ ~IntegratingVelocityTrackerStrategy() override;
- virtual void clearPointers(BitSet32 idBits);
- void addMovement(nsecs_t eventTime, BitSet32 idBits,
- const std::vector<float>& positions) override;
- virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
+ void clearPointer(int32_t pointerId) override;
+ void addMovement(nsecs_t eventTime, int32_t pointerId, float positions) override;
+ std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override;
private:
// Current state estimate for a particular pointer.
@@ -283,12 +262,11 @@
class LegacyVelocityTrackerStrategy : public VelocityTrackerStrategy {
public:
LegacyVelocityTrackerStrategy();
- virtual ~LegacyVelocityTrackerStrategy();
+ ~LegacyVelocityTrackerStrategy() override;
- virtual void clearPointers(BitSet32 idBits);
- void addMovement(nsecs_t eventTime, BitSet32 idBits,
- const std::vector<float>& positions) override;
- virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
+ void clearPointer(int32_t pointerId) override;
+ void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override;
+ std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override;
private:
// Oldest sample to consider when calculating the velocity.
@@ -302,25 +280,21 @@
struct Movement {
nsecs_t eventTime;
- BitSet32 idBits;
- float positions[MAX_POINTERS];
-
- inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; }
+ float position;
};
- uint32_t mIndex;
- Movement mMovements[HISTORY_SIZE];
+ std::map<int32_t /*pointerId*/, size_t /*positionInArray*/> mIndex;
+ std::map<int32_t /*pointerId*/, std::array<Movement, HISTORY_SIZE>> mMovements;
};
class ImpulseVelocityTrackerStrategy : public VelocityTrackerStrategy {
public:
ImpulseVelocityTrackerStrategy(bool deltaValues);
- virtual ~ImpulseVelocityTrackerStrategy();
+ ~ImpulseVelocityTrackerStrategy() override;
- virtual void clearPointers(BitSet32 idBits);
- void addMovement(nsecs_t eventTime, BitSet32 idBits,
- const std::vector<float>& positions) override;
- virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
+ void clearPointer(int32_t pointerId) override;
+ void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override;
+ std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override;
private:
// Sample horizon.
@@ -333,10 +307,7 @@
struct Movement {
nsecs_t eventTime;
- BitSet32 idBits;
- float positions[MAX_POINTERS];
-
- inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; }
+ float position;
};
// Whether or not the input movement values for the strategy come in the form of delta values.
@@ -344,8 +315,8 @@
// velocity calculation.
const bool mDeltaValues;
- size_t mIndex;
- Movement mMovements[HISTORY_SIZE];
+ std::map<int32_t /*pointerId*/, size_t /*positionInArray*/> mIndex;
+ std::map<int32_t /*pointerId*/, std::array<Movement, HISTORY_SIZE>> mMovements;
};
} // namespace android
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 6bf7049..808b1ec 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -76,7 +76,6 @@
srcs: [
"Binder.cpp",
- "BinderRecordReplay.cpp",
"BpBinder.cpp",
"Debug.cpp",
"FdTrigger.cpp",
@@ -84,6 +83,7 @@
"IResultReceiver.cpp",
"Parcel.cpp",
"ParcelFileDescriptor.cpp",
+ "RecordedTransaction.cpp",
"RpcSession.cpp",
"RpcServer.cpp",
"RpcState.cpp",
@@ -195,18 +195,25 @@
],
}
-cc_library_shared {
- name: "libbinder_on_trusty_mock",
- defaults: ["libbinder_common_defaults"],
+cc_library_headers {
+ name: "trusty_mock_headers",
+ host_supported: true,
- srcs: [
- // Trusty-specific files
- "trusty/logging.cpp",
- "trusty/OS.cpp",
- "trusty/RpcServerTrusty.cpp",
- "trusty/RpcTransportTipcTrusty.cpp",
- "trusty/TrustyStatus.cpp",
- "trusty/socket.cpp",
+ export_include_dirs: [
+ "trusty/include",
+ "trusty/include_mock",
+ ],
+
+ visibility: [
+ ":__subpackages__",
+ ],
+}
+
+cc_defaults {
+ name: "trusty_mock_defaults",
+
+ header_libs: [
+ "trusty_mock_headers",
],
cflags: [
@@ -227,16 +234,29 @@
],
rtti: false,
- local_include_dirs: [
- "trusty/include",
- "trusty/include_mock",
- ],
-
visibility: [
":__subpackages__",
],
}
+cc_library_shared {
+ name: "libbinder_on_trusty_mock",
+ defaults: [
+ "libbinder_common_defaults",
+ "trusty_mock_defaults",
+ ],
+
+ srcs: [
+ // Trusty-specific files
+ "trusty/logging.cpp",
+ "trusty/OS.cpp",
+ "trusty/RpcServerTrusty.cpp",
+ "trusty/RpcTransportTipcTrusty.cpp",
+ "trusty/TrustyStatus.cpp",
+ "trusty/socket.cpp",
+ ],
+}
+
cc_defaults {
name: "libbinder_kernel_defaults",
srcs: [
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index da5affb..3e49656 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -21,13 +21,13 @@
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
-#include <binder/BinderRecordReplay.h>
#include <binder/BpBinder.h>
#include <binder/IInterface.h>
#include <binder/IPCThreadState.h>
#include <binder/IResultReceiver.h>
#include <binder/IShellCallback.h>
#include <binder/Parcel.h>
+#include <binder/RecordedTransaction.h>
#include <binder/RpcServer.h>
#include <cutils/compiler.h>
#include <private/android_filesystem_config.h>
@@ -409,11 +409,9 @@
Parcel emptyReply;
timespec ts;
timespec_get(&ts, TIME_UTC);
- auto transaction =
- android::binder::debug::RecordedTransaction::fromDetails(code, flags, ts, data,
- reply ? *reply
- : emptyReply,
- err);
+ auto transaction = android::binder::debug::RecordedTransaction::
+ fromDetails(getInterfaceDescriptor(), code, flags, ts, data,
+ reply ? *reply : emptyReply, err);
if (transaction) {
if (status_t err = transaction->dumpToFile(e->mRecordingFd); err != NO_ERROR) {
LOG(INFO) << "Failed to dump RecordedTransaction to file with error " << err;
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index 1c470a1..d03326e 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -388,7 +388,7 @@
{
if (isRpcBinder()) {
if (rpcSession()->getMaxIncomingThreads() < 1) {
- LOG_ALWAYS_FATAL("Cannot register a DeathRecipient without any incoming connections.");
+ ALOGE("Cannot register a DeathRecipient without any incoming connections.");
return INVALID_OPERATION;
}
} else if constexpr (!kEnableKernelIpc) {
diff --git a/libs/binder/BinderRecordReplay.cpp b/libs/binder/RecordedTransaction.cpp
similarity index 86%
rename from libs/binder/BinderRecordReplay.cpp
rename to libs/binder/RecordedTransaction.cpp
index 58bb106..5406205 100644
--- a/libs/binder/BinderRecordReplay.cpp
+++ b/libs/binder/RecordedTransaction.cpp
@@ -17,7 +17,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
-#include <binder/BinderRecordReplay.h>
+#include <binder/RecordedTransaction.h>
#include <sys/mman.h>
#include <algorithm>
@@ -106,24 +106,30 @@
// End Chunk may therefore produce an empty, meaningless RecordedTransaction.
RecordedTransaction::RecordedTransaction(RecordedTransaction&& t) noexcept {
- mHeader = t.mHeader;
+ mData = t.mData;
mSent.setData(t.getDataParcel().data(), t.getDataParcel().dataSize());
mReply.setData(t.getReplyParcel().data(), t.getReplyParcel().dataSize());
}
-std::optional<RecordedTransaction> RecordedTransaction::fromDetails(uint32_t code, uint32_t flags,
- timespec timestamp,
- const Parcel& dataParcel,
- const Parcel& replyParcel,
- status_t err) {
+std::optional<RecordedTransaction> RecordedTransaction::fromDetails(
+ const String16& interfaceName, uint32_t code, uint32_t flags, timespec timestamp,
+ const Parcel& dataParcel, const Parcel& replyParcel, status_t err) {
RecordedTransaction t;
- t.mHeader = {code,
- flags,
- static_cast<int32_t>(err),
- dataParcel.isForRpc() ? static_cast<uint32_t>(1) : static_cast<uint32_t>(0),
- static_cast<int64_t>(timestamp.tv_sec),
- static_cast<int32_t>(timestamp.tv_nsec),
- 0};
+ t.mData.mHeader = {code,
+ flags,
+ static_cast<int32_t>(err),
+ dataParcel.isForRpc() ? static_cast<uint32_t>(1) : static_cast<uint32_t>(0),
+ static_cast<int64_t>(timestamp.tv_sec),
+ static_cast<int32_t>(timestamp.tv_nsec),
+ 0};
+
+ t.mData.mInterfaceName = String8(interfaceName);
+ if (interfaceName.size() != t.mData.mInterfaceName.bytes()) {
+ LOG(ERROR) << "Interface Name is not valid. Contains characters that aren't single byte "
+ "utf-8: "
+ << interfaceName;
+ return std::nullopt;
+ }
if (t.mSent.setData(dataParcel.data(), dataParcel.dataSize()) != android::NO_ERROR) {
LOG(ERROR) << "Failed to set sent parcel data.";
@@ -142,6 +148,7 @@
HEADER_CHUNK = 1,
DATA_PARCEL_CHUNK = 2,
REPLY_PARCEL_CHUNK = 3,
+ INTERFACE_NAME_CHUNK = 4,
END_CHUNK = 0x00ffffff,
};
@@ -220,7 +227,11 @@
<< sizeof(TransactionHeader) << ".";
return std::nullopt;
}
- t.mHeader = *reinterpret_cast<TransactionHeader*>(payloadMap);
+ t.mData.mHeader = *reinterpret_cast<TransactionHeader*>(payloadMap);
+ break;
+ }
+ case INTERFACE_NAME_CHUNK: {
+ t.mData.mInterfaceName.setTo(reinterpret_cast<char*>(payloadMap), chunk.dataSize);
break;
}
case DATA_PARCEL_CHUNK: {
@@ -291,10 +302,17 @@
android::status_t RecordedTransaction::dumpToFile(const unique_fd& fd) const {
if (NO_ERROR !=
writeChunk(fd, HEADER_CHUNK, sizeof(TransactionHeader),
- reinterpret_cast<const uint8_t*>(&mHeader))) {
+ reinterpret_cast<const uint8_t*>(&(mData.mHeader)))) {
LOG(ERROR) << "Failed to write transactionHeader to fd " << fd.get();
return UNKNOWN_ERROR;
}
+ if (NO_ERROR !=
+ writeChunk(fd, INTERFACE_NAME_CHUNK, mData.mInterfaceName.size() * sizeof(uint8_t),
+ reinterpret_cast<const uint8_t*>(mData.mInterfaceName.string()))) {
+ LOG(INFO) << "Failed to write Interface Name Chunk to fd " << fd.get();
+ return UNKNOWN_ERROR;
+ }
+
if (NO_ERROR != writeChunk(fd, DATA_PARCEL_CHUNK, mSent.dataSize(), mSent.data())) {
LOG(ERROR) << "Failed to write sent Parcel to fd " << fd.get();
return UNKNOWN_ERROR;
@@ -310,26 +328,30 @@
return NO_ERROR;
}
+const android::String8& RecordedTransaction::getInterfaceName() const {
+ return mData.mInterfaceName;
+}
+
uint32_t RecordedTransaction::getCode() const {
- return mHeader.code;
+ return mData.mHeader.code;
}
uint32_t RecordedTransaction::getFlags() const {
- return mHeader.flags;
+ return mData.mHeader.flags;
}
int32_t RecordedTransaction::getReturnedStatus() const {
- return mHeader.statusReturned;
+ return mData.mHeader.statusReturned;
}
timespec RecordedTransaction::getTimestamp() const {
- time_t sec = mHeader.timestampSeconds;
- int32_t nsec = mHeader.timestampNanoseconds;
+ time_t sec = mData.mHeader.timestampSeconds;
+ int32_t nsec = mData.mHeader.timestampNanoseconds;
return (timespec){.tv_sec = sec, .tv_nsec = nsec};
}
uint32_t RecordedTransaction::getVersion() const {
- return mHeader.version;
+ return mData.mHeader.version;
}
const Parcel& RecordedTransaction::getDataParcel() const {
diff --git a/libs/binder/include/binder/Binder.h b/libs/binder/include/binder/Binder.h
index 08dbd13..d960a0b 100644
--- a/libs/binder/include/binder/Binder.h
+++ b/libs/binder/include/binder/Binder.h
@@ -106,7 +106,7 @@
const sp<IBinder>& keepAliveBinder);
// Start recording transactions to the unique_fd in data.
- // See BinderRecordReplay.h for more details.
+ // See RecordedTransaction.h for more details.
[[nodiscard]] status_t startRecordingTransactions(const Parcel& data);
// Stop the current recording.
[[nodiscard]] status_t stopRecordingTransactions();
diff --git a/libs/binder/include/binder/BpBinder.h b/libs/binder/include/binder/BpBinder.h
index 57e103d..5496d61 100644
--- a/libs/binder/include/binder/BpBinder.h
+++ b/libs/binder/include/binder/BpBinder.h
@@ -91,7 +91,7 @@
std::optional<int32_t> getDebugBinderHandle() const;
// Start recording transactions to the unique_fd.
- // See BinderRecordReplay.h for more details.
+ // See RecordedTransaction.h for more details.
status_t startRecordingBinder(const android::base::unique_fd& fd);
// Stop the current recording.
status_t stopRecordingBinder();
diff --git a/libs/binder/include/binder/IInterface.h b/libs/binder/include/binder/IInterface.h
index 8cc8105..dc572ac 100644
--- a/libs/binder/include/binder/IInterface.h
+++ b/libs/binder/include/binder/IInterface.h
@@ -230,6 +230,7 @@
"android.graphicsenv.IGpuService",
"android.gui.IConsumerListener",
"android.gui.IGraphicBufferConsumer",
+ "android.gui.ITransactionComposerListener",
"android.gui.SensorEventConnection",
"android.gui.SensorServer",
"android.hardware.ICamera",
diff --git a/libs/binder/include/binder/BinderRecordReplay.h b/libs/binder/include/binder/RecordedTransaction.h
similarity index 87%
rename from libs/binder/include/binder/BinderRecordReplay.h
rename to libs/binder/include/binder/RecordedTransaction.h
index ff983f0..4966330 100644
--- a/libs/binder/include/binder/BinderRecordReplay.h
+++ b/libs/binder/include/binder/RecordedTransaction.h
@@ -26,20 +26,22 @@
// Warning: Transactions are sequentially recorded to the file descriptor in a
// non-stable format. A detailed description of the recording format can be found in
-// BinderRecordReplay.cpp.
+// RecordedTransaction.cpp.
class RecordedTransaction {
public:
// Filled with the first transaction from fd.
static std::optional<RecordedTransaction> fromFile(const android::base::unique_fd& fd);
// Filled with the arguments.
- static std::optional<RecordedTransaction> fromDetails(uint32_t code, uint32_t flags,
+ static std::optional<RecordedTransaction> fromDetails(const String16& interfaceName,
+ uint32_t code, uint32_t flags,
timespec timestamp, const Parcel& data,
const Parcel& reply, status_t err);
RecordedTransaction(RecordedTransaction&& t) noexcept;
[[nodiscard]] status_t dumpToFile(const android::base::unique_fd& fd) const;
+ const String8& getInterfaceName() const;
uint32_t getCode() const;
uint32_t getFlags() const;
int32_t getReturnedStatus() const;
@@ -69,7 +71,11 @@
static_assert(sizeof(TransactionHeader) == 32);
static_assert(sizeof(TransactionHeader) % 8 == 0);
- TransactionHeader mHeader;
+ struct MovableData { // movable
+ TransactionHeader mHeader;
+ String8 mInterfaceName;
+ };
+ MovableData mData;
Parcel mSent;
Parcel mReply;
};
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index 5db3187..bab4e73 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -336,6 +336,29 @@
],
}
+cc_binary {
+ name: "binderRpcTestService_on_trusty_mock",
+ defaults: [
+ "trusty_mock_defaults",
+ ],
+
+ srcs: [
+ "binderRpcTestCommon.cpp",
+ "binderRpcTestServiceTrusty.cpp",
+ ],
+
+ shared_libs: [
+ "libbinder_on_trusty_mock",
+ "libbase",
+ "libutils",
+ "libcutils",
+ ],
+
+ static_libs: [
+ "binderRpcTestIface-cpp",
+ ],
+}
+
cc_test {
name: "binderRpcTest",
defaults: [
@@ -347,6 +370,7 @@
// Add the Trusty mock library as a fake dependency so it gets built
required: [
"libbinder_on_trusty_mock",
+ "binderRpcTestService_on_trusty_mock",
],
}
diff --git a/libs/binder/tests/binderRecordedTransactionTest.cpp b/libs/binder/tests/binderRecordedTransactionTest.cpp
index 67553fc..2f5c8c6 100644
--- a/libs/binder/tests/binderRecordedTransactionTest.cpp
+++ b/libs/binder/tests/binderRecordedTransactionTest.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include <binder/BinderRecordReplay.h>
+#include <binder/RecordedTransaction.h>
#include <gtest/gtest.h>
#include <utils/Errors.h>
@@ -24,13 +24,16 @@
using android::binder::debug::RecordedTransaction;
TEST(BinderRecordedTransaction, RoundTripEncoding) {
+ android::String16 interfaceName("SampleInterface");
Parcel d;
d.writeInt32(12);
d.writeInt64(2);
Parcel r;
r.writeInt32(99);
timespec ts = {1232456, 567890};
- auto transaction = RecordedTransaction::fromDetails(1, 42, ts, d, r, 0);
+
+ auto transaction = RecordedTransaction::fromDetails(interfaceName, 1, 42, ts, d, r, 0);
+ EXPECT_TRUE(transaction.has_value());
auto file = std::tmpfile();
auto fd = unique_fd(fcntl(fileno(file), F_DUPFD, 1));
@@ -42,6 +45,7 @@
auto retrievedTransaction = RecordedTransaction::fromFile(fd);
+ EXPECT_EQ(retrievedTransaction->getInterfaceName(), android::String8(interfaceName));
EXPECT_EQ(retrievedTransaction->getCode(), 1);
EXPECT_EQ(retrievedTransaction->getFlags(), 42);
EXPECT_EQ(retrievedTransaction->getTimestamp().tv_sec, ts.tv_sec);
@@ -57,13 +61,14 @@
}
TEST(BinderRecordedTransaction, Checksum) {
+ android::String16 interfaceName("SampleInterface");
Parcel d;
d.writeInt32(12);
d.writeInt64(2);
Parcel r;
r.writeInt32(99);
timespec ts = {1232456, 567890};
- auto transaction = RecordedTransaction::fromDetails(1, 42, ts, d, r, 0);
+ auto transaction = RecordedTransaction::fromDetails(interfaceName, 1, 42, ts, d, r, 0);
auto file = std::tmpfile();
auto fd = unique_fd(fcntl(fileno(file), F_DUPFD, 1));
@@ -91,6 +96,7 @@
std::vector<uint8_t> largePayload;
uint8_t filler = 0xaa;
largePayload.insert(largePayload.end(), largeDataSize, filler);
+ android::String16 interfaceName("SampleInterface");
Parcel d;
d.writeInt32(12);
d.writeInt64(2);
@@ -98,7 +104,7 @@
Parcel r;
r.writeInt32(99);
timespec ts = {1232456, 567890};
- auto transaction = RecordedTransaction::fromDetails(1, 42, ts, d, r, 0);
+ auto transaction = RecordedTransaction::fromDetails(interfaceName, 1, 42, ts, d, r, 0);
auto file = std::tmpfile();
auto fd = unique_fd(fcntl(fileno(file), F_DUPFD, 1));
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 8afa49b..36c8d8c 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <aidl/IBinderRpcTest.h>
#include <android-base/stringprintf.h>
#include <chrono>
@@ -682,7 +683,7 @@
proc.expectAlreadyShutdown = true;
}
-TEST_P(BinderRpc, DeathRecipientFatalWithoutIncoming) {
+TEST_P(BinderRpc, DeathRecipientFailsWithoutIncoming) {
class MyDeathRec : public IBinder::DeathRecipient {
public:
void binderDied(const wp<IBinder>& /* who */) override {}
@@ -692,8 +693,7 @@
{.numThreads = 1, .numSessions = 1, .numIncomingConnections = 0});
auto dr = sp<MyDeathRec>::make();
- EXPECT_DEATH(proc.rootBinder->linkToDeath(dr, (void*)1, 0),
- "Cannot register a DeathRecipient without any incoming connections.");
+ EXPECT_EQ(INVALID_OPERATION, proc.rootBinder->linkToDeath(dr, (void*)1, 0));
}
TEST_P(BinderRpc, UnlinkDeathRecipient) {
@@ -1100,15 +1100,6 @@
return ret;
}
-static std::vector<uint32_t> testVersions() {
- std::vector<uint32_t> versions;
- for (size_t i = 0; i < RPC_WIRE_PROTOCOL_VERSION_NEXT; i++) {
- versions.push_back(i);
- }
- versions.push_back(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL);
- return versions;
-}
-
INSTANTIATE_TEST_CASE_P(PerSocket, BinderRpc,
::testing::Combine(::testing::ValuesIn(testSocketTypes()),
::testing::ValuesIn(RpcSecurityValues()),
diff --git a/libs/binder/tests/binderRpcTestCommon.cpp b/libs/binder/tests/binderRpcTestCommon.cpp
index 0d9aa95..fe9a5a1 100644
--- a/libs/binder/tests/binderRpcTestCommon.cpp
+++ b/libs/binder/tests/binderRpcTestCommon.cpp
@@ -19,6 +19,6 @@
namespace android {
std::atomic<int32_t> MyBinderRpcSession::gNum;
-sp<IBinder> MyBinderRpcTest::mHeldBinder;
+sp<IBinder> MyBinderRpcTestBase::mHeldBinder;
} // namespace android
diff --git a/libs/binder/tests/binderRpcTestCommon.h b/libs/binder/tests/binderRpcTestCommon.h
index 654e16c..262d7e4 100644
--- a/libs/binder/tests/binderRpcTestCommon.h
+++ b/libs/binder/tests/binderRpcTestCommon.h
@@ -22,37 +22,42 @@
#include <BnBinderRpcCallback.h>
#include <BnBinderRpcSession.h>
#include <BnBinderRpcTest.h>
-#include <aidl/IBinderRpcTest.h>
+#include <android-base/stringprintf.h>
+#include <binder/Binder.h>
+#include <binder/BpBinder.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/RpcServer.h>
+#include <binder/RpcSession.h>
+#include <binder/RpcThreads.h>
+#include <binder/RpcTransport.h>
+#include <binder/RpcTransportRaw.h>
+#include <unistd.h>
+#include <cinttypes>
+#include <string>
+#include <vector>
+
+#ifndef __TRUSTY__
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android/binder_auto_utils.h>
#include <android/binder_libbinder.h>
-#include <binder/Binder.h>
-#include <binder/BpBinder.h>
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
-#include <binder/RpcServer.h>
-#include <binder/RpcSession.h>
-#include <binder/RpcThreads.h>
#include <binder/RpcTlsTestUtils.h>
#include <binder/RpcTlsUtils.h>
-#include <binder/RpcTransport.h>
-#include <binder/RpcTransportRaw.h>
#include <binder/RpcTransportTls.h>
-#include <unistd.h>
-#include <string>
-#include <vector>
#include <signal.h>
-#include "../BuildFlags.h"
-#include "../FdTrigger.h"
#include "../OS.h" // for testing UnixBootstrap clients
#include "../RpcSocketAddress.h" // for testing preconnected clients
-#include "../RpcState.h" // for debugging
#include "../vm_sockets.h" // for VMADDR_*
+#endif // __TRUSTY__
+
+#include "../BuildFlags.h"
+#include "../FdTrigger.h"
+#include "../RpcState.h" // for debugging
#include "utils/Errors.h"
namespace android {
@@ -65,6 +70,19 @@
return {RpcSecurity::RAW, RpcSecurity::TLS};
}
+static inline std::vector<uint32_t> testVersions() {
+ std::vector<uint32_t> versions;
+ for (size_t i = 0; i < RPC_WIRE_PROTOCOL_VERSION_NEXT; i++) {
+ versions.push_back(i);
+ }
+ versions.push_back(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL);
+ return versions;
+}
+
+static inline std::string trustyIpcPort(uint32_t serverVersion) {
+ return base::StringPrintf("com.android.trusty.binderRpcTestService.V%" PRIu32, serverVersion);
+}
+
enum class SocketType {
PRECONNECTED,
UNIX,
@@ -118,6 +136,7 @@
bool allowConnectFailure = false;
};
+#ifndef __TRUSTY__
static inline void writeString(android::base::borrowed_fd fd, std::string_view str) {
uint64_t length = str.length();
CHECK(android::base::WriteFully(fd, &length, sizeof(length)));
@@ -182,6 +201,7 @@
}).detach();
return readFd;
}
+#endif // __TRUSTY__
// A threadsafe channel where writes block until the value is read.
template <typename T>
@@ -252,9 +272,12 @@
std::vector<std::string> mValues;
};
-class MyBinderRpcTest : public BnBinderRpcTest {
+// Base class for all concrete implementations of MyBinderRpcTest.
+// Sub-classes that want to provide a full implementation should derive
+// from this class instead of MyBinderRpcTestDefault below so the compiler
+// checks that all methods are implemented.
+class MyBinderRpcTestBase : public BnBinderRpcTest {
public:
- wp<RpcServer> server;
int port = 0;
Status sendString(const std::string& str) override {
@@ -269,18 +292,6 @@
*out = port;
return Status::ok();
}
- Status countBinders(std::vector<int32_t>* out) override {
- sp<RpcServer> spServer = server.promote();
- if (spServer == nullptr) {
- return Status::fromExceptionCode(Status::EX_NULL_POINTER);
- }
- out->clear();
- for (auto session : spServer->listSessions()) {
- size_t count = session->state()->countBinders();
- out->push_back(count);
- }
- return Status::ok();
- }
Status getNullBinder(sp<IBinder>* out) override {
out->clear();
return Status::ok();
@@ -381,62 +392,55 @@
return doCallback(callback, oneway, delayed, value);
}
- Status die(bool cleanup) override {
- if (cleanup) {
- exit(1);
- } else {
- _exit(1);
- }
- }
-
- Status scheduleShutdown() override {
- sp<RpcServer> strongServer = server.promote();
- if (strongServer == nullptr) {
+protected:
+ // Generic version of countBinders that works with both
+ // RpcServer and RpcServerTrusty
+ template <typename T>
+ Status countBindersImpl(const wp<T>& server, std::vector<int32_t>* out) {
+ sp<T> spServer = server.promote();
+ if (spServer == nullptr) {
return Status::fromExceptionCode(Status::EX_NULL_POINTER);
}
- RpcMaybeThread([=] {
- LOG_ALWAYS_FATAL_IF(!strongServer->shutdown(), "Could not shutdown");
- }).detach();
- return Status::ok();
- }
-
- Status useKernelBinderCallingId() override {
- // this is WRONG! It does not make sense when using RPC binder, and
- // because it is SO wrong, and so much code calls this, it should abort!
-
- if constexpr (kEnableKernelIpc) {
- (void)IPCThreadState::self()->getCallingPid();
+ out->clear();
+ for (auto session : spServer->listSessions()) {
+ size_t count = session->state()->countBinders();
+ out->push_back(count);
}
return Status::ok();
}
+};
- Status echoAsFile(const std::string& content, android::os::ParcelFileDescriptor* out) override {
- out->reset(mockFileDescriptor(content));
- return Status::ok();
+// Default implementation of MyBinderRpcTest that can be used as-is
+// or derived from by classes that only want to implement a subset of
+// the unimplemented methods
+class MyBinderRpcTestDefault : public MyBinderRpcTestBase {
+public:
+ Status countBinders(std::vector<int32_t>* /*out*/) override {
+ return Status::fromStatusT(UNKNOWN_TRANSACTION);
}
- Status concatFiles(const std::vector<android::os::ParcelFileDescriptor>& files,
- android::os::ParcelFileDescriptor* out) override {
- std::string acc;
- for (const auto& file : files) {
- std::string result;
- CHECK(android::base::ReadFdToString(file.get(), &result));
- acc.append(result);
- }
- out->reset(mockFileDescriptor(acc));
- return Status::ok();
+ Status die(bool /*cleanup*/) override { return Status::fromStatusT(UNKNOWN_TRANSACTION); }
+
+ Status scheduleShutdown() override { return Status::fromStatusT(UNKNOWN_TRANSACTION); }
+
+ Status useKernelBinderCallingId() override { return Status::fromStatusT(UNKNOWN_TRANSACTION); }
+
+ Status echoAsFile(const std::string& /*content*/,
+ android::os::ParcelFileDescriptor* /*out*/) override {
+ return Status::fromStatusT(UNKNOWN_TRANSACTION);
}
- HandoffChannel<android::base::unique_fd> mFdChannel;
-
- Status blockingSendFdOneway(const android::os::ParcelFileDescriptor& fd) override {
- mFdChannel.write(android::base::unique_fd(fcntl(fd.get(), F_DUPFD_CLOEXEC, 0)));
- return Status::ok();
+ Status concatFiles(const std::vector<android::os::ParcelFileDescriptor>& /*files*/,
+ android::os::ParcelFileDescriptor* /*out*/) override {
+ return Status::fromStatusT(UNKNOWN_TRANSACTION);
}
- Status blockingRecvFd(android::os::ParcelFileDescriptor* fd) override {
- fd->reset(mFdChannel.read());
- return Status::ok();
+ Status blockingSendFdOneway(const android::os::ParcelFileDescriptor& /*fd*/) override {
+ return Status::fromStatusT(UNKNOWN_TRANSACTION);
+ }
+
+ Status blockingRecvFd(android::os::ParcelFileDescriptor* /*fd*/) override {
+ return Status::fromStatusT(UNKNOWN_TRANSACTION);
}
};
diff --git a/libs/binder/tests/binderRpcTestService.cpp b/libs/binder/tests/binderRpcTestService.cpp
index cc9726b..714f063 100644
--- a/libs/binder/tests/binderRpcTestService.cpp
+++ b/libs/binder/tests/binderRpcTestService.cpp
@@ -18,6 +18,73 @@
using namespace android;
+class MyBinderRpcTestAndroid : public MyBinderRpcTestBase {
+public:
+ wp<RpcServer> server;
+
+ Status countBinders(std::vector<int32_t>* out) override {
+ return countBindersImpl(server, out);
+ }
+
+ Status die(bool cleanup) override {
+ if (cleanup) {
+ exit(1);
+ } else {
+ _exit(1);
+ }
+ }
+
+ Status scheduleShutdown() override {
+ sp<RpcServer> strongServer = server.promote();
+ if (strongServer == nullptr) {
+ return Status::fromExceptionCode(Status::EX_NULL_POINTER);
+ }
+ RpcMaybeThread([=] {
+ LOG_ALWAYS_FATAL_IF(!strongServer->shutdown(), "Could not shutdown");
+ }).detach();
+ return Status::ok();
+ }
+
+ Status useKernelBinderCallingId() override {
+ // this is WRONG! It does not make sense when using RPC binder, and
+ // because it is SO wrong, and so much code calls this, it should abort!
+
+ if constexpr (kEnableKernelIpc) {
+ (void)IPCThreadState::self()->getCallingPid();
+ }
+ return Status::ok();
+ }
+
+ Status echoAsFile(const std::string& content, android::os::ParcelFileDescriptor* out) override {
+ out->reset(mockFileDescriptor(content));
+ return Status::ok();
+ }
+
+ Status concatFiles(const std::vector<android::os::ParcelFileDescriptor>& files,
+ android::os::ParcelFileDescriptor* out) override {
+ std::string acc;
+ for (const auto& file : files) {
+ std::string result;
+ CHECK(android::base::ReadFdToString(file.get(), &result));
+ acc.append(result);
+ }
+ out->reset(mockFileDescriptor(acc));
+ return Status::ok();
+ }
+
+ HandoffChannel<android::base::unique_fd> mFdChannel;
+
+ Status blockingSendFdOneway(const android::os::ParcelFileDescriptor& fd) override {
+ mFdChannel.write(android::base::unique_fd(fcntl(fd.get(), F_DUPFD_CLOEXEC, 0)));
+ return Status::ok();
+ }
+
+ Status blockingRecvFd(android::os::ParcelFileDescriptor* fd) override {
+ fd->reset(mFdChannel.read());
+ return Status::ok();
+ }
+};
+
int main(int argc, const char* argv[]) {
LOG_ALWAYS_FATAL_IF(argc != 3, "Invalid number of arguments: %d", argc);
base::unique_fd writeEnd(atoi(argv[1]));
@@ -88,7 +155,7 @@
// sizeof(sa_family_t)==2 in addrlen
CHECK_GE(len, sizeof(sa_family_t));
const sockaddr* addr = reinterpret_cast<const sockaddr*>(addrPtr);
- sp<MyBinderRpcTest> service = sp<MyBinderRpcTest>::make();
+ sp<MyBinderRpcTestAndroid> service = sp<MyBinderRpcTestAndroid>::make();
switch (addr->sa_family) {
case AF_UNIX:
// nothing to save
diff --git a/libs/binder/tests/binderRpcTestServiceTrusty.cpp b/libs/binder/tests/binderRpcTestServiceTrusty.cpp
new file mode 100644
index 0000000..8557389
--- /dev/null
+++ b/libs/binder/tests/binderRpcTestServiceTrusty.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 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 TLOG_TAG "binderRpcTestService"
+
+#include <android-base/stringprintf.h>
+#include <binder/RpcServerTrusty.h>
+#include <inttypes.h>
+#include <lib/tipc/tipc.h>
+#include <lk/err_ptr.h>
+#include <stdio.h>
+#include <trusty_log.h>
+#include <vector>
+
+#include "binderRpcTestCommon.h"
+
+using namespace android;
+using android::base::StringPrintf;
+using binder::Status;
+
+static int gConnectionCounter = 0;
+
+class MyBinderRpcTestTrusty : public MyBinderRpcTestDefault {
+public:
+ wp<RpcServerTrusty> server;
+
+ Status countBinders(std::vector<int32_t>* out) override {
+ return countBindersImpl(server, out);
+ }
+
+ Status scheduleShutdown() override {
+ // TODO: Trusty does not support shutting down the tipc event loop,
+ // so we just terminate the service app since it is marked
+ // restart_on_exit
+ exit(EXIT_SUCCESS);
+ }
+
+ // TODO(b/242940548): implement echoAsFile and concatFiles
+};
+
+struct ServerInfo {
+ std::unique_ptr<std::string> port;
+ sp<RpcServerTrusty> server;
+};
+
+int main(void) {
+ TLOGI("Starting service\n");
+
+ tipc_hset* hset = tipc_hset_create();
+ if (IS_ERR(hset)) {
+ TLOGE("Failed to create handle set (%d)\n", PTR_ERR(hset));
+ return EXIT_FAILURE;
+ }
+
+ const auto port_acl = RpcServerTrusty::PortAcl{
+ .flags = IPC_PORT_ALLOW_NS_CONNECT | IPC_PORT_ALLOW_TA_CONNECT,
+ };
+
+ std::vector<ServerInfo> servers;
+ for (auto serverVersion : testVersions()) {
+ ServerInfo serverInfo{
+ .port = std::make_unique<std::string>(trustyIpcPort(serverVersion)),
+ };
+ TLOGI("Adding service port '%s'\n", serverInfo.port->c_str());
+
+ // Message size needs to be large enough to cover all messages sent by the
+ // tests: SendAndGetResultBackBig sends two large strings.
+ constexpr size_t max_msg_size = 4096;
+ auto serverOrErr =
+ RpcServerTrusty::make(hset, serverInfo.port->c_str(),
+ std::shared_ptr<const RpcServerTrusty::PortAcl>(&port_acl),
+ max_msg_size);
+ if (!serverOrErr.ok()) {
+ TLOGE("Failed to create RpcServer (%d)\n", serverOrErr.error());
+ return EXIT_FAILURE;
+ }
+
+ auto server = std::move(*serverOrErr);
+ serverInfo.server = server;
+ serverInfo.server->setProtocolVersion(serverVersion);
+ serverInfo.server->setPerSessionRootObject([=](const void* /*addrPtr*/, size_t /*len*/) {
+ auto service = sp<MyBinderRpcTestTrusty>::make();
+ // Assign a unique connection identifier to service->port so
+ // getClientPort returns a unique value per connection
+ service->port = ++gConnectionCounter;
+ service->server = server;
+ return service;
+ });
+
+ servers.push_back(std::move(serverInfo));
+ }
+
+ return tipc_run_event_loop(hset);
+}
diff --git a/libs/binder/tests/binderRpcUniversalTests.cpp b/libs/binder/tests/binderRpcUniversalTests.cpp
index f960442..9cd8a82 100644
--- a/libs/binder/tests/binderRpcUniversalTests.cpp
+++ b/libs/binder/tests/binderRpcUniversalTests.cpp
@@ -327,7 +327,7 @@
{RpcSession::FileDescriptorTransportMode::UNIX},
});
- auto nastyNester = sp<MyBinderRpcTest>::make();
+ auto nastyNester = sp<MyBinderRpcTestDefault>::make();
EXPECT_OK(proc.rootIface->nestMe(nastyNester, 10));
wp<IBinder> weak = nastyNester;
diff --git a/libs/binder/trusty/binderRpcTest/aidl/rules.mk b/libs/binder/trusty/binderRpcTest/aidl/rules.mk
new file mode 100644
index 0000000..1afd324
--- /dev/null
+++ b/libs/binder/trusty/binderRpcTest/aidl/rules.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2022 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_TESTS_DIR := frameworks/native/libs/binder/tests
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_AIDLS := \
+ $(LIBBINDER_TESTS_DIR)/BinderRpcTestClientInfo.aidl \
+ $(LIBBINDER_TESTS_DIR)/BinderRpcTestServerConfig.aidl \
+ $(LIBBINDER_TESTS_DIR)/BinderRpcTestServerInfo.aidl \
+ $(LIBBINDER_TESTS_DIR)/IBinderRpcCallback.aidl \
+ $(LIBBINDER_TESTS_DIR)/IBinderRpcSession.aidl \
+ $(LIBBINDER_TESTS_DIR)/IBinderRpcTest.aidl \
+ $(LIBBINDER_TESTS_DIR)/ParcelableCertificateData.aidl \
+
+include make/aidl.mk
diff --git a/libs/binder/trusty/binderRpcTest/service/manifest.json b/libs/binder/trusty/binderRpcTest/service/manifest.json
new file mode 100644
index 0000000..1c4f7ee
--- /dev/null
+++ b/libs/binder/trusty/binderRpcTest/service/manifest.json
@@ -0,0 +1,10 @@
+{
+ "uuid": "87e424e5-69d7-4bbd-8b7c-7e24812cbc94",
+ "app_name": "binderRpcTestService",
+ "min_heap": 65536,
+ "min_stack": 16384,
+ "mgmt_flags": {
+ "restart_on_exit": true,
+ "non_critical_app": true
+ }
+}
diff --git a/libs/binder/trusty/binderRpcTest/service/rules.mk b/libs/binder/trusty/binderRpcTest/service/rules.mk
new file mode 100644
index 0000000..5d1a51d
--- /dev/null
+++ b/libs/binder/trusty/binderRpcTest/service/rules.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2022 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_TESTS_DIR := frameworks/native/libs/binder/tests
+
+MODULE := $(LOCAL_DIR)
+
+MANIFEST := $(LOCAL_DIR)/manifest.json
+
+MODULE_SRCS := \
+ $(LIBBINDER_TESTS_DIR)/binderRpcTestCommon.cpp \
+ $(LIBBINDER_TESTS_DIR)/binderRpcTestServiceTrusty.cpp \
+
+MODULE_LIBRARY_DEPS := \
+ frameworks/native/libs/binder/trusty \
+ frameworks/native/libs/binder/trusty/binderRpcTest/aidl \
+ trusty/user/base/lib/libstdc++-trusty \
+ trusty/user/base/lib/tipc \
+
+include make/trusted_app.mk
diff --git a/libs/binder/trusty/include/binder/RpcServerTrusty.h b/libs/binder/trusty/include/binder/RpcServerTrusty.h
index 7d9dd8c..6678eb8 100644
--- a/libs/binder/trusty/include/binder/RpcServerTrusty.h
+++ b/libs/binder/trusty/include/binder/RpcServerTrusty.h
@@ -71,6 +71,11 @@
}
sp<IBinder> getRootObject() { return mRpcServer->getRootObject(); }
+ /**
+ * For debugging!
+ */
+ std::vector<sp<RpcSession>> listSessions() { return mRpcServer->listSessions(); }
+
private:
// Both this class and RpcServer have multiple non-copyable fields,
// including mPortAcl below which can't be copied because mUuidPtrs
diff --git a/libs/gui/aidl/android/gui/ReleaseCallbackId.aidl b/libs/binder/trusty/include_mock/lib/tipc/tipc.h
similarity index 69%
copy from libs/gui/aidl/android/gui/ReleaseCallbackId.aidl
copy to libs/binder/trusty/include_mock/lib/tipc/tipc.h
index c86de34..f295be4 100644
--- a/libs/gui/aidl/android/gui/ReleaseCallbackId.aidl
+++ b/libs/binder/trusty/include_mock/lib/tipc/tipc.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -13,7 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#pragma once
-package android.gui;
+__BEGIN_DECLS
-parcelable ReleaseCallbackId cpp_header "gui/ReleaseCallbackId.h";
+struct tipc_hset;
+
+struct tipc_hset* tipc_hset_create(void) {
+ return nullptr;
+}
+int tipc_run_event_loop(struct tipc_hset*) {
+ return 0;
+}
+
+__END_DECLS
diff --git a/libs/gui/aidl/android/gui/ListenerStats.aidl b/libs/binder/trusty/include_mock/lk/err_ptr.h
similarity index 80%
rename from libs/gui/aidl/android/gui/ListenerStats.aidl
rename to libs/binder/trusty/include_mock/lk/err_ptr.h
index 63248b2..ab3fbba 100644
--- a/libs/gui/aidl/android/gui/ListenerStats.aidl
+++ b/libs/binder/trusty/include_mock/lk/err_ptr.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#pragma once
-package android.gui;
-
-parcelable ListenerStats cpp_header "gui/ListenerStats.h";
+#define IS_ERR(x) (!(x))
+#define PTR_ERR(x) (!!(x))
diff --git a/libs/binder/trusty/include_mock/trusty_ipc.h b/libs/binder/trusty/include_mock/trusty_ipc.h
index a2170ce..43ab84a 100644
--- a/libs/binder/trusty/include_mock/trusty_ipc.h
+++ b/libs/binder/trusty/include_mock/trusty_ipc.h
@@ -24,6 +24,9 @@
#define INFINITE_TIME 1
#define IPC_MAX_MSG_HANDLES 8
+#define IPC_PORT_ALLOW_TA_CONNECT 0x1
+#define IPC_PORT_ALLOW_NS_CONNECT 0x2
+
#define IPC_HANDLE_POLL_HUP 0x1
#define IPC_HANDLE_POLL_MSG 0x2
#define IPC_HANDLE_POLL_SEND_UNBLOCKED 0x4
diff --git a/libs/binder/trusty/rules.mk b/libs/binder/trusty/rules.mk
index 4e5cd18..42db29a 100644
--- a/libs/binder/trusty/rules.mk
+++ b/libs/binder/trusty/rules.mk
@@ -79,6 +79,11 @@
-DBINDER_RPC_SINGLE_THREADED \
-D__ANDROID_VNDK__ \
+# libbinder has some deprecated declarations that we want to produce warnings
+# not errors
+MODULE_EXPORT_COMPILEFLAGS += \
+ -Wno-error=deprecated-declarations \
+
MODULE_LIBRARY_DEPS += \
trusty/user/base/lib/libstdc++-trusty \
trusty/user/base/lib/tipc \
diff --git a/libs/binder/trusty/usertests-inc.mk b/libs/binder/trusty/usertests-inc.mk
new file mode 100644
index 0000000..2f5a7f4
--- /dev/null
+++ b/libs/binder/trusty/usertests-inc.mk
@@ -0,0 +1,17 @@
+# Copyright (C) 2022 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.
+#
+
+TRUSTY_USER_TESTS += \
+ frameworks/native/libs/binder/trusty/binderRpcTest/service \
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index a0e75ff..a77ca04 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -42,7 +42,6 @@
namespace android {
-using gui::CallbackId;
using gui::DisplayCaptureArgs;
using gui::IDisplayEventConnection;
using gui::IRegionSamplingListener;
diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp
index 23d7d50..2b25b61 100644
--- a/libs/gui/ITransactionCompletedListener.cpp
+++ b/libs/gui/ITransactionCompletedListener.cpp
@@ -21,11 +21,22 @@
#include <optional>
#include <gui/ISurfaceComposer.h>
+#include <gui/ITransactionCompletedListener.h>
#include <gui/LayerState.h>
-#include <gui/ListenerStats.h>
#include <private/gui/ParcelUtils.h>
-namespace android::gui {
+namespace android {
+
+namespace { // Anonymous
+
+enum class Tag : uint32_t {
+ ON_TRANSACTION_COMPLETED = IBinder::FIRST_CALL_TRANSACTION,
+ ON_RELEASE_BUFFER,
+ ON_TRANSACTION_QUEUE_STALLED,
+ LAST = ON_TRANSACTION_QUEUE_STALLED,
+};
+
+} // Anonymous namespace
status_t FrameEventHistoryStats::writeToParcel(Parcel* output) const {
status_t err = output->writeUint64(frameNumber);
@@ -263,6 +274,60 @@
return listenerStats;
}
+class BpTransactionCompletedListener : public SafeBpInterface<ITransactionCompletedListener> {
+public:
+ explicit BpTransactionCompletedListener(const sp<IBinder>& impl)
+ : SafeBpInterface<ITransactionCompletedListener>(impl, "BpTransactionCompletedListener") {
+ }
+
+ ~BpTransactionCompletedListener() override;
+
+ void onTransactionCompleted(ListenerStats stats) override {
+ callRemoteAsync<decltype(&ITransactionCompletedListener::
+ onTransactionCompleted)>(Tag::ON_TRANSACTION_COMPLETED,
+ stats);
+ }
+
+ void onReleaseBuffer(ReleaseCallbackId callbackId, sp<Fence> releaseFence,
+ uint32_t currentMaxAcquiredBufferCount) override {
+ callRemoteAsync<decltype(&ITransactionCompletedListener::
+ onReleaseBuffer)>(Tag::ON_RELEASE_BUFFER, callbackId,
+ releaseFence,
+ currentMaxAcquiredBufferCount);
+ }
+
+ void onTransactionQueueStalled(const String8& reason) override {
+ callRemoteAsync<
+ decltype(&ITransactionCompletedListener::
+ onTransactionQueueStalled)>(Tag::ON_TRANSACTION_QUEUE_STALLED,
+ reason);
+ }
+};
+
+// Out-of-line virtual method definitions to trigger vtable emission in this translation unit (see
+// clang warning -Wweak-vtables)
+BpTransactionCompletedListener::~BpTransactionCompletedListener() = default;
+
+IMPLEMENT_META_INTERFACE(TransactionCompletedListener, "android.gui.ITransactionComposerListener");
+
+status_t BnTransactionCompletedListener::onTransact(uint32_t code, const Parcel& data,
+ Parcel* reply, uint32_t flags) {
+ if (code < IBinder::FIRST_CALL_TRANSACTION || code > static_cast<uint32_t>(Tag::LAST)) {
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+ auto tag = static_cast<Tag>(code);
+ switch (tag) {
+ case Tag::ON_TRANSACTION_COMPLETED:
+ return callLocalAsync(data, reply,
+ &ITransactionCompletedListener::onTransactionCompleted);
+ case Tag::ON_RELEASE_BUFFER:
+ return callLocalAsync(data, reply, &ITransactionCompletedListener::onReleaseBuffer);
+ case Tag::ON_TRANSACTION_QUEUE_STALLED:
+ return callLocalAsync(data, reply,
+ &ITransactionCompletedListener::onTransactionQueueStalled);
+ }
+}
+
ListenerCallbacks ListenerCallbacks::filter(CallbackId::Type type) const {
std::vector<CallbackId> filteredCallbackIds;
for (const auto& callbackId : callbackIds) {
@@ -301,4 +366,4 @@
const ReleaseCallbackId ReleaseCallbackId::INVALID_ID = ReleaseCallbackId(0, 0);
-}; // namespace android::gui
+}; // namespace android
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 0d1a69b..59b62fe 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -51,7 +51,6 @@
namespace android {
-using gui::CallbackId;
using gui::FocusRequest;
using gui::WindowInfoHandle;
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index a2ed8aa..92125ea 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -314,8 +314,7 @@
}
}
-binder::Status TransactionCompletedListener::onTransactionCompleted(
- const ListenerStats& listenerStats) {
+void TransactionCompletedListener::onTransactionCompleted(ListenerStats listenerStats) {
std::unordered_map<CallbackId, CallbackTranslation, CallbackIdHash> callbacksMap;
std::multimap<int32_t, sp<JankDataListener>> jankListenersMap;
{
@@ -455,10 +454,9 @@
}
}
}
- return binder::Status::ok();
}
-binder::Status TransactionCompletedListener::onTransactionQueueStalled(const std::string& reason) {
+void TransactionCompletedListener::onTransactionQueueStalled(const String8& reason) {
std::unordered_map<void*, std::function<void(const std::string&)>> callbackCopy;
{
std::scoped_lock<std::mutex> lock(mMutex);
@@ -467,7 +465,6 @@
for (auto const& it : callbackCopy) {
it.second(reason.c_str());
}
- return binder::Status::ok();
}
void TransactionCompletedListener::addQueueStallListener(
@@ -481,12 +478,9 @@
mQueueStallListeners.erase(id);
}
-binder::Status TransactionCompletedListener::onReleaseBuffer(
- const ReleaseCallbackId& callbackId,
- const std::optional<os::ParcelFileDescriptor>& releaseFenceFd,
- int32_t currentMaxAcquiredBufferCount) {
- sp<Fence> releaseFence(releaseFenceFd ? new Fence(::dup(releaseFenceFd->get()))
- : Fence::NO_FENCE);
+void TransactionCompletedListener::onReleaseBuffer(ReleaseCallbackId callbackId,
+ sp<Fence> releaseFence,
+ uint32_t currentMaxAcquiredBufferCount) {
ReleaseBufferCallback callback;
{
std::scoped_lock<std::mutex> lock(mMutex);
@@ -495,14 +489,13 @@
if (!callback) {
ALOGE("Could not call release buffer callback, buffer not found %s",
callbackId.to_string().c_str());
- return binder::Status::fromExceptionCode(binder::Status::EX_ILLEGAL_ARGUMENT);
+ return;
}
std::optional<uint32_t> optionalMaxAcquiredBufferCount =
- static_cast<uint32_t>(currentMaxAcquiredBufferCount) == UINT_MAX
+ currentMaxAcquiredBufferCount == UINT_MAX
? std::nullopt
: std::make_optional<uint32_t>(currentMaxAcquiredBufferCount);
callback(callbackId, releaseFence, optionalMaxAcquiredBufferCount);
- return binder::Status::ok();
}
ReleaseBufferCallback TransactionCompletedListener::popReleaseBufferCallbackLocked(
@@ -832,11 +825,7 @@
->mReleaseCallbackThread
.addReleaseCallback(state.bufferData->generateReleaseCallbackId(), fence);
} else {
- std::optional<os::ParcelFileDescriptor> fenceFd;
- if (fence != Fence::NO_FENCE) {
- fenceFd = os::ParcelFileDescriptor(base::unique_fd(::dup(fence->get())));
- }
- listener->onReleaseBuffer(state.bufferData->generateReleaseCallbackId(), fenceFd, UINT_MAX);
+ listener->onReleaseBuffer(state.bufferData->generateReleaseCallbackId(), fence, UINT_MAX);
}
}
@@ -2496,6 +2485,20 @@
return statusTFromBinderStatus(status);
}
+status_t SurfaceComposerClient::getHdrConversionCapabilities(
+ std::vector<gui::HdrConversionCapability>* hdrConversionCapabilities) {
+ binder::Status status = ComposerServiceAIDL::getComposerService()->getHdrConversionCapabilities(
+ hdrConversionCapabilities);
+ return statusTFromBinderStatus(status);
+}
+
+status_t SurfaceComposerClient::setHdrConversionStrategy(
+ gui::HdrConversionStrategy hdrConversionStrategy) {
+ binder::Status status = ComposerServiceAIDL::getComposerService()->setHdrConversionStrategy(
+ hdrConversionStrategy);
+ return statusTFromBinderStatus(status);
+}
+
status_t SurfaceComposerClient::setOverrideFrameRate(uid_t uid, float frameRate) {
binder::Status status =
ComposerServiceAIDL::getComposerService()->setOverrideFrameRate(uid, frameRate);
@@ -2866,11 +2869,7 @@
while (!callbackInfos.empty()) {
auto [callbackId, releaseFence] = callbackInfos.front();
- std::optional<os::ParcelFileDescriptor> fenceFd;
- if (releaseFence != Fence::NO_FENCE) {
- fenceFd = os::ParcelFileDescriptor(base::unique_fd(::dup(releaseFence->get())));
- }
- listener->onReleaseBuffer(callbackId, fenceFd, UINT_MAX);
+ listener->onReleaseBuffer(callbackId, std::move(releaseFence), UINT_MAX);
callbackInfos.pop();
}
diff --git a/libs/gui/aidl/android/gui/ReleaseCallbackId.aidl b/libs/gui/aidl/android/gui/HdrConversionCapability.aidl
similarity index 75%
rename from libs/gui/aidl/android/gui/ReleaseCallbackId.aidl
rename to libs/gui/aidl/android/gui/HdrConversionCapability.aidl
index c86de34..1bcfd38 100644
--- a/libs/gui/aidl/android/gui/ReleaseCallbackId.aidl
+++ b/libs/gui/aidl/android/gui/HdrConversionCapability.aidl
@@ -16,4 +16,10 @@
package android.gui;
-parcelable ReleaseCallbackId cpp_header "gui/ReleaseCallbackId.h";
+// TODO(b/265277221): use android.hardware.graphics.common.HdrConversionCapability.aidl
+/** @hide */
+parcelable HdrConversionCapability {
+ int sourceType;
+ int outputType;
+ boolean addsLatency;
+}
\ No newline at end of file
diff --git a/libs/gui/aidl/android/gui/ReleaseCallbackId.aidl b/libs/gui/aidl/android/gui/HdrConversionStrategy.aidl
similarity index 74%
copy from libs/gui/aidl/android/gui/ReleaseCallbackId.aidl
copy to libs/gui/aidl/android/gui/HdrConversionStrategy.aidl
index c86de34..1be74b4 100644
--- a/libs/gui/aidl/android/gui/ReleaseCallbackId.aidl
+++ b/libs/gui/aidl/android/gui/HdrConversionStrategy.aidl
@@ -16,4 +16,10 @@
package android.gui;
-parcelable ReleaseCallbackId cpp_header "gui/ReleaseCallbackId.h";
+// TODO(b/265277221): use android.hardware.graphics.common.HdrConversionStrategy.aidl
+/** @hide */
+union HdrConversionStrategy {
+ boolean passthrough = true;
+ int[] autoAllowedHdrTypes;
+ int forceHdrConversion;
+}
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index a0b613c..c08a7c6 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -30,6 +30,8 @@
import android.gui.DynamicDisplayInfo;
import android.gui.FrameEvent;
import android.gui.FrameStats;
+import android.gui.HdrConversionCapability;
+import android.gui.HdrConversionStrategy;
import android.gui.IDisplayEventConnection;
import android.gui.IFpsListener;
import android.gui.IHdrLayerInfoListener;
@@ -162,6 +164,26 @@
boolean getBootDisplayModeSupport();
/**
+ * Gets the HDR conversion capabilities of the device. The conversion capability defines whether
+ * conversion from sourceType to outputType is possible (with or without latency).
+ *
+ * Requires the ACCESS_SURFACE_FLINGER permission.
+ */
+ List<HdrConversionCapability> getHdrConversionCapabilities();
+
+ /**
+ * Sets the HDR conversion strategy of the device.
+ *
+ * Requires the ACCESS_SURFACE_FLINGER permission.
+ */
+ void setHdrConversionStrategy(in HdrConversionStrategy hdrConversionStrategy);
+
+ /**
+ * Gets whether HDR output conversion operations are supported on the device.
+ */
+ boolean getHdrOutputConversionSupport();
+
+ /**
* Switches Auto Low Latency Mode on/off on the connected display, if it is
* available. This should only be called if the display supports Auto Low
* Latency Mode as reported in #getDynamicDisplayInfo.
diff --git a/libs/gui/aidl/android/gui/ITransactionCompletedListener.aidl b/libs/gui/aidl/android/gui/ITransactionCompletedListener.aidl
deleted file mode 100644
index dde4d38..0000000
--- a/libs/gui/aidl/android/gui/ITransactionCompletedListener.aidl
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2022 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.
- */
-
-package android.gui;
-
-import android.gui.ListenerStats;
-import android.gui.ReleaseCallbackId;
-
-/** @hide */
-oneway interface ITransactionCompletedListener {
- void onTransactionCompleted(in ListenerStats stats);
-
- void onReleaseBuffer(in ReleaseCallbackId callbackId,
- in @nullable ParcelFileDescriptor releaseFenceFd,
- int currentMaxAcquiredBufferCount);
-
- void onTransactionQueueStalled(@utf8InCpp String name);
-}
diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h
index 8810e4e..685bd92 100644
--- a/libs/gui/fuzzer/libgui_fuzzer_utils.h
+++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h
@@ -91,6 +91,11 @@
MOCK_METHOD(binder::Status, setBootDisplayMode, (const sp<IBinder>&, int), (override));
MOCK_METHOD(binder::Status, clearBootDisplayMode, (const sp<IBinder>&), (override));
MOCK_METHOD(binder::Status, getBootDisplayModeSupport, (bool*), (override));
+ MOCK_METHOD(binder::Status, getHdrConversionCapabilities,
+ (std::vector<gui::HdrConversionCapability>*), (override));
+ MOCK_METHOD(binder::Status, setHdrConversionStrategy, (const gui::HdrConversionStrategy&),
+ (override));
+ MOCK_METHOD(binder::Status, getHdrOutputConversionSupport, (bool*), (override));
MOCK_METHOD(binder::Status, setAutoLowLatencyMode, (const sp<IBinder>&, bool), (override));
MOCK_METHOD(binder::Status, setGameContentType, (const sp<IBinder>&, bool), (override));
MOCK_METHOD(binder::Status, captureDisplay,
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 06a246e..045cc2a 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -23,11 +23,11 @@
#include <android/gui/IHdrLayerInfoListener.h>
#include <android/gui/IRegionSamplingListener.h>
#include <android/gui/IScreenCaptureListener.h>
-#include <android/gui/ITransactionCompletedListener.h>
#include <android/gui/ITunnelModeEnabledListener.h>
#include <android/gui/IWindowInfosListener.h>
#include <binder/IBinder.h>
#include <binder/IInterface.h>
+#include <gui/ITransactionCompletedListener.h>
#include <gui/SpHash.h>
#include <math/vec4.h>
#include <stdint.h>
@@ -66,7 +66,6 @@
using gui::IDisplayEventConnection;
using gui::IRegionSamplingListener;
using gui::IScreenCaptureListener;
-using gui::ListenerCallbacks;
using gui::SpHash;
namespace gui {
diff --git a/libs/gui/include/gui/ListenerStats.h b/libs/gui/include/gui/ITransactionCompletedListener.h
similarity index 81%
rename from libs/gui/include/gui/ListenerStats.h
rename to libs/gui/include/gui/ITransactionCompletedListener.h
index 3a12802..453e8f3 100644
--- a/libs/gui/include/gui/ListenerStats.h
+++ b/libs/gui/include/gui/ITransactionCompletedListener.h
@@ -24,8 +24,6 @@
#include <binder/SafeInterface.h>
#include <gui/FrameTimestamps.h>
-#include <gui/ReleaseCallbackId.h>
-
#include <ui/Fence.h>
#include <utils/Timers.h>
@@ -34,7 +32,10 @@
#include <unordered_set>
#include <variant>
-namespace android::gui {
+namespace android {
+
+class ITransactionCompletedListener;
+class ListenerCallbacks;
class CallbackId : public Parcelable {
public:
@@ -53,6 +54,30 @@
std::size_t operator()(const CallbackId& key) const { return std::hash<int64_t>()(key.id); }
};
+class ReleaseCallbackId : public Parcelable {
+public:
+ static const ReleaseCallbackId INVALID_ID;
+
+ uint64_t bufferId;
+ uint64_t framenumber;
+ ReleaseCallbackId() {}
+ ReleaseCallbackId(uint64_t bufferId, uint64_t framenumber)
+ : bufferId(bufferId), framenumber(framenumber) {}
+ status_t writeToParcel(Parcel* output) const override;
+ status_t readFromParcel(const Parcel* input) override;
+
+ bool operator==(const ReleaseCallbackId& rhs) const {
+ return bufferId == rhs.bufferId && framenumber == rhs.framenumber;
+ }
+ bool operator!=(const ReleaseCallbackId& rhs) const { return !operator==(rhs); }
+ std::string to_string() const {
+ if (*this == INVALID_ID) return "INVALID_ID";
+
+ return "bufferId:" + std::to_string(bufferId) +
+ " framenumber:" + std::to_string(framenumber);
+ }
+};
+
struct ReleaseBufferCallbackIdHash {
std::size_t operator()(const ReleaseCallbackId& key) const {
return std::hash<uint64_t>()(key.bufferId);
@@ -161,6 +186,27 @@
std::vector<TransactionStats> transactionStats;
};
+class ITransactionCompletedListener : public IInterface {
+public:
+ DECLARE_META_INTERFACE(TransactionCompletedListener)
+
+ virtual void onTransactionCompleted(ListenerStats stats) = 0;
+
+ virtual void onReleaseBuffer(ReleaseCallbackId callbackId, sp<Fence> releaseFence,
+ uint32_t currentMaxAcquiredBufferCount) = 0;
+
+ virtual void onTransactionQueueStalled(const String8& name) = 0;
+};
+
+class BnTransactionCompletedListener : public SafeBnInterface<ITransactionCompletedListener> {
+public:
+ BnTransactionCompletedListener()
+ : SafeBnInterface<ITransactionCompletedListener>("BnTransactionCompletedListener") {}
+
+ status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
+ uint32_t flags = 0) override;
+};
+
class ListenerCallbacks {
public:
ListenerCallbacks(const sp<IBinder>& listener,
@@ -222,4 +268,4 @@
}
};
-} // namespace android::gui
+} // namespace android
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index c5fdf82..ecde47f 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -21,10 +21,10 @@
#include <stdint.h>
#include <sys/types.h>
-#include <android/gui/ITransactionCompletedListener.h>
#include <android/gui/IWindowInfosReportedListener.h>
#include <android/native_window.h>
#include <gui/IGraphicBufferProducer.h>
+#include <gui/ITransactionCompletedListener.h>
#include <math/mat4.h>
#include <android/gui/DropInputMode.h>
@@ -35,7 +35,6 @@
#include <gui/ISurfaceComposer.h>
#include <gui/LayerCaptureArgs.h>
#include <gui/LayerMetadata.h>
-#include <gui/ReleaseCallbackId.h>
#include <gui/SpHash.h>
#include <gui/SurfaceControl.h>
#include <gui/WindowInfo.h>
@@ -57,9 +56,6 @@
using gui::ISurfaceComposerClient;
using gui::LayerMetadata;
-using gui::ITransactionCompletedListener;
-using gui::ReleaseCallbackId;
-
struct client_cache_t {
wp<IBinder> token = nullptr;
uint64_t id;
@@ -210,26 +206,46 @@
uint64_t diff(const layer_state_t& other) const;
bool hasBufferChanges() const;
- // Changes to the tree structure.
- static constexpr uint64_t HIERARCHY_CHANGES = layer_state_t::eLayerChanged |
- layer_state_t::eRelativeLayerChanged | layer_state_t::eReparent |
- layer_state_t::eBackgroundColorChanged;
+ // Layer hierarchy updates.
+ static constexpr uint64_t HIERARCHY_CHANGES = layer_state_t::eBackgroundColorChanged |
+ layer_state_t::eLayerChanged | layer_state_t::eRelativeLayerChanged |
+ layer_state_t::eReparent;
+
+ // Geometry updates.
+ static constexpr uint64_t GEOMETRY_CHANGES = layer_state_t::eBufferCropChanged |
+ layer_state_t::eBufferTransformChanged | layer_state_t::eCropChanged |
+ layer_state_t::eDestinationFrameChanged | layer_state_t::eMatrixChanged |
+ layer_state_t::ePositionChanged | layer_state_t::eTransformToDisplayInverseChanged |
+ layer_state_t::eTransparentRegionChanged;
+
+ // Buffer and related updates.
+ static constexpr uint64_t BUFFER_CHANGES = layer_state_t::eApiChanged |
+ layer_state_t::eBufferChanged | layer_state_t::eBufferCropChanged |
+ layer_state_t::eBufferTransformChanged | layer_state_t::eDataspaceChanged |
+ layer_state_t::eSidebandStreamChanged | layer_state_t::eSurfaceDamageRegionChanged |
+ layer_state_t::eTransformToDisplayInverseChanged |
+ layer_state_t::eTransparentRegionChanged;
+
// Content updates.
- static constexpr uint64_t CONTENT_CHANGES = layer_state_t::eAlphaChanged |
- layer_state_t::eTransparentRegionChanged | layer_state_t::eShadowRadiusChanged |
- layer_state_t::eRenderBorderChanged | layer_state_t::eColorChanged |
- layer_state_t::eBufferChanged | layer_state_t::eDataspaceChanged |
- layer_state_t::eApiChanged | layer_state_t::eSidebandStreamChanged |
+ static constexpr uint64_t CONTENT_CHANGES = layer_state_t::BUFFER_CHANGES |
+ layer_state_t::eAlphaChanged | layer_state_t::eAutoRefreshChanged |
+ layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBackgroundColorChanged |
+ layer_state_t::eBlurRegionsChanged | layer_state_t::eColorChanged |
+ layer_state_t::eColorSpaceAgnosticChanged | layer_state_t::eColorTransformChanged |
+ layer_state_t::eCornerRadiusChanged | layer_state_t::eHdrMetadataChanged |
+ layer_state_t::eRenderBorderChanged | layer_state_t::eShadowRadiusChanged |
+ layer_state_t::eStretchChanged;
+
+ // Changes which invalidates the layer's visible region in CE.
+ static constexpr uint64_t CONTENT_DIRTY = layer_state_t::CONTENT_CHANGES |
+ layer_state_t::GEOMETRY_CHANGES | layer_state_t::HIERARCHY_CHANGES;
+
+ // 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::eColorTransformChanged | layer_state_t::eCornerRadiusChanged |
- layer_state_t::eBackgroundColorChanged | layer_state_t::eColorSpaceAgnosticChanged |
- layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBlurRegionsChanged |
- layer_state_t::eAutoRefreshChanged | layer_state_t::eStretchChanged;
- // Changes to content or children size.
- static constexpr uint64_t GEOMETRY_CHANGES = layer_state_t::ePositionChanged |
- layer_state_t::eMatrixChanged | layer_state_t::eTransparentRegionChanged |
- layer_state_t::eBufferCropChanged | layer_state_t::eBufferTransformChanged |
- layer_state_t::eTransformToDisplayInverseChanged | layer_state_t::eCropChanged |
- layer_state_t::eDestinationFrameChanged;
+ layer_state_t::eFlagsChanged | layer_state_t::eLayerStackChanged |
+ layer_state_t::eTrustedOverlayChanged;
bool hasValidBuffer() const;
void sanitize(int32_t permissions);
diff --git a/libs/gui/include/gui/ReleaseCallbackId.h b/libs/gui/include/gui/ReleaseCallbackId.h
deleted file mode 100644
index 142ee5a..0000000
--- a/libs/gui/include/gui/ReleaseCallbackId.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <binder/Parcel.h>
-#include <binder/Parcelable.h>
-
-#include <cstdint>
-
-namespace android::gui {
-
-class ReleaseCallbackId : public Parcelable {
-public:
- static const ReleaseCallbackId INVALID_ID;
-
- uint64_t bufferId;
- uint64_t framenumber;
- ReleaseCallbackId() {}
- ReleaseCallbackId(uint64_t bufferId, uint64_t framenumber)
- : bufferId(bufferId), framenumber(framenumber) {}
- status_t writeToParcel(Parcel* output) const override;
- status_t readFromParcel(const Parcel* input) override;
-
- bool operator==(const ReleaseCallbackId& rhs) const {
- return bufferId == rhs.bufferId && framenumber == rhs.framenumber;
- }
- bool operator!=(const ReleaseCallbackId& rhs) const { return !operator==(rhs); }
- std::string to_string() const {
- if (*this == INVALID_ID) return "INVALID_ID";
-
- return "bufferId:" + std::to_string(bufferId) +
- " framenumber:" + std::to_string(framenumber);
- }
-};
-
-} // namespace android::gui
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index cc459c5..2458a40 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -42,13 +42,10 @@
#include <android/gui/ISurfaceComposerClient.h>
-#include <android/gui/BnTransactionCompletedListener.h>
-
#include <gui/CpuConsumer.h>
#include <gui/ISurfaceComposer.h>
+#include <gui/ITransactionCompletedListener.h>
#include <gui/LayerState.h>
-#include <gui/ListenerStats.h>
-#include <gui/ReleaseCallbackId.h>
#include <gui/SurfaceControl.h>
#include <gui/WindowInfosListenerReporter.h>
#include <math/vec3.h>
@@ -62,21 +59,11 @@
class ITunnelModeEnabledListener;
class Region;
-using gui::BnTransactionCompletedListener;
-using gui::CallbackId;
-using gui::CallbackIdHash;
using gui::DisplayCaptureArgs;
-using gui::FrameEventHistoryStats;
using gui::IRegionSamplingListener;
using gui::ISurfaceComposerClient;
-using gui::ITransactionCompletedListener;
-using gui::JankData;
using gui::LayerCaptureArgs;
using gui::LayerMetadata;
-using gui::ListenerStats;
-using gui::ReleaseBufferCallbackIdHash;
-using gui::ReleaseCallbackId;
-using gui::SurfaceStats;
struct SurfaceControlStats {
SurfaceControlStats(const sp<SurfaceControl>& sc, nsecs_t latchTime,
@@ -200,6 +187,11 @@
// Clears the user-preferred display mode
static status_t clearBootDisplayMode(const sp<IBinder>& display);
+ // Gets the HDR conversion capabilities of the device
+ static status_t getHdrConversionCapabilities(std::vector<gui::HdrConversionCapability>*);
+ // Sets the HDR conversion strategy for the device
+ static status_t setHdrConversionStrategy(gui::HdrConversionStrategy hdrConversionStrategy);
+
// Sets the frame rate of a particular app (uid). This is currently called
// by GameManager.
static status_t setOverrideFrameRate(uid_t uid, float frameRate);
@@ -841,17 +833,17 @@
void setReleaseBufferCallback(const ReleaseCallbackId&, ReleaseBufferCallback);
// BnTransactionCompletedListener overrides
- binder::Status onTransactionCompleted(const ListenerStats& stats) override;
- binder::Status onReleaseBuffer(const ReleaseCallbackId& callbackId,
- const std::optional<os::ParcelFileDescriptor>& releaseFenceFd,
- int32_t currentMaxAcquiredBufferCount) override;
- binder::Status onTransactionQueueStalled(const std::string& reason) override;
+ void onTransactionCompleted(ListenerStats stats) override;
+ void onReleaseBuffer(ReleaseCallbackId, sp<Fence> releaseFence,
+ uint32_t currentMaxAcquiredBufferCount) override;
void removeReleaseBufferCallback(const ReleaseCallbackId& callbackId);
// For Testing Only
static void setInstance(const sp<TransactionCompletedListener>&);
+ void onTransactionQueueStalled(const String8& reason) override;
+
private:
ReleaseBufferCallback popReleaseBufferCallbackLocked(const ReleaseCallbackId&);
static sp<TransactionCompletedListener> sInstance;
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 55242df..3014804 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -819,6 +819,20 @@
return binder::Status::ok();
}
+ binder::Status getHdrConversionCapabilities(
+ std::vector<gui::HdrConversionCapability>*) override {
+ return binder::Status::ok();
+ }
+
+ binder::Status setHdrConversionStrategy(
+ const gui::HdrConversionStrategy& /*hdrConversionStrategy*/) override {
+ return binder::Status::ok();
+ }
+
+ binder::Status getHdrOutputConversionSupport(bool* /*outSupport*/) override {
+ return binder::Status::ok();
+ }
+
binder::Status setAutoLowLatencyMode(const sp<IBinder>& /*display*/, bool /*on*/) override {
return binder::Status::ok();
}
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index 87333f2..9c7c0c1 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -22,6 +22,7 @@
#include <android-base/stringprintf.h>
#include <ftl/enum.h>
+#include <gui/constants.h>
#include <input/InputDevice.h>
#include <input/InputEventLabels.h>
@@ -166,7 +167,7 @@
// --- InputDeviceInfo ---
InputDeviceInfo::InputDeviceInfo() {
- initialize(-1, 0, -1, InputDeviceIdentifier(), "", false, false);
+ initialize(-1, 0, -1, InputDeviceIdentifier(), "", false, false, ADISPLAY_ID_NONE);
}
InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other)
@@ -181,7 +182,8 @@
mSources(other.mSources),
mKeyboardType(other.mKeyboardType),
mKeyCharacterMap(other.mKeyCharacterMap),
- mSupportsUsi(other.mSupportsUsi),
+ mUsiVersion(other.mUsiVersion),
+ mAssociatedDisplayId(other.mAssociatedDisplayId),
mHasVibrator(other.mHasVibrator),
mHasBattery(other.mHasBattery),
mHasButtonUnderPad(other.mHasButtonUnderPad),
@@ -195,7 +197,7 @@
void InputDeviceInfo::initialize(int32_t id, int32_t generation, int32_t controllerNumber,
const InputDeviceIdentifier& identifier, const std::string& alias,
- bool isExternal, bool hasMic) {
+ bool isExternal, bool hasMic, int32_t associatedDisplayId) {
mId = id;
mGeneration = generation;
mControllerNumber = controllerNumber;
@@ -205,11 +207,12 @@
mHasMic = hasMic;
mSources = 0;
mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
+ mAssociatedDisplayId = associatedDisplayId;
mHasVibrator = false;
mHasBattery = false;
mHasButtonUnderPad = false;
mHasSensor = false;
- mSupportsUsi = false;
+ mUsiVersion.reset();
mMotionRanges.clear();
mSensors.clear();
mLights.clear();
diff --git a/libs/input/VelocityControl.cpp b/libs/input/VelocityControl.cpp
index 5c008b1..5720099 100644
--- a/libs/input/VelocityControl.cpp
+++ b/libs/input/VelocityControl.cpp
@@ -70,9 +70,10 @@
if (deltaY) {
mRawPositionY += *deltaY;
}
- mVelocityTracker.addMovement(eventTime, BitSet32(BitSet32::valueForBit(0)),
- {{AMOTION_EVENT_AXIS_X, {mRawPositionX}},
- {AMOTION_EVENT_AXIS_Y, {mRawPositionY}}});
+ mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_X,
+ mRawPositionX);
+ mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_Y,
+ mRawPositionY);
std::optional<float> vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0);
std::optional<float> vy = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, 0);
diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp
index 19b4684..3632914 100644
--- a/libs/input/VelocityTracker.cpp
+++ b/libs/input/VelocityTracker.cpp
@@ -23,6 +23,7 @@
#include <optional>
#include <android-base/stringprintf.h>
+#include <input/PrintTools.h>
#include <input/VelocityTracker.h>
#include <utils/BitSet.h>
#include <utils/Timers.h>
@@ -142,10 +143,7 @@
// --- VelocityTracker ---
VelocityTracker::VelocityTracker(const Strategy strategy)
- : mLastEventTime(0),
- mCurrentPointerIdBits(0),
- mActivePointerId(-1),
- mOverrideStrategy(strategy) {}
+ : mLastEventTime(0), mCurrentPointerIdBits(0), mOverrideStrategy(strategy) {}
VelocityTracker::~VelocityTracker() {
}
@@ -191,17 +189,17 @@
return std::make_unique<
LeastSquaresVelocityTrackerStrategy>(2,
LeastSquaresVelocityTrackerStrategy::
- WEIGHTING_DELTA);
+ Weighting::DELTA);
case VelocityTracker::Strategy::WLSQ2_CENTRAL:
return std::make_unique<
LeastSquaresVelocityTrackerStrategy>(2,
LeastSquaresVelocityTrackerStrategy::
- WEIGHTING_CENTRAL);
+ Weighting::CENTRAL);
case VelocityTracker::Strategy::WLSQ2_RECENT:
return std::make_unique<
LeastSquaresVelocityTrackerStrategy>(2,
LeastSquaresVelocityTrackerStrategy::
- WEIGHTING_RECENT);
+ Weighting::RECENT);
case VelocityTracker::Strategy::INT1:
return std::make_unique<IntegratingVelocityTrackerStrategy>(1);
@@ -220,30 +218,30 @@
void VelocityTracker::clear() {
mCurrentPointerIdBits.clear();
- mActivePointerId = -1;
+ mActivePointerId = std::nullopt;
mConfiguredStrategies.clear();
}
-void VelocityTracker::clearPointers(BitSet32 idBits) {
- BitSet32 remainingIdBits(mCurrentPointerIdBits.value & ~idBits.value);
- mCurrentPointerIdBits = remainingIdBits;
+void VelocityTracker::clearPointer(int32_t pointerId) {
+ mCurrentPointerIdBits.clearBit(pointerId);
- if (mActivePointerId >= 0 && idBits.hasBit(mActivePointerId)) {
- mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1;
+ if (mActivePointerId && *mActivePointerId == pointerId) {
+ // The active pointer id is being removed. Mark it invalid and try to find a new one
+ // from the remaining pointers.
+ mActivePointerId = std::nullopt;
+ if (!mCurrentPointerIdBits.isEmpty()) {
+ mActivePointerId = mCurrentPointerIdBits.firstMarkedBit();
+ }
}
for (const auto& [_, strategy] : mConfiguredStrategies) {
- strategy->clearPointers(idBits);
+ strategy->clearPointer(pointerId);
}
}
-void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits,
- const std::map<int32_t /*axis*/, std::vector<float>>& positions) {
- while (idBits.count() > MAX_POINTERS) {
- idBits.clearLastMarkedBit();
- }
-
- if ((mCurrentPointerIdBits.value & idBits.value) &&
+void VelocityTracker::addMovement(nsecs_t eventTime, int32_t pointerId, int32_t axis,
+ float position) {
+ if (mCurrentPointerIdBits.hasBit(pointerId) &&
std::chrono::nanoseconds(eventTime - mLastEventTime) > ASSUME_POINTER_STOPPED_TIME) {
ALOGD_IF(DEBUG_VELOCITY, "VelocityTracker: stopped for %s, clearing state.",
toString(std::chrono::nanoseconds(eventTime - mLastEventTime)).c_str());
@@ -254,39 +252,28 @@
}
mLastEventTime = eventTime;
- mCurrentPointerIdBits = idBits;
- if (mActivePointerId < 0 || !idBits.hasBit(mActivePointerId)) {
- mActivePointerId = idBits.isEmpty() ? -1 : idBits.firstMarkedBit();
+ mCurrentPointerIdBits.markBit(pointerId);
+ if (!mActivePointerId) {
+ // Let this be the new active pointer if no active pointer is currently set
+ mActivePointerId = pointerId;
}
- for (const auto& [axis, positionValues] : positions) {
- LOG_ALWAYS_FATAL_IF(idBits.count() != positionValues.size(),
- "Mismatching number of pointers, idBits=%" PRIu32 ", positions=%zu",
- idBits.count(), positionValues.size());
- if (mConfiguredStrategies.find(axis) == mConfiguredStrategies.end()) {
- configureStrategy(axis);
- }
- mConfiguredStrategies[axis]->addMovement(eventTime, idBits, positionValues);
+ if (mConfiguredStrategies.find(axis) == mConfiguredStrategies.end()) {
+ configureStrategy(axis);
}
+ mConfiguredStrategies[axis]->addMovement(eventTime, pointerId, position);
if (DEBUG_VELOCITY) {
- ALOGD("VelocityTracker: addMovement eventTime=%" PRId64
- ", idBits=0x%08x, activePointerId=%d",
- eventTime, idBits.value, mActivePointerId);
- for (const auto& positionsEntry : positions) {
- for (BitSet32 iterBits(idBits); !iterBits.isEmpty();) {
- uint32_t id = iterBits.firstMarkedBit();
- uint32_t index = idBits.getIndexOfBit(id);
- iterBits.clearBit(id);
- Estimator estimator;
- getEstimator(positionsEntry.first, id, &estimator);
- ALOGD(" %d: axis=%d, position=%0.3f, "
- "estimator (degree=%d, coeff=%s, confidence=%f)",
- id, positionsEntry.first, positionsEntry.second[index], int(estimator.degree),
- vectorToString(estimator.coeff, estimator.degree + 1).c_str(),
- estimator.confidence);
- }
- }
+ ALOGD("VelocityTracker: addMovement eventTime=%" PRId64 ", pointerId=%" PRId32
+ ", activePointerId=%s",
+ eventTime, pointerId, toString(mActivePointerId).c_str());
+
+ std::optional<Estimator> estimator = getEstimator(axis, pointerId);
+ ALOGD(" %d: axis=%d, position=%0.3f, "
+ "estimator (degree=%d, coeff=%s, confidence=%f)",
+ pointerId, axis, position, int((*estimator).degree),
+ vectorToString((*estimator).coeff.data(), (*estimator).degree + 1).c_str(),
+ (*estimator).confidence);
}
}
@@ -296,94 +283,75 @@
int32_t actionMasked = event->getActionMasked();
switch (actionMasked) {
- case AMOTION_EVENT_ACTION_DOWN:
- case AMOTION_EVENT_ACTION_HOVER_ENTER:
- // Clear all pointers on down before adding the new movement.
- clear();
- axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
- break;
- case AMOTION_EVENT_ACTION_POINTER_DOWN: {
- // Start a new movement trace for a pointer that just went down.
- // We do this on down instead of on up because the client may want to query the
- // final velocity for a pointer that just went up.
- BitSet32 downIdBits;
- downIdBits.markBit(event->getPointerId(event->getActionIndex()));
- clearPointers(downIdBits);
- axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
- break;
- }
- case AMOTION_EVENT_ACTION_MOVE:
- case AMOTION_EVENT_ACTION_HOVER_MOVE:
- axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
- break;
- case AMOTION_EVENT_ACTION_POINTER_UP:
- case AMOTION_EVENT_ACTION_UP: {
- std::chrono::nanoseconds delaySinceLastEvent(event->getEventTime() - mLastEventTime);
- if (delaySinceLastEvent > ASSUME_POINTER_STOPPED_TIME) {
- ALOGD_IF(DEBUG_VELOCITY,
- "VelocityTracker: stopped for %s, clearing state upon pointer liftoff.",
- toString(delaySinceLastEvent).c_str());
- // We have not received any movements for too long. Assume that all pointers
- // have stopped.
- for (int32_t axis : PLANAR_AXES) {
- mConfiguredStrategies.erase(axis);
- }
+ case AMOTION_EVENT_ACTION_DOWN:
+ case AMOTION_EVENT_ACTION_HOVER_ENTER:
+ // Clear all pointers on down before adding the new movement.
+ clear();
+ axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
+ break;
+ case AMOTION_EVENT_ACTION_POINTER_DOWN: {
+ // Start a new movement trace for a pointer that just went down.
+ // We do this on down instead of on up because the client may want to query the
+ // final velocity for a pointer that just went up.
+ clearPointer(event->getPointerId(event->getActionIndex()));
+ axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
+ break;
}
- // These actions because they do not convey any new information about
- // pointer movement. We also want to preserve the last known velocity of the pointers.
- // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position
- // of the pointers that went up. ACTION_POINTER_UP does include the new position of
- // pointers that remained down but we will also receive an ACTION_MOVE with this
- // information if any of them actually moved. Since we don't know how many pointers
- // will be going up at once it makes sense to just wait for the following ACTION_MOVE
- // before adding the movement.
- return;
- }
- case AMOTION_EVENT_ACTION_SCROLL:
- axesToProcess.insert(AMOTION_EVENT_AXIS_SCROLL);
- break;
- default:
- // Ignore all other actions.
- return;
+ case AMOTION_EVENT_ACTION_MOVE:
+ case AMOTION_EVENT_ACTION_HOVER_MOVE:
+ axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
+ break;
+ case AMOTION_EVENT_ACTION_POINTER_UP:
+ case AMOTION_EVENT_ACTION_UP: {
+ std::chrono::nanoseconds delaySinceLastEvent(event->getEventTime() - mLastEventTime);
+ if (delaySinceLastEvent > ASSUME_POINTER_STOPPED_TIME) {
+ ALOGD_IF(DEBUG_VELOCITY,
+ "VelocityTracker: stopped for %s, clearing state upon pointer liftoff.",
+ toString(delaySinceLastEvent).c_str());
+ // We have not received any movements for too long. Assume that all pointers
+ // have stopped.
+ for (int32_t axis : PLANAR_AXES) {
+ mConfiguredStrategies.erase(axis);
+ }
+ }
+ // These actions because they do not convey any new information about
+ // pointer movement. We also want to preserve the last known velocity of the pointers.
+ // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position
+ // of the pointers that went up. ACTION_POINTER_UP does include the new position of
+ // pointers that remained down but we will also receive an ACTION_MOVE with this
+ // information if any of them actually moved. Since we don't know how many pointers
+ // will be going up at once it makes sense to just wait for the following ACTION_MOVE
+ // before adding the movement.
+ return;
+ }
+ case AMOTION_EVENT_ACTION_SCROLL:
+ axesToProcess.insert(AMOTION_EVENT_AXIS_SCROLL);
+ break;
+ default:
+ // Ignore all other actions.
+ return;
}
- size_t pointerCount = event->getPointerCount();
- if (pointerCount > MAX_POINTERS) {
- pointerCount = MAX_POINTERS;
- }
-
- BitSet32 idBits;
- for (size_t i = 0; i < pointerCount; i++) {
- idBits.markBit(event->getPointerId(i));
- }
-
- uint32_t pointerIndex[MAX_POINTERS];
- for (size_t i = 0; i < pointerCount; i++) {
- pointerIndex[i] = idBits.getIndexOfBit(event->getPointerId(i));
- }
-
- std::map<int32_t, std::vector<float>> positions;
- for (int32_t axis : axesToProcess) {
- positions[axis].resize(pointerCount);
- }
-
- size_t historySize = event->getHistorySize();
+ const size_t historySize = event->getHistorySize();
for (size_t h = 0; h <= historySize; h++) {
- nsecs_t eventTime = event->getHistoricalEventTime(h);
- for (int32_t axis : axesToProcess) {
- for (size_t i = 0; i < pointerCount; i++) {
- positions[axis][pointerIndex[i]] = event->getHistoricalAxisValue(axis, i, h);
+ const nsecs_t eventTime = event->getHistoricalEventTime(h);
+ for (size_t i = 0; i < event->getPointerCount(); i++) {
+ if (event->isResampled(i, h)) {
+ continue; // skip resampled samples
+ }
+ const int32_t pointerId = event->getPointerId(i);
+ for (int32_t axis : axesToProcess) {
+ const float position = event->getHistoricalAxisValue(axis, i, h);
+ addMovement(eventTime, pointerId, axis, position);
}
}
- addMovement(eventTime, idBits, positions);
}
}
-std::optional<float> VelocityTracker::getVelocity(int32_t axis, uint32_t id) const {
- Estimator estimator;
- bool validVelocity = getEstimator(axis, id, &estimator) && estimator.degree >= 1;
- if (validVelocity) {
- return estimator.coeff[1];
+std::optional<float> VelocityTracker::getVelocity(int32_t axis, int32_t pointerId) const {
+ std::optional<Estimator> estimator = getEstimator(axis, pointerId);
+ if (estimator && (*estimator).degree >= 1) {
+ return (*estimator).coeff[1];
}
return {};
}
@@ -406,50 +374,55 @@
return computedVelocity;
}
-bool VelocityTracker::getEstimator(int32_t axis, uint32_t id, Estimator* outEstimator) const {
+std::optional<VelocityTracker::Estimator> VelocityTracker::getEstimator(int32_t axis,
+ int32_t pointerId) const {
const auto& it = mConfiguredStrategies.find(axis);
if (it == mConfiguredStrategies.end()) {
- return false;
+ return std::nullopt;
}
- return it->second->getEstimator(id, outEstimator);
+ return it->second->getEstimator(pointerId);
}
// --- LeastSquaresVelocityTrackerStrategy ---
LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(uint32_t degree,
Weighting weighting)
- : mDegree(degree), mWeighting(weighting), mIndex(0) {}
+ : mDegree(degree), mWeighting(weighting) {}
LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {
}
-void LeastSquaresVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
- BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
- mMovements[mIndex].idBits = remainingIdBits;
+void LeastSquaresVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
+ mIndex.erase(pointerId);
+ mMovements.erase(pointerId);
}
-void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
- const std::vector<float>& positions) {
- if (mMovements[mIndex].eventTime != eventTime) {
+void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
+ float position) {
+ // If data for this pointer already exists, we have a valid entry at the position of
+ // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index
+ // to the next position in the circular buffer and write the new Movement there. Otherwise,
+ // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements
+ // for this pointer and write to the first position.
+ auto [movementIt, inserted] = mMovements.insert({pointerId, {}});
+ auto [indexIt, _] = mIndex.insert({pointerId, 0});
+ size_t& index = indexIt->second;
+ if (!inserted && movementIt->second[index].eventTime != eventTime) {
// When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
// of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
// the new pointer. If the eventtimes for both events are identical, just update the data
// for this time.
// We only compare against the last value, as it is likely that addMovement is called
// in chronological order as events occur.
- mIndex++;
+ index++;
}
- if (mIndex == HISTORY_SIZE) {
- mIndex = 0;
+ if (index == HISTORY_SIZE) {
+ index = 0;
}
- Movement& movement = mMovements[mIndex];
+ Movement& movement = movementIt->second[index];
movement.eventTime = eventTime;
- movement.idBits = idBits;
- uint32_t count = idBits.count();
- for (uint32_t i = 0; i < count; i++) {
- movement.positions[i] = positions[i];
- }
+ movement.position = position;
}
/**
@@ -502,7 +475,9 @@
* http://en.wikipedia.org/wiki/Gram-Schmidt
*/
static bool solveLeastSquares(const std::vector<float>& x, const std::vector<float>& y,
- const std::vector<float>& w, uint32_t n, float* outB, float* outDet) {
+ const std::vector<float>& w, uint32_t n,
+ std::array<float, VelocityTracker::Estimator::MAX_DEGREE + 1>& outB,
+ float* outDet) {
const size_t m = x.size();
ALOGD_IF(DEBUG_STRATEGY, "solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n),
@@ -583,7 +558,7 @@
outB[i] /= r[i][i];
}
- ALOGD_IF(DEBUG_STRATEGY, " - b=%s", vectorToString(outB, n).c_str());
+ ALOGD_IF(DEBUG_STRATEGY, " - b=%s", vectorToString(outB.data(), n).c_str());
// Calculate the coefficient of determination as 1 - (SSerr / SStot) where
// SSerr is the residual sum of squares (variance of the error),
@@ -671,37 +646,47 @@
return std::make_optional(std::array<float, 3>({c, b, a}));
}
-bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id,
- VelocityTracker::Estimator* outEstimator) const {
- outEstimator->clear();
-
+std::optional<VelocityTracker::Estimator> LeastSquaresVelocityTrackerStrategy::getEstimator(
+ int32_t pointerId) const {
+ const auto movementIt = mMovements.find(pointerId);
+ if (movementIt == mMovements.end()) {
+ return std::nullopt; // no data
+ }
// Iterate over movement samples in reverse time order and collect samples.
std::vector<float> positions;
std::vector<float> w;
std::vector<float> time;
- uint32_t index = mIndex;
- const Movement& newestMovement = mMovements[mIndex];
+ uint32_t index = mIndex.at(pointerId);
+ const Movement& newestMovement = movementIt->second[index];
do {
- const Movement& movement = mMovements[index];
- if (!movement.idBits.hasBit(id)) {
- break;
- }
+ const Movement& movement = movementIt->second[index];
nsecs_t age = newestMovement.eventTime - movement.eventTime;
if (age > HORIZON) {
break;
}
-
- positions.push_back(movement.getPosition(id));
- w.push_back(chooseWeight(index));
+ if (movement.eventTime == 0 && index != 0) {
+ // All eventTime's are initialized to 0. In this fixed-width circular buffer, it's
+ // possible that not all entries are valid. We use a time=0 as a signal for those
+ // uninitialized values. If we encounter a time of 0 in a position
+ // that's > 0, it means that we hit the block where the data wasn't initialized.
+ // We still don't know whether the value at index=0, with eventTime=0 is valid.
+ // However, that's only possible when the value is by itself. So there's no hard in
+ // processing it anyways, since the velocity for a single point is zero, and this
+ // situation will only be encountered in artificial circumstances (in tests).
+ // In practice, time will never be 0.
+ break;
+ }
+ positions.push_back(movement.position);
+ w.push_back(chooseWeight(pointerId, index));
time.push_back(-age * 0.000000001f);
index = (index == 0 ? HISTORY_SIZE : index) - 1;
} while (positions.size() < HISTORY_SIZE);
const size_t m = positions.size();
if (m == 0) {
- return false; // no data
+ return std::nullopt; // no data
}
// Calculate a least squares polynomial fit.
@@ -710,112 +695,116 @@
degree = m - 1;
}
- if (degree == 2 && mWeighting == WEIGHTING_NONE) {
+ if (degree == 2 && mWeighting == Weighting::NONE) {
// Optimize unweighted, quadratic polynomial fit
std::optional<std::array<float, 3>> coeff =
solveUnweightedLeastSquaresDeg2(time, positions);
if (coeff) {
- outEstimator->time = newestMovement.eventTime;
- outEstimator->degree = 2;
- outEstimator->confidence = 1;
- for (size_t i = 0; i <= outEstimator->degree; i++) {
- outEstimator->coeff[i] = (*coeff)[i];
+ VelocityTracker::Estimator estimator;
+ estimator.time = newestMovement.eventTime;
+ estimator.degree = 2;
+ estimator.confidence = 1;
+ for (size_t i = 0; i <= estimator.degree; i++) {
+ estimator.coeff[i] = (*coeff)[i];
}
- return true;
+ return estimator;
}
} else if (degree >= 1) {
// General case for an Nth degree polynomial fit
float det;
uint32_t n = degree + 1;
- if (solveLeastSquares(time, positions, w, n, outEstimator->coeff, &det)) {
- outEstimator->time = newestMovement.eventTime;
- outEstimator->degree = degree;
- outEstimator->confidence = det;
+ VelocityTracker::Estimator estimator;
+ if (solveLeastSquares(time, positions, w, n, estimator.coeff, &det)) {
+ estimator.time = newestMovement.eventTime;
+ estimator.degree = degree;
+ estimator.confidence = det;
ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, coeff=%s, confidence=%f",
- int(outEstimator->degree), vectorToString(outEstimator->coeff, n).c_str(),
- outEstimator->confidence);
+ int(estimator.degree), vectorToString(estimator.coeff.data(), n).c_str(),
+ estimator.confidence);
- return true;
+ return estimator;
}
}
// No velocity data available for this pointer, but we do have its current position.
- outEstimator->coeff[0] = positions[0];
- outEstimator->time = newestMovement.eventTime;
- outEstimator->degree = 0;
- outEstimator->confidence = 1;
- return true;
+ VelocityTracker::Estimator estimator;
+ estimator.coeff[0] = positions[0];
+ estimator.time = newestMovement.eventTime;
+ estimator.degree = 0;
+ estimator.confidence = 1;
+ return estimator;
}
-float LeastSquaresVelocityTrackerStrategy::chooseWeight(uint32_t index) const {
+float LeastSquaresVelocityTrackerStrategy::chooseWeight(int32_t pointerId, uint32_t index) const {
+ const std::array<Movement, HISTORY_SIZE>& movements = mMovements.at(pointerId);
switch (mWeighting) {
- case WEIGHTING_DELTA: {
- // Weight points based on how much time elapsed between them and the next
- // point so that points that "cover" a shorter time span are weighed less.
- // delta 0ms: 0.5
- // delta 10ms: 1.0
- if (index == mIndex) {
+ case Weighting::DELTA: {
+ // Weight points based on how much time elapsed between them and the next
+ // point so that points that "cover" a shorter time span are weighed less.
+ // delta 0ms: 0.5
+ // delta 10ms: 1.0
+ if (index == mIndex.at(pointerId)) {
+ return 1.0f;
+ }
+ uint32_t nextIndex = (index + 1) % HISTORY_SIZE;
+ float deltaMillis =
+ (movements[nextIndex].eventTime - movements[index].eventTime) * 0.000001f;
+ if (deltaMillis < 0) {
+ return 0.5f;
+ }
+ if (deltaMillis < 10) {
+ return 0.5f + deltaMillis * 0.05;
+ }
return 1.0f;
}
- uint32_t nextIndex = (index + 1) % HISTORY_SIZE;
- float deltaMillis = (mMovements[nextIndex].eventTime- mMovements[index].eventTime)
- * 0.000001f;
- if (deltaMillis < 0) {
+
+ case Weighting::CENTRAL: {
+ // Weight points based on their age, weighing very recent and very old points less.
+ // age 0ms: 0.5
+ // age 10ms: 1.0
+ // age 50ms: 1.0
+ // age 60ms: 0.5
+ float ageMillis =
+ (movements[mIndex.at(pointerId)].eventTime - movements[index].eventTime) *
+ 0.000001f;
+ if (ageMillis < 0) {
+ return 0.5f;
+ }
+ if (ageMillis < 10) {
+ return 0.5f + ageMillis * 0.05;
+ }
+ if (ageMillis < 50) {
+ return 1.0f;
+ }
+ if (ageMillis < 60) {
+ return 0.5f + (60 - ageMillis) * 0.05;
+ }
return 0.5f;
}
- if (deltaMillis < 10) {
- return 0.5f + deltaMillis * 0.05;
- }
- return 1.0f;
- }
- case WEIGHTING_CENTRAL: {
- // Weight points based on their age, weighing very recent and very old points less.
- // age 0ms: 0.5
- // age 10ms: 1.0
- // age 50ms: 1.0
- // age 60ms: 0.5
- float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime)
- * 0.000001f;
- if (ageMillis < 0) {
+ case Weighting::RECENT: {
+ // Weight points based on their age, weighing older points less.
+ // age 0ms: 1.0
+ // age 50ms: 1.0
+ // age 100ms: 0.5
+ float ageMillis =
+ (movements[mIndex.at(pointerId)].eventTime - movements[index].eventTime) *
+ 0.000001f;
+ if (ageMillis < 50) {
+ return 1.0f;
+ }
+ if (ageMillis < 100) {
+ return 0.5f + (100 - ageMillis) * 0.01f;
+ }
return 0.5f;
}
- if (ageMillis < 10) {
- return 0.5f + ageMillis * 0.05;
- }
- if (ageMillis < 50) {
- return 1.0f;
- }
- if (ageMillis < 60) {
- return 0.5f + (60 - ageMillis) * 0.05;
- }
- return 0.5f;
- }
- case WEIGHTING_RECENT: {
- // Weight points based on their age, weighing older points less.
- // age 0ms: 1.0
- // age 50ms: 1.0
- // age 100ms: 0.5
- float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime)
- * 0.000001f;
- if (ageMillis < 50) {
+ case Weighting::NONE:
return 1.0f;
- }
- if (ageMillis < 100) {
- return 0.5f + (100 - ageMillis) * 0.01f;
- }
- return 0.5f;
- }
-
- case WEIGHTING_NONE:
- default:
- return 1.0f;
}
}
-
// --- IntegratingVelocityTrackerStrategy ---
IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(uint32_t degree) :
@@ -825,38 +814,32 @@
IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() {
}
-void IntegratingVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
- mPointerIdBits.value &= ~idBits.value;
+void IntegratingVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
+ mPointerIdBits.clearBit(pointerId);
}
-void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
- const std::vector<float>& positions) {
- uint32_t index = 0;
- for (BitSet32 iterIdBits(idBits); !iterIdBits.isEmpty();) {
- uint32_t id = iterIdBits.clearFirstMarkedBit();
- State& state = mPointerState[id];
- const float position = positions[index++];
- if (mPointerIdBits.hasBit(id)) {
- updateState(state, eventTime, position);
- } else {
- initState(state, eventTime, position);
- }
+void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
+ float position) {
+ State& state = mPointerState[pointerId];
+ if (mPointerIdBits.hasBit(pointerId)) {
+ updateState(state, eventTime, position);
+ } else {
+ initState(state, eventTime, position);
}
- mPointerIdBits = idBits;
+ mPointerIdBits.markBit(pointerId);
}
-bool IntegratingVelocityTrackerStrategy::getEstimator(uint32_t id,
- VelocityTracker::Estimator* outEstimator) const {
- outEstimator->clear();
-
- if (mPointerIdBits.hasBit(id)) {
- const State& state = mPointerState[id];
- populateEstimator(state, outEstimator);
- return true;
+std::optional<VelocityTracker::Estimator> IntegratingVelocityTrackerStrategy::getEstimator(
+ int32_t pointerId) const {
+ if (mPointerIdBits.hasBit(pointerId)) {
+ const State& state = mPointerState[pointerId];
+ VelocityTracker::Estimator estimator;
+ populateEstimator(state, &estimator);
+ return estimator;
}
- return false;
+ return std::nullopt;
}
void IntegratingVelocityTrackerStrategy::initState(State& state, nsecs_t eventTime,
@@ -916,49 +899,60 @@
// --- LegacyVelocityTrackerStrategy ---
-LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() : mIndex(0) {}
+LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() {}
LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() {
}
-void LegacyVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
- BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
- mMovements[mIndex].idBits = remainingIdBits;
+void LegacyVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
+ mIndex.erase(pointerId);
+ mMovements.erase(pointerId);
}
-void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
- const std::vector<float>& positions) {
- if (++mIndex == HISTORY_SIZE) {
- mIndex = 0;
+void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
+ float position) {
+ // If data for this pointer already exists, we have a valid entry at the position of
+ // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index
+ // to the next position in the circular buffer and write the new Movement there. Otherwise,
+ // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements
+ // for this pointer and write to the first position.
+ auto [movementIt, inserted] = mMovements.insert({pointerId, {}});
+ auto [indexIt, _] = mIndex.insert({pointerId, 0});
+ size_t& index = indexIt->second;
+ if (!inserted && movementIt->second[index].eventTime != eventTime) {
+ // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
+ // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
+ // the new pointer. If the eventtimes for both events are identical, just update the data
+ // for this time.
+ // We only compare against the last value, as it is likely that addMovement is called
+ // in chronological order as events occur.
+ index++;
+ }
+ if (index == HISTORY_SIZE) {
+ index = 0;
}
- Movement& movement = mMovements[mIndex];
+ Movement& movement = movementIt->second[index];
movement.eventTime = eventTime;
- movement.idBits = idBits;
- uint32_t count = idBits.count();
- for (uint32_t i = 0; i < count; i++) {
- movement.positions[i] = positions[i];
- }
+ movement.position = position;
}
-bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id,
- VelocityTracker::Estimator* outEstimator) const {
- outEstimator->clear();
-
- const Movement& newestMovement = mMovements[mIndex];
- if (!newestMovement.idBits.hasBit(id)) {
- return false; // no data
+std::optional<VelocityTracker::Estimator> LegacyVelocityTrackerStrategy::getEstimator(
+ int32_t pointerId) const {
+ const auto movementIt = mMovements.find(pointerId);
+ if (movementIt == mMovements.end()) {
+ return std::nullopt; // no data
}
+ const Movement& newestMovement = movementIt->second[mIndex.at(pointerId)];
// Find the oldest sample that contains the pointer and that is not older than HORIZON.
nsecs_t minTime = newestMovement.eventTime - HORIZON;
- uint32_t oldestIndex = mIndex;
+ uint32_t oldestIndex = mIndex.at(pointerId);
uint32_t numTouches = 1;
do {
uint32_t nextOldestIndex = (oldestIndex == 0 ? HISTORY_SIZE : oldestIndex) - 1;
- const Movement& nextOldestMovement = mMovements[nextOldestIndex];
- if (!nextOldestMovement.idBits.hasBit(id)
- || nextOldestMovement.eventTime < minTime) {
+ const Movement& nextOldestMovement = mMovements.at(pointerId)[nextOldestIndex];
+ if (nextOldestMovement.eventTime < minTime) {
break;
}
oldestIndex = nextOldestIndex;
@@ -978,22 +972,22 @@
float accumV = 0;
uint32_t index = oldestIndex;
uint32_t samplesUsed = 0;
- const Movement& oldestMovement = mMovements[oldestIndex];
- float oldestPosition = oldestMovement.getPosition(id);
+ const Movement& oldestMovement = mMovements.at(pointerId)[oldestIndex];
+ float oldestPosition = oldestMovement.position;
nsecs_t lastDuration = 0;
while (numTouches-- > 1) {
if (++index == HISTORY_SIZE) {
index = 0;
}
- const Movement& movement = mMovements[index];
+ const Movement& movement = mMovements.at(pointerId)[index];
nsecs_t duration = movement.eventTime - oldestMovement.eventTime;
// If the duration between samples is small, we may significantly overestimate
// the velocity. Consequently, we impose a minimum duration constraint on the
// samples that we include in the calculation.
if (duration >= MIN_DURATION) {
- float position = movement.getPosition(id);
+ float position = movement.position;
float scale = 1000000000.0f / duration; // one over time delta in seconds
float v = (position - oldestPosition) * scale;
accumV = (accumV * lastDuration + v * duration) / (duration + lastDuration);
@@ -1003,54 +997,59 @@
}
// Report velocity.
- float newestPosition = newestMovement.getPosition(id);
- outEstimator->time = newestMovement.eventTime;
- outEstimator->confidence = 1;
- outEstimator->coeff[0] = newestPosition;
+ float newestPosition = newestMovement.position;
+ VelocityTracker::Estimator estimator;
+ estimator.time = newestMovement.eventTime;
+ estimator.confidence = 1;
+ estimator.coeff[0] = newestPosition;
if (samplesUsed) {
- outEstimator->coeff[1] = accumV;
- outEstimator->degree = 1;
+ estimator.coeff[1] = accumV;
+ estimator.degree = 1;
} else {
- outEstimator->degree = 0;
+ estimator.degree = 0;
}
- return true;
+ return estimator;
}
// --- ImpulseVelocityTrackerStrategy ---
ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy(bool deltaValues)
- : mDeltaValues(deltaValues), mIndex(0) {}
+ : mDeltaValues(deltaValues) {}
ImpulseVelocityTrackerStrategy::~ImpulseVelocityTrackerStrategy() {
}
-void ImpulseVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
- BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
- mMovements[mIndex].idBits = remainingIdBits;
+void ImpulseVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
+ mIndex.erase(pointerId);
+ mMovements.erase(pointerId);
}
-void ImpulseVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
- const std::vector<float>& positions) {
- if (mMovements[mIndex].eventTime != eventTime) {
+void ImpulseVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
+ float position) {
+ // If data for this pointer already exists, we have a valid entry at the position of
+ // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index
+ // to the next position in the circular buffer and write the new Movement there. Otherwise,
+ // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements
+ // for this pointer and write to the first position.
+ auto [movementIt, inserted] = mMovements.insert({pointerId, {}});
+ auto [indexIt, _] = mIndex.insert({pointerId, 0});
+ size_t& index = indexIt->second;
+ if (!inserted && movementIt->second[index].eventTime != eventTime) {
// When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
// of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
// the new pointer. If the eventtimes for both events are identical, just update the data
// for this time.
// We only compare against the last value, as it is likely that addMovement is called
// in chronological order as events occur.
- mIndex++;
+ index++;
}
- if (mIndex == HISTORY_SIZE) {
- mIndex = 0;
+ if (index == HISTORY_SIZE) {
+ index = 0;
}
- Movement& movement = mMovements[mIndex];
+ Movement& movement = movementIt->second[index];
movement.eventTime = eventTime;
- movement.idBits = idBits;
- uint32_t count = idBits.count();
- for (uint32_t i = 0; i < count; i++) {
- movement.positions[i] = positions[i];
- }
+ movement.position = position;
}
/**
@@ -1178,55 +1177,61 @@
return kineticEnergyToVelocity(work);
}
-bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id,
- VelocityTracker::Estimator* outEstimator) const {
- outEstimator->clear();
+std::optional<VelocityTracker::Estimator> ImpulseVelocityTrackerStrategy::getEstimator(
+ int32_t pointerId) const {
+ const auto movementIt = mMovements.find(pointerId);
+ if (movementIt == mMovements.end()) {
+ return std::nullopt; // no data
+ }
// Iterate over movement samples in reverse time order and collect samples.
float positions[HISTORY_SIZE];
nsecs_t time[HISTORY_SIZE];
size_t m = 0; // number of points that will be used for fitting
- size_t index = mIndex;
- const Movement& newestMovement = mMovements[mIndex];
+ size_t index = mIndex.at(pointerId);
+ const Movement& newestMovement = movementIt->second[index];
do {
- const Movement& movement = mMovements[index];
- if (!movement.idBits.hasBit(id)) {
- break;
- }
+ const Movement& movement = movementIt->second[index];
nsecs_t age = newestMovement.eventTime - movement.eventTime;
if (age > HORIZON) {
break;
}
+ if (movement.eventTime == 0 && index != 0) {
+ // All eventTime's are initialized to 0. If we encounter a time of 0 in a position
+ // that's >0, it means that we hit the block where the data wasn't initialized.
+ // It's also possible that the sample at 0 would be invalid, but there's no harm in
+ // processing it, since it would be just a single point, and will only be encountered
+ // in artificial circumstances (in tests).
+ break;
+ }
- positions[m] = movement.getPosition(id);
+ positions[m] = movement.position;
time[m] = movement.eventTime;
index = (index == 0 ? HISTORY_SIZE : index) - 1;
} while (++m < HISTORY_SIZE);
if (m == 0) {
- return false; // no data
+ return std::nullopt; // no data
}
- outEstimator->coeff[0] = 0;
- outEstimator->coeff[1] = calculateImpulseVelocity(time, positions, m, mDeltaValues);
- outEstimator->coeff[2] = 0;
+ VelocityTracker::Estimator estimator;
+ estimator.coeff[0] = 0;
+ estimator.coeff[1] = calculateImpulseVelocity(time, positions, m, mDeltaValues);
+ estimator.coeff[2] = 0;
- outEstimator->time = newestMovement.eventTime;
- outEstimator->degree = 2; // similar results to 2nd degree fit
- outEstimator->confidence = 1;
+ estimator.time = newestMovement.eventTime;
+ estimator.degree = 2; // similar results to 2nd degree fit
+ estimator.confidence = 1;
- ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", outEstimator->coeff[1]);
+ ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", estimator.coeff[1]);
if (DEBUG_IMPULSE) {
// TODO(b/134179997): delete this block once the switch to 'impulse' is complete.
// Calculate the lsq2 velocity for the same inputs to allow runtime comparisons.
// X axis chosen arbitrarily for velocity comparisons.
VelocityTracker lsq2(VelocityTracker::Strategy::LSQ2);
- BitSet32 idBits;
- const uint32_t pointerId = 0;
- idBits.markBit(pointerId);
for (ssize_t i = m - 1; i >= 0; i--) {
- lsq2.addMovement(time[i], idBits, {{AMOTION_EVENT_AXIS_X, {positions[i]}}});
+ lsq2.addMovement(time[i], pointerId, AMOTION_EVENT_AXIS_X, positions[i]);
}
std::optional<float> v = lsq2.getVelocity(AMOTION_EVENT_AXIS_X, pointerId);
if (v) {
@@ -1235,7 +1240,7 @@
ALOGD("lsq2 velocity: could not compute velocity");
}
}
- return true;
+ return estimator;
}
} // namespace android
diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index 54feea2..c6ad3a2 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -84,6 +84,8 @@
float x;
float y;
+ bool isResampled = false;
+
/**
* If both values are NAN, then this is considered to be an empty entry (no pointer data).
* If only one of the values is NAN, this is still a valid entry,
@@ -203,10 +205,11 @@
coords[pointerIndex].clear();
// We are treating column positions as pointerId
- EXPECT_TRUE(entry.positions[pointerId].isValid()) <<
- "The entry at pointerId must be valid";
- coords[pointerIndex].setAxisValue(AMOTION_EVENT_AXIS_X, entry.positions[pointerId].x);
- coords[pointerIndex].setAxisValue(AMOTION_EVENT_AXIS_Y, entry.positions[pointerId].y);
+ const Position& position = entry.positions[pointerId];
+ EXPECT_TRUE(position.isValid()) << "The entry at " << pointerId << " must be valid";
+ coords[pointerIndex].setAxisValue(AMOTION_EVENT_AXIS_X, position.x);
+ coords[pointerIndex].setAxisValue(AMOTION_EVENT_AXIS_Y, position.y);
+ coords[pointerIndex].isResampled = position.isResampled;
properties[pointerIndex].id = pointerId;
properties[pointerIndex].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
@@ -288,13 +291,13 @@
for (MotionEvent event : events) {
vt.addMovement(&event);
}
- VelocityTracker::Estimator estimatorX;
- VelocityTracker::Estimator estimatorY;
- EXPECT_TRUE(vt.getEstimator(AMOTION_EVENT_AXIS_X, 0, &estimatorX));
- EXPECT_TRUE(vt.getEstimator(AMOTION_EVENT_AXIS_Y, 0, &estimatorY));
+ std::optional<VelocityTracker::Estimator> estimatorX = vt.getEstimator(AMOTION_EVENT_AXIS_X, 0);
+ std::optional<VelocityTracker::Estimator> estimatorY = vt.getEstimator(AMOTION_EVENT_AXIS_Y, 0);
+ EXPECT_TRUE(estimatorX);
+ EXPECT_TRUE(estimatorY);
for (size_t i = 0; i< coefficients.size(); i++) {
- checkCoefficient(estimatorX.coeff[i], coefficients[i]);
- checkCoefficient(estimatorY.coeff[i], coefficients[i]);
+ checkCoefficient((*estimatorX).coeff[i], coefficients[i]);
+ checkCoefficient((*estimatorY).coeff[i], coefficients[i]);
}
}
@@ -375,6 +378,44 @@
EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID + 1));
}
+/**
+ * For a single pointer, the resampled data is ignored.
+ */
+TEST_F(VelocityTrackerTest, SinglePointerResampledData) {
+ std::vector<PlanarMotionEventEntry> motions = {{10ms, {{1, 2}}},
+ {20ms, {{2, 4}}},
+ {30ms, {{3, 6}}},
+ {35ms, {{30, 60, .isResampled = true}}},
+ {40ms, {{4, 8}}}};
+
+ computeAndCheckVelocity(VelocityTracker::Strategy::DEFAULT, motions, AMOTION_EVENT_AXIS_X, 100);
+ computeAndCheckVelocity(VelocityTracker::Strategy::DEFAULT, motions, AMOTION_EVENT_AXIS_Y, 200);
+}
+
+/**
+ * For multiple pointers, the resampled data is ignored on a per-pointer basis. If a certain pointer
+ * does not have a resampled value, all of the points are used.
+ */
+TEST_F(VelocityTrackerTest, MultiPointerResampledData) {
+ std::vector<PlanarMotionEventEntry> motions = {
+ {0ms, {{0, 0}}},
+ {10ms, {{1, 0}, {1, 0}}},
+ {20ms, {{2, 0}, {2, 0}}},
+ {30ms, {{3, 0}, {3, 0}}},
+ {35ms, {{30, 0, .isResampled = true}, {30, 0}}},
+ {40ms, {{4, 0}, {4, 0}}},
+ {45ms, {{5, 0}}}, // ACTION_UP
+ };
+
+ // Sample at t=35ms breaks trend. It's marked as resampled for the first pointer, so it should
+ // be ignored, and the resulting velocity should be linear. For the second pointer, it's not
+ // resampled, so it should cause the velocity to be non-linear.
+ computeAndCheckVelocity(VelocityTracker::Strategy::DEFAULT, motions, AMOTION_EVENT_AXIS_X, 100,
+ /*pointerId=*/0);
+ computeAndCheckVelocity(VelocityTracker::Strategy::DEFAULT, motions, AMOTION_EVENT_AXIS_X, 3455,
+ /*pointerId=*/1);
+}
+
TEST_F(VelocityTrackerTest, TestGetComputedVelocity) {
std::vector<PlanarMotionEventEntry> motions = {
{235089067457000ns, {{528.00, 0}}}, {235089084684000ns, {{527.00, 0}}},
@@ -420,8 +461,7 @@
EXPECT_FALSE(vt.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID));
- VelocityTracker::Estimator estimator;
- EXPECT_FALSE(vt.getEstimator(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID, &estimator));
+ EXPECT_FALSE(vt.getEstimator(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID));
VelocityTracker::ComputedVelocity computedVelocity = vt.getComputedVelocity(1000, 1000);
for (uint32_t id = 0; id <= MAX_POINTER_ID; id++) {
@@ -432,7 +472,7 @@
EXPECT_EQ(-1, vt.getActivePointerId());
// Make sure that the clearing functions execute without an issue.
- vt.clearPointers(BitSet32(7U));
+ vt.clearPointer(7U);
vt.clear();
}
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp
index 54af7c9..01318dc 100644
--- a/libs/jpegrecoverymap/Android.bp
+++ b/libs/jpegrecoverymap/Android.bp
@@ -21,7 +21,7 @@
default_applicable_licenses: ["frameworks_native_license"],
}
-cc_library_static {
+cc_library {
name: "libjpegrecoverymap",
host_supported: true,
@@ -37,15 +37,19 @@
shared_libs: [
"libimage_io",
"libjpeg",
- "libutils",
+ "libjpegencoder",
+ "libjpegdecoder",
+ "liblog",
],
}
-cc_library_static {
+cc_library {
name: "libjpegencoder",
+ host_supported: true,
shared_libs: [
"libjpeg",
+ "liblog",
],
export_include_dirs: ["include"],
@@ -55,11 +59,13 @@
],
}
-cc_library_static {
+cc_library {
name: "libjpegdecoder",
+ host_supported: true,
shared_libs: [
"libjpeg",
+ "liblog",
],
export_include_dirs: ["include"],
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
index 6995762..699c0d3 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
@@ -38,6 +38,7 @@
ERROR_JPEGR_RESOLUTION_MISMATCH = JPEGR_IO_ERROR_BASE - 3,
ERROR_JPEGR_BUFFER_TOO_SMALL = JPEGR_IO_ERROR_BASE - 4,
ERROR_JPEGR_INVALID_COLORGAMUT = JPEGR_IO_ERROR_BASE - 5,
+ ERROR_JPEGR_INVALID_TRANS_FUNC = JPEGR_IO_ERROR_BASE - 6,
JPEGR_RUNTIME_ERROR_BASE = -20000,
ERROR_JPEGR_ENCODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 1,
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
index 3597903..ae15d24 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
@@ -30,6 +30,7 @@
// Transfer functions as defined for XMP metadata
typedef enum {
+ JPEGR_TF_UNSPECIFIED = -1,
JPEGR_TF_LINEAR = 0,
JPEGR_TF_HLG = 1,
JPEGR_TF_PQ = 2,
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
index 0fb64d3..0695bb7 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
@@ -17,12 +17,15 @@
#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
+#include <cmath>
#include <stdint.h>
#include <jpegrecoverymap/recoverymap.h>
namespace android::recoverymap {
+#define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x)
+
////////////////////////////////////////////////////////////////////////////////
// Framework
@@ -112,6 +115,76 @@
return temp /= rhs;
}
+constexpr size_t kRecoveryFactorPrecision = 10;
+constexpr size_t kRecoveryFactorNumEntries = 1 << kRecoveryFactorPrecision;
+struct RecoveryLUT {
+ RecoveryLUT(float hdrRatio) {
+ float increment = 2.0 / kRecoveryFactorNumEntries;
+ float value = -1.0f;
+ for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++, value += increment) {
+ mRecoveryTable[idx] = pow(hdrRatio, value);
+ }
+ }
+
+ ~RecoveryLUT() {
+ }
+
+ float getRecoveryFactor(float recovery) {
+ uint32_t value = static_cast<uint32_t>(((recovery + 1.0f) / 2.0f) * kRecoveryFactorNumEntries);
+ //TODO() : Remove once conversion modules have appropriate clamping in place
+ value = CLIP3(value, 0, kRecoveryFactorNumEntries - 1);
+ return mRecoveryTable[value];
+ }
+
+private:
+ float mRecoveryTable[kRecoveryFactorNumEntries];
+};
+
+struct ShepardsIDW {
+ ShepardsIDW(int mapScaleFactor) : mMapScaleFactor{mapScaleFactor} {
+ const int size = mMapScaleFactor * mMapScaleFactor * 4;
+ mWeights = new float[size];
+ mWeightsNR = new float[size];
+ mWeightsNB = new float[size];
+ mWeightsC = new float[size];
+ fillShepardsIDW(mWeights, 1, 1);
+ fillShepardsIDW(mWeightsNR, 0, 1);
+ fillShepardsIDW(mWeightsNB, 1, 0);
+ fillShepardsIDW(mWeightsC, 0, 0);
+ }
+ ~ShepardsIDW() {
+ delete[] mWeights;
+ delete[] mWeightsNR;
+ delete[] mWeightsNB;
+ delete[] mWeightsC;
+ }
+
+ int mMapScaleFactor;
+ // Image :-
+ // p00 p01 p02 p03 p04 p05 p06 p07
+ // p10 p11 p12 p13 p14 p15 p16 p17
+ // p20 p21 p22 p23 p24 p25 p26 p27
+ // p30 p31 p32 p33 p34 p35 p36 p37
+ // p40 p41 p42 p43 p44 p45 p46 p47
+ // p50 p51 p52 p53 p54 p55 p56 p57
+ // p60 p61 p62 p63 p64 p65 p66 p67
+ // p70 p71 p72 p73 p74 p75 p76 p77
+
+ // Recovery Map (for 4 scale factor) :-
+ // m00 p01
+ // m10 m11
+
+ // Recovery sample of curr 4x4, right 4x4, bottom 4x4, bottom right 4x4 are used during
+ // reconstruction. hence table weight size is 4.
+ float* mWeights;
+ // TODO: check if its ok to mWeights at places
+ float* mWeightsNR; // no right
+ float* mWeightsNB; // no bottom
+ float* mWeightsC; // no right & bottom
+
+ float euclideanDistance(float x1, float x2, float y1, float y2);
+ void fillShepardsIDW(float *weights, int incR, int incB);
+};
////////////////////////////////////////////////////////////////////////////////
// sRGB transformations
@@ -143,7 +216,8 @@
*/
float srgbInvOetf(float e_gamma);
Color srgbInvOetf(Color e_gamma);
-
+float srgbInvOetfLUT(float e_gamma);
+Color srgbInvOetfLUT(Color e_gamma);
////////////////////////////////////////////////////////////////////////////////
// Display-P3 transformations
@@ -183,6 +257,8 @@
*/
float hlgOetf(float e);
Color hlgOetf(Color e);
+float hlgOetfLUT(float e);
+Color hlgOetfLUT(Color e);
/*
* Convert from HLG to scene luminance.
@@ -191,6 +267,8 @@
*/
float hlgInvOetf(float e_gamma);
Color hlgInvOetf(Color e_gamma);
+float hlgInvOetfLUT(float e_gamma);
+Color hlgInvOetfLUT(Color e_gamma);
/*
* Convert from scene luminance to PQ.
@@ -199,6 +277,8 @@
*/
float pqOetf(float e);
Color pqOetf(Color e);
+float pqOetfLUT(float e);
+Color pqOetfLUT(Color e);
/*
* Convert from PQ to scene luminance in nits.
@@ -207,6 +287,8 @@
*/
float pqInvOetf(float e_gamma);
Color pqInvOetf(Color e_gamma);
+float pqInvOetfLUT(float e_gamma);
+Color pqInvOetfLUT(Color e_gamma);
////////////////////////////////////////////////////////////////////////////////
@@ -251,6 +333,7 @@
* value, with the given hdr ratio, to the given sdr input in the range [0, 1].
*/
Color applyRecovery(Color e, float recovery, float hdr_ratio);
+Color applyRecoveryLUT(Color e, float recovery, RecoveryLUT& recoveryLUT);
/*
* Helper for sampling from YUV 420 images.
@@ -282,7 +365,9 @@
* Sample the recovery value for the map from a given x,y coordinate on a scale
* that is map scale factor larger than the map size.
*/
-float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
+float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y);
+float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y,
+ ShepardsIDW& weightTables);
/*
* Convert from Color to RGBA1010102.
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
index e61d0c4..164ea64 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
@@ -56,8 +56,8 @@
* <rdf:Seq>
* <rdf:li>
* <GContainer:Item
- * Item:Semantic="Primary"
- * Item:Mime="image/jpeg"
+ * GContainer:ItemSemantic="Primary"
+ * GContainer:ItemMime="image/jpeg"
* RecoveryMap:Version=”1”
* RecoveryMap:RangeScalingFactor=”1.25”
* RecoveryMap:TransferFunction=”2”/>
@@ -68,9 +68,9 @@
* </rdf:li>
* <rdf:li>
* <GContainer:Item
- * Item:Semantic="RecoveryMap"
- * Item:Mime="image/jpeg"
- * Item:Length="1000"/>
+ * GContainer:ItemSemantic="RecoveryMap"
+ * GContainer:ItemMime="image/jpeg"
+ * GContainer:ItemLength="1000"/>
* </rdf:li>
* </rdf:Seq>
* </GContainer:Directory>
@@ -85,4 +85,4 @@
std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata);
}
-#endif //ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
\ No newline at end of file
+#endif //ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoder.cpp
index 0ae6a63..6fbc6b0 100644
--- a/libs/jpegrecoverymap/jpegdecoder.cpp
+++ b/libs/jpegrecoverymap/jpegdecoder.cpp
@@ -16,7 +16,7 @@
#include <jpegrecoverymap/jpegdecoder.h>
-#include <cutils/log.h>
+#include <utils/Log.h>
#include <errno.h>
#include <setjmp.h>
@@ -32,6 +32,10 @@
const std::string kXmpNameSpace = "http://ns.adobe.com/xap/1.0/";
const std::string kExifIdCode = "Exif";
+constexpr uint32_t kICCMarkerHeaderSize = 14;
+constexpr uint8_t kICCSig[] = {
+ 'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0',
+};
struct jpegr_source_mgr : jpeg_source_mgr {
jpegr_source_mgr(const uint8_t* ptr, int len);
@@ -336,8 +340,22 @@
*pWidth = cinfo.image_width;
*pHeight = cinfo.image_height;
- //TODO: Parse iccProfile
- (void)iccData;
+ if (iccData != nullptr) {
+ for (jpeg_marker_struct* marker = cinfo.marker_list; marker;
+ marker = marker->next) {
+ if (marker->marker != kAPP2Marker) {
+ continue;
+ }
+ if (marker->data_length <= kICCMarkerHeaderSize ||
+ memcmp(marker->data, kICCSig, sizeof(kICCSig)) != 0) {
+ continue;
+ }
+
+ const unsigned int len = marker->data_length - kICCMarkerHeaderSize;
+ const uint8_t *src = marker->data + kICCMarkerHeaderSize;
+ iccData->insert(iccData->end(), src, src+len);
+ }
+ }
if (exifData != nullptr) {
bool exifAppears = false;
diff --git a/libs/jpegrecoverymap/jpegencoder.cpp b/libs/jpegrecoverymap/jpegencoder.cpp
index 1997bf9..627dcdf 100644
--- a/libs/jpegrecoverymap/jpegencoder.cpp
+++ b/libs/jpegrecoverymap/jpegencoder.cpp
@@ -16,7 +16,7 @@
#include <jpegrecoverymap/jpegencoder.h>
-#include <cutils/log.h>
+#include <utils/Log.h>
#include <errno.h>
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index ee68043..eb557e5 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -25,17 +25,30 @@
#include <image_io/jpeg/jpeg_scanner.h>
#include <image_io/jpeg/jpeg_info_builder.h>
#include <image_io/base/data_segment_data_source.h>
+#include <utils/Log.h>
#include <memory>
#include <sstream>
#include <string>
#include <cmath>
+#include <condition_variable>
+#include <deque>
+#include <mutex>
+#include <thread>
+#include <unistd.h>
using namespace std;
using namespace photos_editing_formats::image_io;
namespace android::recoverymap {
+#define USE_SRGB_INVOETF_LUT 1
+#define USE_HLG_OETF_LUT 1
+#define USE_PQ_OETF_LUT 1
+#define USE_HLG_INVOETF_LUT 1
+#define USE_PQ_INVOETF_LUT 1
+#define USE_APPLY_RECOVERY_LUT 1
+
#define JPEGR_CHECK(x) \
{ \
status_t status = (x); \
@@ -49,6 +62,10 @@
// Map is quarter res / sixteenth size
static const size_t kMapDimensionScaleFactor = 4;
+// JPEG block size.
+// JPEG encoding / decoding will require 8 x 8 DCT transform.
+// Width must be 8 dividable, and height must be 2 dividable.
+static const size_t kJpegBlock = 8;
// JPEG compress quality (0 ~ 100) for recovery map
static const int kMapCompressQuality = 85;
@@ -62,6 +79,20 @@
1.0f,
};
+#define CONFIG_MULTITHREAD 1
+int GetCPUCoreCount() {
+ int cpuCoreCount = 1;
+#if CONFIG_MULTITHREAD
+#if defined(_SC_NPROCESSORS_ONLN)
+ cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
+#else
+ // _SC_NPROC_ONLN must be defined...
+ cpuCoreCount = sysconf(_SC_NPROC_ONLN);
+#endif
+#endif
+ return cpuCoreCount;
+}
+
/*
* Helper function used for writing data to destination.
*
@@ -230,6 +261,13 @@
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
+ if (uncompressed_p010_image->width % kJpegBlock != 0
+ || uncompressed_p010_image->height % 2 != 0) {
+ ALOGE("Image size can not be handled: %dx%d",
+ uncompressed_p010_image->width, uncompressed_p010_image->height);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
jpegr_metadata metadata;
metadata.version = kJpegrVersion;
metadata.transferFunction = hdr_tf;
@@ -304,6 +342,13 @@
return ERROR_JPEGR_RESOLUTION_MISMATCH;
}
+ if (uncompressed_p010_image->width % kJpegBlock != 0
+ || uncompressed_p010_image->height % 2 != 0) {
+ ALOGE("Image size can not be handled: %dx%d",
+ uncompressed_p010_image->width, uncompressed_p010_image->height);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
jpegr_metadata metadata;
metadata.version = kJpegrVersion;
metadata.transferFunction = hdr_tf;
@@ -369,6 +414,13 @@
return ERROR_JPEGR_RESOLUTION_MISMATCH;
}
+ if (uncompressed_p010_image->width % kJpegBlock != 0
+ || uncompressed_p010_image->height % 2 != 0) {
+ ALOGE("Image size can not be handled: %dx%d",
+ uncompressed_p010_image->width, uncompressed_p010_image->height);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
jpegr_metadata metadata;
metadata.version = kJpegrVersion;
metadata.transferFunction = hdr_tf;
@@ -444,6 +496,13 @@
return ERROR_JPEGR_INVALID_NULL_PTR;
}
+ if (uncompressed_p010_image->width % kJpegBlock != 0
+ || uncompressed_p010_image->height % 2 != 0) {
+ ALOGE("Image size can not be handled: %dx%d",
+ uncompressed_p010_image->width, uncompressed_p010_image->height);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
JpegDecoder jpeg_decoder;
if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
return ERROR_JPEGR_DECODE_ERROR;
@@ -626,6 +685,62 @@
return NO_ERROR;
}
+const int kJobSzInRows = 16;
+static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
+ "align job size to kMapDimensionScaleFactor");
+
+class JobQueue {
+ public:
+ bool dequeueJob(size_t& rowStart, size_t& rowEnd);
+ void enqueueJob(size_t rowStart, size_t rowEnd);
+ void markQueueForEnd();
+ void reset();
+
+ private:
+ bool mQueuedAllJobs = false;
+ std::deque<std::tuple<size_t, size_t>> mJobs;
+ std::mutex mMutex;
+ std::condition_variable mCv;
+};
+
+bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) {
+ std::unique_lock<std::mutex> lock{mMutex};
+ while (true) {
+ if (mJobs.empty()) {
+ if (mQueuedAllJobs) {
+ return false;
+ } else {
+ mCv.wait(lock);
+ }
+ } else {
+ auto it = mJobs.begin();
+ rowStart = std::get<0>(*it);
+ rowEnd = std::get<1>(*it);
+ mJobs.erase(it);
+ return true;
+ }
+ }
+ return false;
+}
+
+void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) {
+ std::unique_lock<std::mutex> lock{mMutex};
+ mJobs.push_back(std::make_tuple(rowStart, rowEnd));
+ lock.unlock();
+ mCv.notify_one();
+}
+
+void JobQueue::markQueueForEnd() {
+ std::unique_lock<std::mutex> lock{mMutex};
+ mQueuedAllJobs = true;
+}
+
+void JobQueue::reset() {
+ std::unique_lock<std::mutex> lock{mMutex};
+ mJobs.clear();
+ mQueuedAllJobs = false;
+}
+
status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
jr_uncompressed_ptr uncompressed_p010_image,
jr_metadata_ptr metadata,
@@ -651,11 +766,14 @@
size_t image_height = uncompressed_yuv_420_image->height;
size_t map_width = image_width / kMapDimensionScaleFactor;
size_t map_height = image_height / kMapDimensionScaleFactor;
+ size_t map_stride = static_cast<size_t>(
+ floor((map_width + kJpegBlock - 1) / kJpegBlock)) * kJpegBlock;
+ size_t map_height_aligned = ((map_height + 1) >> 1) << 1;
- dest->width = map_width;
- dest->height = map_height;
+ dest->width = map_stride;
+ dest->height = map_height_aligned;
dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
- dest->data = new uint8_t[map_width * map_height];
+ dest->data = new uint8_t[map_stride * map_height_aligned];
std::unique_ptr<uint8_t[]> map_data;
map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
@@ -666,13 +784,24 @@
hdrInvOetf = identityConversion;
break;
case JPEGR_TF_HLG:
+#if USE_HLG_INVOETF_LUT
+ hdrInvOetf = hlgInvOetfLUT;
+#else
hdrInvOetf = hlgInvOetf;
+#endif
hdr_white_nits = kHlgMaxNits;
break;
case JPEGR_TF_PQ:
+#if USE_PQ_INVOETF_LUT
+ hdrInvOetf = pqInvOetfLUT;
+#else
hdrInvOetf = pqInvOetf;
+#endif
hdr_white_nits = kPqMaxNits;
break;
+ case JPEGR_TF_UNSPECIFIED:
+ // Should be impossible to hit after input validation.
+ return ERROR_JPEGR_INVALID_TRANS_FUNC;
}
ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
@@ -694,22 +823,87 @@
return ERROR_JPEGR_INVALID_COLORGAMUT;
}
+ std::mutex mutex;
float hdr_y_nits_max = 0.0f;
double hdr_y_nits_avg = 0.0f;
- for (size_t y = 0; y < image_height; ++y) {
- for (size_t x = 0; x < image_width; ++x) {
- Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
- Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
- Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
- hdr_rgb = hdrGamutConversionFn(hdr_rgb);
- float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
+ const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
+ size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
+ JobQueue jobQueue;
- hdr_y_nits_avg += hdr_y_nits;
- if (hdr_y_nits > hdr_y_nits_max) {
- hdr_y_nits_max = hdr_y_nits;
+ std::function<void()> computeMetadata = [uncompressed_p010_image, hdrInvOetf,
+ hdrGamutConversionFn, luminanceFn, hdr_white_nits,
+ threads, &mutex, &hdr_y_nits_avg,
+ &hdr_y_nits_max, &jobQueue]() -> void {
+ size_t rowStart, rowEnd;
+ float hdr_y_nits_max_th = 0.0f;
+ double hdr_y_nits_avg_th = 0.0f;
+ while (jobQueue.dequeueJob(rowStart, rowEnd)) {
+ for (size_t y = rowStart; y < rowEnd; ++y) {
+ for (size_t x = 0; x < uncompressed_p010_image->width; ++x) {
+ Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
+ Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
+ Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
+ hdr_rgb = hdrGamutConversionFn(hdr_rgb);
+ float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
+
+ hdr_y_nits_avg_th += hdr_y_nits;
+ if (hdr_y_nits > hdr_y_nits_max_th) {
+ hdr_y_nits_max_th = hdr_y_nits;
+ }
+ }
}
}
+ std::unique_lock<std::mutex> lock{mutex};
+ hdr_y_nits_avg += hdr_y_nits_avg_th;
+ hdr_y_nits_max = std::max(hdr_y_nits_max, hdr_y_nits_max_th);
+ };
+
+ std::function<void()> generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image,
+ metadata, dest, hdrInvOetf, hdrGamutConversionFn,
+ luminanceFn, hdr_white_nits, &jobQueue]() -> void {
+ size_t rowStart, rowEnd;
+ while (jobQueue.dequeueJob(rowStart, rowEnd)) {
+ for (size_t y = rowStart; y < rowEnd; ++y) {
+ for (size_t x = 0; x < dest->width; ++x) {
+ Color sdr_yuv_gamma =
+ sampleYuv420(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
+ Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
+#if USE_SRGB_INVOETF_LUT
+ Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
+#else
+ Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
+#endif
+ float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
+
+ Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
+ Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
+ Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
+ hdr_rgb = hdrGamutConversionFn(hdr_rgb);
+ float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
+
+ size_t pixel_idx = x + y * dest->width;
+ reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
+ encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
+ }
+ }
+ }
+ };
+
+ std::vector<std::thread> workers;
+ for (int th = 0; th < threads - 1; th++) {
+ workers.push_back(std::thread(computeMetadata));
}
+
+ // compute metadata
+ for (size_t rowStart = 0; rowStart < image_height;) {
+ size_t rowEnd = std::min(rowStart + rowStep, image_height);
+ jobQueue.enqueueJob(rowStart, rowEnd);
+ rowStart = rowEnd;
+ }
+ jobQueue.markQueueForEnd();
+ computeMetadata();
+ std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
+ workers.clear();
hdr_y_nits_avg /= image_width * image_height;
metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits;
@@ -718,26 +912,22 @@
metadata->hdr10Metadata.maxCLL = hdr_y_nits_max;
}
- for (size_t y = 0; y < map_height; ++y) {
- for (size_t x = 0; x < map_width; ++x) {
- Color sdr_yuv_gamma = sampleYuv420(uncompressed_yuv_420_image,
- kMapDimensionScaleFactor, x, y);
- Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
- Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
- float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
-
- Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
- Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
- Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
- hdr_rgb = hdrGamutConversionFn(hdr_rgb);
- float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
-
- size_t pixel_idx = x + y * map_width;
- reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
- encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
- }
+ // generate map
+ jobQueue.reset();
+ for (int th = 0; th < threads - 1; th++) {
+ workers.push_back(std::thread(generateMap));
}
+ rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
+ for (size_t rowStart = 0; rowStart < map_height;) {
+ size_t rowEnd = std::min(rowStart + rowStep, map_height);
+ jobQueue.enqueueJob(rowStart, rowEnd);
+ rowStart = rowEnd;
+ }
+ jobQueue.markQueueForEnd();
+ generateMap();
+ std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
+
map_data.release();
return NO_ERROR;
}
@@ -753,43 +943,95 @@
return ERROR_JPEGR_INVALID_NULL_PTR;
}
- size_t width = uncompressed_yuv_420_image->width;
- size_t height = uncompressed_yuv_420_image->height;
+ dest->width = uncompressed_yuv_420_image->width;
+ dest->height = uncompressed_yuv_420_image->height;
+ ShepardsIDW idwTable(kMapDimensionScaleFactor);
+ RecoveryLUT recoveryLUT(metadata->rangeScalingFactor);
- dest->width = width;
- dest->height = height;
- size_t pixel_count = width * height;
+ JobQueue jobQueue;
+ std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_recovery_map,
+ metadata, dest, &jobQueue, &idwTable,
+ &recoveryLUT]() -> void {
+ const float hdr_ratio = metadata->rangeScalingFactor;
+ size_t width = uncompressed_yuv_420_image->width;
+ size_t height = uncompressed_yuv_420_image->height;
- ColorTransformFn hdrOetf = nullptr;
- switch (metadata->transferFunction) {
- case JPEGR_TF_LINEAR:
- hdrOetf = identityConversion;
- break;
- case JPEGR_TF_HLG:
- hdrOetf = hlgOetf;
- break;
- case JPEGR_TF_PQ:
- hdrOetf = pqOetf;
- break;
- }
-
- for (size_t y = 0; y < height; ++y) {
- for (size_t x = 0; x < width; ++x) {
- Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
- Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
- Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
-
- // TODO: determine map scaling factor based on actual map dims
- float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y);
- Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata->rangeScalingFactor);
-
- Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->rangeScalingFactor);
- uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
-
- size_t pixel_idx = x + y * width;
- reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
+ ColorTransformFn hdrOetf = nullptr;
+ switch (metadata->transferFunction) {
+ case JPEGR_TF_LINEAR:
+ hdrOetf = identityConversion;
+ break;
+ case JPEGR_TF_HLG:
+#if USE_HLG_OETF_LUT
+ hdrOetf = hlgOetfLUT;
+#else
+ hdrOetf = hlgOetf;
+#endif
+ break;
+ case JPEGR_TF_PQ:
+#if USE_PQ_OETF_LUT
+ hdrOetf = pqOetfLUT;
+#else
+ hdrOetf = pqOetf;
+#endif
+ break;
+ case JPEGR_TF_UNSPECIFIED:
+ // Should be impossible to hit after input validation.
+ hdrOetf = identityConversion;
}
+
+ size_t rowStart, rowEnd;
+ while (jobQueue.dequeueJob(rowStart, rowEnd)) {
+ for (size_t y = rowStart; y < rowEnd; ++y) {
+ for (size_t x = 0; x < width; ++x) {
+ Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
+ Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
+#if USE_SRGB_INVOETF_LUT
+ Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
+#else
+ Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
+#endif
+ float recovery;
+ // TODO: determine map scaling factor based on actual map dims
+ size_t map_scale_factor = kMapDimensionScaleFactor;
+ // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
+ // Currently map_scale_factor is of type size_t, but it could be changed to a float
+ // later.
+ if (map_scale_factor != floorf(map_scale_factor)) {
+ recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y);
+ } else {
+ recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y,
+ idwTable);
+ }
+#if USE_APPLY_RECOVERY_LUT
+ Color rgb_hdr = applyRecoveryLUT(rgb_sdr, recovery, recoveryLUT);
+#else
+ Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio);
+#endif
+ Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->rangeScalingFactor);
+ uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
+
+ size_t pixel_idx = x + y * width;
+ reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
+ }
+ }
+ }
+ };
+
+ const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
+ std::vector<std::thread> workers;
+ for (int th = 0; th < threads - 1; th++) {
+ workers.push_back(std::thread(applyRecMap));
}
+ const int rowStep = threads == 1 ? uncompressed_yuv_420_image->height : kJobSzInRows;
+ for (int rowStart = 0; rowStart < uncompressed_yuv_420_image->height;) {
+ int rowEnd = std::min(rowStart + rowStep, uncompressed_yuv_420_image->height);
+ jobQueue.enqueueJob(rowStart, rowEnd);
+ rowStart = rowEnd;
+ }
+ jobQueue.markQueueForEnd();
+ applyRecMap();
+ std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
return NO_ERROR;
}
diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp
index 9ed2949..4f21ac6 100644
--- a/libs/jpegrecoverymap/recoverymapmath.cpp
+++ b/libs/jpegrecoverymap/recoverymapmath.cpp
@@ -15,14 +15,129 @@
*/
#include <cmath>
-
+#include <vector>
#include <jpegrecoverymap/recoverymapmath.h>
namespace android::recoverymap {
+constexpr size_t kPqOETFPrecision = 10;
+constexpr size_t kPqOETFNumEntries = 1 << kPqOETFPrecision;
+
+static const std::vector<float> kPqOETF = [] {
+ std::vector<float> result;
+ float increment = 1.0 / kPqOETFNumEntries;
+ float value = 0.0f;
+ for (int idx = 0; idx < kPqOETFNumEntries; idx++, value += increment) {
+ result.push_back(pqOetf(value));
+ }
+ return result;
+}();
+
+constexpr size_t kPqInvOETFPrecision = 10;
+constexpr size_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision;
+
+static const std::vector<float> kPqInvOETF = [] {
+ std::vector<float> result;
+ float increment = 1.0 / kPqInvOETFNumEntries;
+ float value = 0.0f;
+ for (int idx = 0; idx < kPqInvOETFNumEntries; idx++, value += increment) {
+ result.push_back(pqInvOetf(value));
+ }
+ return result;
+}();
+
+constexpr size_t kHlgOETFPrecision = 10;
+constexpr size_t kHlgOETFNumEntries = 1 << kHlgOETFPrecision;
+
+static const std::vector<float> kHlgOETF = [] {
+ std::vector<float> result;
+ float increment = 1.0 / kHlgOETFNumEntries;
+ float value = 0.0f;
+ for (int idx = 0; idx < kHlgOETFNumEntries; idx++, value += increment) {
+ result.push_back(hlgOetf(value));
+ }
+ return result;
+}();
+
+constexpr size_t kHlgInvOETFPrecision = 10;
+constexpr size_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision;
+
+static const std::vector<float> kHlgInvOETF = [] {
+ std::vector<float> result;
+ float increment = 1.0 / kHlgInvOETFNumEntries;
+ float value = 0.0f;
+ for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++, value += increment) {
+ result.push_back(hlgInvOetf(value));
+ }
+ return result;
+}();
+
+constexpr size_t kSRGBInvOETFPrecision = 10;
+constexpr size_t kSRGBInvOETFNumEntries = 1 << kSRGBInvOETFPrecision;
+static const std::vector<float> kSRGBInvOETF = [] {
+ std::vector<float> result;
+ float increment = 1.0 / kSRGBInvOETFNumEntries;
+ float value = 0.0f;
+ for (int idx = 0; idx < kSRGBInvOETFNumEntries; idx++, value += increment) {
+ result.push_back(srgbInvOetf(value));
+ }
+ return result;
+}();
+
+// Use Shepard's method for inverse distance weighting. For more information:
+// en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method
+
+float ShepardsIDW::euclideanDistance(float x1, float x2, float y1, float y2) {
+ return sqrt(((y2 - y1) * (y2 - y1)) + (x2 - x1) * (x2 - x1));
+}
+
+void ShepardsIDW::fillShepardsIDW(float *weights, int incR, int incB) {
+ for (int y = 0; y < mMapScaleFactor; y++) {
+ for (int x = 0; x < mMapScaleFactor; x++) {
+ float pos_x = ((float)x) / mMapScaleFactor;
+ float pos_y = ((float)y) / mMapScaleFactor;
+ int curr_x = floor(pos_x);
+ int curr_y = floor(pos_y);
+ int next_x = curr_x + incR;
+ int next_y = curr_y + incB;
+ float e1_distance = euclideanDistance(pos_x, curr_x, pos_y, curr_y);
+ int index = y * mMapScaleFactor * 4 + x * 4;
+ if (e1_distance == 0) {
+ weights[index++] = 1.f;
+ weights[index++] = 0.f;
+ weights[index++] = 0.f;
+ weights[index++] = 0.f;
+ } else {
+ float e1_weight = 1.f / e1_distance;
+
+ float e2_distance = euclideanDistance(pos_x, curr_x, pos_y, next_y);
+ float e2_weight = 1.f / e2_distance;
+
+ float e3_distance = euclideanDistance(pos_x, next_x, pos_y, curr_y);
+ float e3_weight = 1.f / e3_distance;
+
+ float e4_distance = euclideanDistance(pos_x, next_x, pos_y, next_y);
+ float e4_weight = 1.f / e4_distance;
+
+ float total_weight = e1_weight + e2_weight + e3_weight + e4_weight;
+
+ weights[index++] = e1_weight / total_weight;
+ weights[index++] = e2_weight / total_weight;
+ weights[index++] = e3_weight / total_weight;
+ weights[index++] = e4_weight / total_weight;
+ }
+ }
+ }
+}
+
////////////////////////////////////////////////////////////////////////////////
// sRGB transformations
+static const float kMaxPixelFloat = 1.0f;
+static float clampPixelFloat(float value) {
+ return (value < 0.0f) ? 0.0f : (value > kMaxPixelFloat) ? kMaxPixelFloat : value;
+}
+
// See IEC 61966-2-1, Equation F.7.
static const float kSrgbR = 0.2126f, kSrgbG = 0.7152f, kSrgbB = 0.0722f;
@@ -34,9 +149,9 @@
static const float kSrgbRCr = 1.402f, kSrgbGCb = 0.34414f, kSrgbGCr = 0.71414f, kSrgbBCb = 1.772f;
Color srgbYuvToRgb(Color e_gamma) {
- return {{{ e_gamma.y + kSrgbRCr * e_gamma.v,
- e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v,
- e_gamma.y + kSrgbBCb * e_gamma.u }}};
+ return {{{ clampPixelFloat(e_gamma.y + kSrgbRCr * e_gamma.v),
+ clampPixelFloat(e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v),
+ clampPixelFloat(e_gamma.y + kSrgbBCb * e_gamma.u) }}};
}
// See ECMA TR/98, Section 7.
@@ -65,6 +180,19 @@
srgbInvOetf(e_gamma.b) }}};
}
+// See IEC 61966-2-1, Equations F.5 and F.6.
+float srgbInvOetfLUT(float e_gamma) {
+ uint32_t value = static_cast<uint32_t>(e_gamma * kSRGBInvOETFNumEntries);
+ //TODO() : Remove once conversion modules have appropriate clamping in place
+ value = CLIP3(value, 0, kSRGBInvOETFNumEntries - 1);
+ return kSRGBInvOETF[value];
+}
+
+Color srgbInvOetfLUT(Color e_gamma) {
+ return {{{ srgbInvOetfLUT(e_gamma.r),
+ srgbInvOetfLUT(e_gamma.g),
+ srgbInvOetfLUT(e_gamma.b) }}};
+}
////////////////////////////////////////////////////////////////////////////////
// Display-P3 transformations
@@ -122,9 +250,9 @@
static const float kBt2100GCr = kBt2100R * kBt2100Cr / kBt2100G;
Color bt2100YuvToRgb(Color e_gamma) {
- return {{{ e_gamma.y + kBt2100Cr * e_gamma.v,
- e_gamma.y - kBt2100GCb * e_gamma.u - kBt2100GCr * e_gamma.v,
- e_gamma.y + kBt2100Cb * e_gamma.u }}};
+ return {{{ clampPixelFloat(e_gamma.y + kBt2100Cr * e_gamma.v),
+ clampPixelFloat(e_gamma.y - kBt2100GCb * e_gamma.u - kBt2100GCr * e_gamma.v),
+ clampPixelFloat(e_gamma.y + kBt2100Cb * e_gamma.u) }}};
}
// See ITU-R BT.2100-2, Table 5, HLG Reference OETF.
@@ -142,6 +270,18 @@
return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}};
}
+float hlgOetfLUT(float e) {
+ uint32_t value = static_cast<uint32_t>(e * kHlgOETFNumEntries);
+ //TODO() : Remove once conversion modules have appropriate clamping in place
+ value = CLIP3(value, 0, kHlgOETFNumEntries - 1);
+
+ return kHlgOETF[value];
+}
+
+Color hlgOetfLUT(Color e) {
+ return {{{ hlgOetfLUT(e.r), hlgOetfLUT(e.g), hlgOetfLUT(e.b) }}};
+}
+
// See ITU-R BT.2100-2, Table 5, HLG Reference EOTF.
float hlgInvOetf(float e_gamma) {
if (e_gamma <= 0.5f) {
@@ -157,6 +297,20 @@
hlgInvOetf(e_gamma.b) }}};
}
+float hlgInvOetfLUT(float e_gamma) {
+ uint32_t value = static_cast<uint32_t>(e_gamma * kHlgInvOETFNumEntries);
+ //TODO() : Remove once conversion modules have appropriate clamping in place
+ value = CLIP3(value, 0, kHlgInvOETFNumEntries - 1);
+
+ return kHlgInvOETF[value];
+}
+
+Color hlgInvOetfLUT(Color e_gamma) {
+ return {{{ hlgInvOetfLUT(e_gamma.r),
+ hlgInvOetfLUT(e_gamma.g),
+ hlgInvOetfLUT(e_gamma.b) }}};
+}
+
// See ITU-R BT.2100-2, Table 4, Reference PQ OETF.
static const float kPqM1 = 2610.0f / 16384.0f, kPqM2 = 2523.0f / 4096.0f * 128.0f;
static const float kPqC1 = 3424.0f / 4096.0f, kPqC2 = 2413.0f / 4096.0f * 32.0f,
@@ -172,6 +326,18 @@
return {{{ pqOetf(e.r), pqOetf(e.g), pqOetf(e.b) }}};
}
+float pqOetfLUT(float e) {
+ uint32_t value = static_cast<uint32_t>(e * kPqOETFNumEntries);
+ //TODO() : Remove once conversion modules have appropriate clamping in place
+ value = CLIP3(value, 0, kPqOETFNumEntries - 1);
+
+ return kPqOETF[value];
+}
+
+Color pqOetfLUT(Color e) {
+ return {{{ pqOetfLUT(e.r), pqOetfLUT(e.g), pqOetfLUT(e.b) }}};
+}
+
// Derived from the inverse of the Reference PQ OETF.
static const float kPqInvA = 128.0f, kPqInvB = 107.0f, kPqInvC = 2413.0f, kPqInvD = 2392.0f,
kPqInvE = 6.2773946361f, kPqInvF = 0.0126833f;
@@ -192,6 +358,20 @@
pqInvOetf(e_gamma.b) }}};
}
+float pqInvOetfLUT(float e_gamma) {
+ uint32_t value = static_cast<uint32_t>(e_gamma * kPqInvOETFNumEntries);
+ //TODO() : Remove once conversion modules have appropriate clamping in place
+ value = CLIP3(value, 0, kPqInvOETFNumEntries - 1);
+
+ return kPqInvOETF[value];
+}
+
+Color pqInvOetfLUT(Color e_gamma) {
+ return {{{ pqInvOetfLUT(e_gamma.r),
+ pqInvOetfLUT(e_gamma.g),
+ pqInvOetfLUT(e_gamma.b) }}};
+}
+
////////////////////////////////////////////////////////////////////////////////
// Color conversions
@@ -294,15 +474,14 @@
return static_cast<uint8_t>(log2(gain) / log2(hdr_ratio) * 127.5f + 127.5f);
}
-static float applyRecovery(float e, float recovery, float hdr_ratio) {
- if (e <= 0.0f) return 0.0f;
- return exp2(log2(e) + recovery * log2(hdr_ratio));
+Color applyRecovery(Color e, float recovery, float hdr_ratio) {
+ float recoveryFactor = pow(hdr_ratio, recovery);
+ return e * recoveryFactor;
}
-Color applyRecovery(Color e, float recovery, float hdr_ratio) {
- return {{{ applyRecovery(e.r, recovery, hdr_ratio),
- applyRecovery(e.g, recovery, hdr_ratio),
- applyRecovery(e.b, recovery, hdr_ratio) }}};
+Color applyRecoveryLUT(Color e, float recovery, RecoveryLUT& recoveryLUT) {
+ float recoveryFactor = recoveryLUT.getRecoveryFactor(recovery);
+ return e * recoveryFactor;
}
Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
@@ -378,6 +557,7 @@
return sqrt(pow(x_diff, 2.0f) + pow(y_diff, 2.0f));
}
+// TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y) {
float x_map = static_cast<float>(x) / static_cast<float>(map_scale_factor);
float y_map = static_cast<float>(y) / static_cast<float>(map_scale_factor);
@@ -427,6 +607,39 @@
+ e4 * (e4_weight / total_weight);
}
+float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y,
+ ShepardsIDW& weightTables) {
+ // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the
+ // following by computing log2(map_scale_factor) once and then using >> log2(map_scale_factor)
+ int x_lower = x / map_scale_factor;
+ int x_upper = x_lower + 1;
+ int y_lower = y / map_scale_factor;
+ int y_upper = y_lower + 1;
+
+ x_lower = std::min(x_lower, map->width - 1);
+ x_upper = std::min(x_upper, map->width - 1);
+ y_lower = std::min(y_lower, map->height - 1);
+ y_upper = std::min(y_upper, map->height - 1);
+
+ float e1 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_lower * map->width]);
+ float e2 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_upper * map->width]);
+ float e3 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_lower * map->width]);
+ float e4 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_upper * map->width]);
+
+ // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the
+ // following by using & (map_scale_factor - 1)
+ int offset_x = x % map_scale_factor;
+ int offset_y = y % map_scale_factor;
+
+ float* weights = weightTables.mWeights;
+ if (x_lower == x_upper && y_lower == y_upper) weights = weightTables.mWeightsC;
+ else if (x_lower == x_upper) weights = weightTables.mWeightsNR;
+ else if (y_lower == y_upper) weights = weightTables.mWeightsNB;
+ weights += offset_y * map_scale_factor * 4 + offset_x * 4;
+
+ return e1 * weights[0] + e2 * weights[1] + e3 * weights[2] + e4 * weights[3];
+}
+
uint32_t colorToRgba1010102(Color e_gamma) {
return (0x3ff & static_cast<uint32_t>(e_gamma.r * 1023.0f))
| ((0x3ff & static_cast<uint32_t>(e_gamma.g * 1023.0f)) << 10)
diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/recoverymaputils.cpp
index 63b25f7..8997b4d 100644
--- a/libs/jpegrecoverymap/recoverymaputils.cpp
+++ b/libs/jpegrecoverymap/recoverymaputils.cpp
@@ -148,41 +148,58 @@
ParseState gContainerItemState;
};
-const string XMPXmlHandler::gContainerItemName = "GContainer:Item";
-const string XMPXmlHandler::rangeScalingFactorAttrName = "RecoveryMap:RangeScalingFactor";
-const string XMPXmlHandler::transferFunctionAttrName = "RecoveryMap:TransferFunction";
+// GContainer XMP constants - URI and namespace prefix
+const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
+const string kContainerPrefix = "GContainer";
+// GContainer XMP constants - element and attribute names
+const string kConDirectory = Name(kContainerPrefix, "Directory");
+const string kConItem = Name(kContainerPrefix, "Item");
+const string kConItemLength = Name(kContainerPrefix, "ItemLength");
+const string kConItemMime = Name(kContainerPrefix, "ItemMime");
+const string kConItemSemantic = Name(kContainerPrefix, "ItemSemantic");
+const string kConVersion = Name(kContainerPrefix, "Version");
-const string kContainerPrefix = "GContainer";
-const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
-const string kRecoveryMapUri = "http://ns.google.com/photos/1.0/recoverymap/";
-const string kItemPrefix = "Item";
-const string kRecoveryMap = "RecoveryMap";
-const string kDirectory = "Directory";
-const string kImageJpeg = "image/jpeg";
-const string kItem = "Item";
-const string kLength = "Length";
-const string kMime = "Mime";
-const string kPrimary = "Primary";
-const string kSemantic = "Semantic";
-const string kVersion = "Version";
-const string kHdr10Metadata = "HDR10Metadata";
-const string kSt2086Metadata = "ST2086Metadata";
-const string kSt2086Coordinate = "ST2086Coordinate";
-const string kSt2086CoordinateX = "ST2086CoordinateX";
-const string kSt2086CoordinateY = "ST2086CoordinateY";
-const string kSt2086Primary = "ST2086Primary";
-const int kSt2086PrimaryRed = 0;
-const int kSt2086PrimaryGreen = 1;
-const int kSt2086PrimaryBlue = 2;
-const int kSt2086PrimaryWhite = 3;
-const int kGContainerVersion = 1;
+// GContainer XMP constants - element and attribute values
+const string kSemanticPrimary = "Primary";
+const string kSemanticRecoveryMap = "RecoveryMap";
+const string kMimeImageJpeg = "image/jpeg";
-const string kConDir = Name(kContainerPrefix, kDirectory);
-const string kContainerItem = Name(kContainerPrefix, kItem);
-const string kItemLength = Name(kItemPrefix, kLength);
-const string kItemMime = Name(kItemPrefix, kMime);
-const string kItemSemantic = Name(kItemPrefix, kSemantic);
+const int kGContainerVersion = 1;
+
+// GContainer XMP constants - names for XMP handlers
+const string XMPXmlHandler::gContainerItemName = kConItem;
+
+// RecoveryMap XMP constants - URI and namespace prefix
+const string kRecoveryMapUri = "http://ns.google.com/photos/1.0/recoverymap/";
+const string kRecoveryMapPrefix = "RecoveryMap";
+
+// RecoveryMap XMP constants - element and attribute names
+const string kMapRangeScalingFactor = Name(kRecoveryMapPrefix, "RangeScalingFactor");
+const string kMapTransferFunction = Name(kRecoveryMapPrefix, "TransferFunction");
+const string kMapVersion = Name(kRecoveryMapPrefix, "Version");
+
+const string kMapHdr10Metadata = Name(kRecoveryMapPrefix, "HDR10Metadata");
+const string kMapHdr10MaxFall = Name(kRecoveryMapPrefix, "HDR10MaxFALL");
+const string kMapHdr10MaxCll = Name(kRecoveryMapPrefix, "HDR10MaxCLL");
+
+const string kMapSt2086Metadata = Name(kRecoveryMapPrefix, "ST2086Metadata");
+const string kMapSt2086MaxLum = Name(kRecoveryMapPrefix, "ST2086MaxLuminance");
+const string kMapSt2086MinLum = Name(kRecoveryMapPrefix, "ST2086MinLuminance");
+const string kMapSt2086Primary = Name(kRecoveryMapPrefix, "ST2086Primary");
+const string kMapSt2086Coordinate = Name(kRecoveryMapPrefix, "ST2086Coordinate");
+const string kMapSt2086CoordinateX = Name(kRecoveryMapPrefix, "ST2086CoordinateX");
+const string kMapSt2086CoordinateY = Name(kRecoveryMapPrefix, "ST2086CoordinateY");
+
+// RecoveryMap XMP constants - element and attribute values
+const int kSt2086PrimaryRed = 0;
+const int kSt2086PrimaryGreen = 1;
+const int kSt2086PrimaryBlue = 2;
+const int kSt2086PrimaryWhite = 3;
+
+// RecoveryMap XMP constants - names for XMP handlers
+const string XMPXmlHandler::rangeScalingFactorAttrName = kMapRangeScalingFactor;
+const string XMPXmlHandler::transferFunctionAttrName = kMapTransferFunction;
bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
@@ -230,8 +247,8 @@
}
string generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
- const vector<string> kConDirSeq({kConDir, string("rdf:Seq")});
- const vector<string> kLiItem({string("rdf:li"), kContainerItem});
+ const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
+ const vector<string> kLiItem({string("rdf:li"), kConItem});
std::stringstream ss;
photos_editing_formats::image_io::XmlWriter writer(ss);
@@ -242,83 +259,69 @@
writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
writer.StartWritingElement("rdf:Description");
writer.WriteXmlns(kContainerPrefix, kContainerUri);
- writer.WriteXmlns(kRecoveryMap, kRecoveryMapUri);
- writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), kGContainerVersion);
+ writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri);
+ writer.WriteElementAndContent(kConVersion, kGContainerVersion);
writer.StartWritingElements(kConDirSeq);
size_t item_depth = writer.StartWritingElements(kLiItem);
- writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);
- writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
- writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kVersion), metadata.version);
- writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, "RangeScalingFactor"), metadata.rangeScalingFactor);
- writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, "TransferFunction"), metadata.transferFunction);
+ writer.WriteAttributeNameAndValue(kConItemSemantic, kSemanticPrimary);
+ writer.WriteAttributeNameAndValue(kConItemMime, kMimeImageJpeg);
+ writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
+ writer.WriteAttributeNameAndValue(kMapRangeScalingFactor, metadata.rangeScalingFactor);
+ writer.WriteAttributeNameAndValue(kMapTransferFunction, metadata.transferFunction);
if (metadata.transferFunction == JPEGR_TF_PQ) {
- writer.StartWritingElement(Name(kRecoveryMap, kHdr10Metadata));
+ writer.StartWritingElement(kMapHdr10Metadata);
+ writer.WriteAttributeNameAndValue(kMapHdr10MaxFall, metadata.hdr10Metadata.maxFALL);
+ writer.WriteAttributeNameAndValue(kMapHdr10MaxCll, metadata.hdr10Metadata.maxCLL);
+ writer.StartWritingElement(kMapSt2086Metadata);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, "HDR10MaxFALL"), metadata.hdr10Metadata.maxFALL);
+ kMapSt2086MaxLum, metadata.hdr10Metadata.st2086Metadata.maxLuminance);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, "HDR10MaxCLL"), metadata.hdr10Metadata.maxCLL);
- writer.StartWritingElement(Name(kRecoveryMap, kSt2086Metadata));
- writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, "ST2086MaxLuminance"),
- metadata.hdr10Metadata.st2086Metadata.maxLuminance);
- writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, "ST2086MinLuminance"),
- metadata.hdr10Metadata.st2086Metadata.minLuminance);
+ kMapSt2086MinLum, metadata.hdr10Metadata.st2086Metadata.minLuminance);
// red
- writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate));
- writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryRed);
+ writer.StartWritingElement(kMapSt2086Coordinate);
+ writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryRed);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, kSt2086CoordinateX),
- metadata.hdr10Metadata.st2086Metadata.redPrimary.x);
+ kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.redPrimary.x);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, kSt2086CoordinateY),
- metadata.hdr10Metadata.st2086Metadata.redPrimary.y);
+ kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.redPrimary.y);
writer.FinishWritingElement();
// green
- writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate));
- writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryGreen);
+ writer.StartWritingElement(kMapSt2086Coordinate);
+ writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryGreen);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, kSt2086CoordinateX),
- metadata.hdr10Metadata.st2086Metadata.greenPrimary.x);
+ kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.greenPrimary.x);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, kSt2086CoordinateY),
- metadata.hdr10Metadata.st2086Metadata.greenPrimary.y);
+ kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.greenPrimary.y);
writer.FinishWritingElement();
// blue
- writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate));
- writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryBlue);
+ writer.StartWritingElement(kMapSt2086Coordinate);
+ writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryBlue);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, kSt2086CoordinateX),
- metadata.hdr10Metadata.st2086Metadata.bluePrimary.x);
+ kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.bluePrimary.x);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, kSt2086CoordinateY),
- metadata.hdr10Metadata.st2086Metadata.bluePrimary.y);
+ kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.bluePrimary.y);
writer.FinishWritingElement();
// white
- writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate));
- writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryWhite);
+ writer.StartWritingElement(kMapSt2086Coordinate);
+ writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryWhite);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, kSt2086CoordinateX),
- metadata.hdr10Metadata.st2086Metadata.whitePoint.x);
+ kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.whitePoint.x);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, kSt2086CoordinateY),
- metadata.hdr10Metadata.st2086Metadata.whitePoint.y);
+ kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.whitePoint.y);
writer.FinishWritingElement();
}
writer.FinishWritingElementsToDepth(item_depth);
writer.StartWritingElements(kLiItem);
- writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap);
- writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
- writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
+ writer.WriteAttributeNameAndValue(kConItemSemantic, kSemanticRecoveryMap);
+ writer.WriteAttributeNameAndValue(kConItemMime, kMimeImageJpeg);
+ writer.WriteAttributeNameAndValue(kConItemLength, secondary_image_length);
writer.FinishWriting();
return ss.str();
}
-} // namespace android::recoverymap
\ No newline at end of file
+} // namespace android::recoverymap
diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp
index b509478..39445f8 100644
--- a/libs/jpegrecoverymap/tests/Android.bp
+++ b/libs/jpegrecoverymap/tests/Android.bp
@@ -30,10 +30,10 @@
],
shared_libs: [
"libjpeg",
+ "libimage_io",
"liblog",
],
static_libs: [
- "libimage_io",
"libgmock",
"libgtest",
"libjpegdecoder",
diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
index 8ff12fb..dfab76a 100644
--- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
@@ -15,6 +15,7 @@
*/
#include <jpegrecoverymap/recoverymap.h>
+#include <jpegrecoverymap/recoverymapmath.h>
#include <jpegrecoverymap/recoverymaputils.h>
#include <fcntl.h>
#include <fstream>
@@ -30,6 +31,7 @@
#define SAVE_ENCODING_RESULT true
#define SAVE_DECODING_RESULT true
+#define SAVE_INPUT_RGBA true
namespace android::recoverymap {
@@ -104,13 +106,22 @@
metadata_expected.transferFunction = JPEGR_TF_HLG;
metadata_expected.rangeScalingFactor = 1.25;
int length_expected = 1000;
+ const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
+ const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
+
std::string xmp = generateXmp(1000, metadata_expected);
+ std::vector<uint8_t> xmpData;
+ xmpData.reserve(nameSpaceLength + xmp.size());
+ xmpData.insert(xmpData.end(), reinterpret_cast<const uint8_t*>(nameSpace.c_str()),
+ reinterpret_cast<const uint8_t*>(nameSpace.c_str()) + nameSpaceLength);
+ xmpData.insert(xmpData.end(), reinterpret_cast<const uint8_t*>(xmp.c_str()),
+ reinterpret_cast<const uint8_t*>(xmp.c_str()) + xmp.size());
+
jpegr_metadata metadata_read;
- EXPECT_TRUE(getMetadataFromXMP(reinterpret_cast<uint8_t*>(xmp[0]), xmp.size(), &metadata_read));
+ EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read));
ASSERT_EQ(metadata_expected.transferFunction, metadata_read.transferFunction);
ASSERT_EQ(metadata_expected.rangeScalingFactor, metadata_read.rangeScalingFactor);
-
}
/* Test Encode API-0 and decode */
@@ -304,6 +315,29 @@
mRawP010Image.height = TEST_IMAGE_HEIGHT;
mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+ if (SAVE_INPUT_RGBA) {
+ size_t rgbaSize = mRawP010Image.width * mRawP010Image.height * sizeof(uint32_t);
+ uint32_t *data = (uint32_t *)malloc(rgbaSize);
+
+ for (size_t y = 0; y < mRawP010Image.height; ++y) {
+ for (size_t x = 0; x < mRawP010Image.width; ++x) {
+ Color hdr_yuv_gamma = getP010Pixel(&mRawP010Image, x, y);
+ Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
+ uint32_t rgba1010102 = colorToRgba1010102(hdr_rgb_gamma);
+ size_t pixel_idx = x + y * mRawP010Image.width;
+ reinterpret_cast<uint32_t*>(data)[pixel_idx] = rgba1010102;
+ }
+ }
+
+ // Output image data to file
+ std::string filePath = "/sdcard/Documents/input_from_p010.rgb10";
+ std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+ if (!imageFile.is_open()) {
+ ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+ }
+ imageFile.write((const char*)data, rgbaSize);
+ free(data);
+ }
if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) {
FAIL() << "Load file " << JPEG_IMAGE << " failed";
}
diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
index f8dd490..1d522d1 100644
--- a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
@@ -517,6 +517,65 @@
EXPECT_RGB_NEAR(pqInvOetf(e_gamma), e);
}
+TEST_F(RecoveryMapMathTest, PqInvOetfLUT) {
+ float increment = 1.0 / 1024.0;
+ float value = 0.0f;
+ for (int idx = 0; idx < 1024; idx++, value += increment) {
+ EXPECT_FLOAT_EQ(pqInvOetf(value), pqInvOetfLUT(value));
+ }
+}
+
+TEST_F(RecoveryMapMathTest, HlgInvOetfLUT) {
+ float increment = 1.0 / 1024.0;
+ float value = 0.0f;
+ for (int idx = 0; idx < 1024; idx++, value += increment) {
+ EXPECT_FLOAT_EQ(hlgInvOetf(value), hlgInvOetfLUT(value));
+ }
+}
+
+TEST_F(RecoveryMapMathTest, pqOetfLUT) {
+ float increment = 1.0 / 1024.0;
+ float value = 0.0f;
+ for (int idx = 0; idx < 1024; idx++, value += increment) {
+ EXPECT_FLOAT_EQ(pqOetf(value), pqOetfLUT(value));
+ }
+}
+
+TEST_F(RecoveryMapMathTest, hlgOetfLUT) {
+ float increment = 1.0 / 1024.0;
+ float value = 0.0f;
+ for (int idx = 0; idx < 1024; idx++, value += increment) {
+ EXPECT_FLOAT_EQ(hlgOetf(value), hlgOetfLUT(value));
+ }
+}
+
+TEST_F(RecoveryMapMathTest, srgbInvOetfLUT) {
+ float increment = 1.0 / 1024.0;
+ float value = 0.0f;
+ for (int idx = 0; idx < 1024; idx++, value += increment) {
+ EXPECT_FLOAT_EQ(srgbInvOetf(value), srgbInvOetfLUT(value));
+ }
+}
+
+TEST_F(RecoveryMapMathTest, applyRecoveryLUT) {
+ float increment = 2.0 / kRecoveryFactorNumEntries;
+ for (float hdrRatio = 1.0f; hdrRatio <= 10.0f; hdrRatio += 1.0f) {
+ RecoveryLUT recoveryLUT(hdrRatio);
+ for (float value = -1.0f; value <= -1.0f; value += increment) {
+ EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, hdrRatio),
+ applyRecoveryLUT(RgbBlack(), value, recoveryLUT));
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, hdrRatio),
+ applyRecoveryLUT(RgbWhite(), value, recoveryLUT));
+ EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, hdrRatio),
+ applyRecoveryLUT(RgbRed(), value, recoveryLUT));
+ EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, hdrRatio),
+ applyRecoveryLUT(RgbGreen(), value, recoveryLUT));
+ EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, hdrRatio),
+ applyRecoveryLUT(RgbBlue(), value, recoveryLUT));
+ }
+ }
+}
+
TEST_F(RecoveryMapMathTest, PqTransferFunctionRoundtrip) {
EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(0.0f)), 0.0f);
EXPECT_NEAR(pqInvOetf(pqOetf(0.01f)), 0.01f, ComparisonEpsilon());
@@ -699,6 +758,7 @@
float (*values)[4] = MapValues();
static const size_t kMapScaleFactor = 2;
+ ShepardsIDW idwTable(kMapScaleFactor);
for (size_t y = 0; y < 4 * kMapScaleFactor; ++y) {
for (size_t x = 0; x < 4 * kMapScaleFactor; ++x) {
size_t x_base = x / kMapScaleFactor;
@@ -725,7 +785,7 @@
// Instead of reimplementing the sampling algorithm, confirm that the
// sample output is within the range of the min and max of the nearest
// points.
- EXPECT_THAT(sampleMap(&image, kMapScaleFactor, x, y),
+ EXPECT_THAT(sampleMap(&image, kMapScaleFactor, x, y, idwTable),
testing::AllOf(testing::Ge(min), testing::Le(max)));
}
}
diff --git a/libs/nativewindow/include/android/hardware_buffer.h b/libs/nativewindow/include/android/hardware_buffer.h
index b2e8bea..85a5249 100644
--- a/libs/nativewindow/include/android/hardware_buffer.h
+++ b/libs/nativewindow/include/android/hardware_buffer.h
@@ -311,6 +311,16 @@
*/
AHARDWAREBUFFER_USAGE_GPU_MIPMAP_COMPLETE = 1UL << 26,
+ /**
+ * Usage: The buffer is used for front-buffer rendering. When
+ * front-buffering rendering is specified, different usages may adjust their
+ * behavior as a result. For example, when used as GPU_COLOR_OUTPUT the buffer
+ * will behave similar to a single-buffered window. When used with
+ * COMPOSER_OVERLAY, the system will try to prioritize the buffer receiving
+ * an overlay plane & avoid caching it in intermediate composition buffers.
+ */
+ AHARDWAREBUFFER_USAGE_FRONT_BUFFER = 1UL << 32,
+
AHARDWAREBUFFER_USAGE_VENDOR_0 = 1ULL << 28,
AHARDWAREBUFFER_USAGE_VENDOR_1 = 1ULL << 29,
AHARDWAREBUFFER_USAGE_VENDOR_2 = 1ULL << 30,
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index b8fd1b2..8d19c45 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -133,8 +133,6 @@
"-fvisibility=hidden",
"-Werror=format",
"-Wno-unused-parameter",
- // TODO: Investigate reducing pinned-memory usage (b/263377839)
- "-DRE_SKIAVK",
],
srcs: [
":librenderengine_sources",
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index 341c011..d08c221 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -39,12 +39,8 @@
ALOGD("RenderEngine with SkiaGL Backend");
return renderengine::skia::SkiaGLRenderEngine::create(args);
case RenderEngineType::SKIA_VK:
-#ifdef RE_SKIAVK
ALOGD("RenderEngine with SkiaVK Backend");
return renderengine::skia::SkiaVkRenderEngine::create(args);
-#else
- LOG_ALWAYS_FATAL("Requested VK backend, but RE_SKIAVK is not defined!");
-#endif
case RenderEngineType::SKIA_GL_THREADED: {
ALOGD("Threaded RenderEngine with SkiaGL Backend");
return renderengine::threaded::RenderEngineThreaded::create(
@@ -54,16 +50,12 @@
args.renderEngineType);
}
case RenderEngineType::SKIA_VK_THREADED:
-#ifdef RE_SKIAVK
ALOGD("Threaded RenderEngine with SkiaVK Backend");
return renderengine::threaded::RenderEngineThreaded::create(
[args]() {
return android::renderengine::skia::SkiaVkRenderEngine::create(args);
},
args.renderEngineType);
-#else
- LOG_ALWAYS_FATAL("Requested VK backend, but RE_SKIAVK is not defined!");
-#endif
case RenderEngineType::GLES:
default:
ALOGD("RenderEngine with GLES Backend");
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index 2b8495c..8d99f3d 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -14,10 +14,6 @@
* limitations under the License.
*/
-// Allow the SkiaVkRenderEngine class to not be compiled, to save space
-// NOTE: In order to build this class, define `RE_SKIAVK` in a build file.
-#ifdef RE_SKIAVK
-
// #define LOG_NDEBUG 0
#undef LOG_TAG
#define LOG_TAG "RenderEngine"
@@ -67,7 +63,7 @@
GrVkExtensions grExtensions;
VkPhysicalDeviceFeatures2* physicalDeviceFeatures2 = nullptr;
VkPhysicalDeviceSamplerYcbcrConversionFeatures* samplerYcbcrConversionFeatures = nullptr;
- VkPhysicalDeviceProtectedMemoryProperties* protectedMemoryFeatures = nullptr;
+ VkPhysicalDeviceProtectedMemoryFeatures* protectedMemoryFeatures = nullptr;
GrVkGetProc grGetProc;
bool isProtected;
bool isRealtimePriority;
@@ -390,7 +386,7 @@
void** tailPnext = &interface.samplerYcbcrConversionFeatures->pNext;
if (protectedContent) {
- interface.protectedMemoryFeatures = new VkPhysicalDeviceProtectedMemoryProperties;
+ interface.protectedMemoryFeatures = new VkPhysicalDeviceProtectedMemoryFeatures;
interface.protectedMemoryFeatures->sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES;
interface.protectedMemoryFeatures->pNext = nullptr;
@@ -677,4 +673,3 @@
} // namespace skia
} // namespace renderengine
} // namespace android
-#endif // RE_SKIAVK
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h
index 1e42b80..2e0cf45 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.h
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.h
@@ -17,10 +17,6 @@
#ifndef SF_SKIAVKRENDERENGINE_H_
#define SF_SKIAVKRENDERENGINE_H_
-// Allow the SkiaVkRenderEngine class to not be compiled, to save space
-// NOTE: In order to build this class, define `RE_SKIAVK` in a build file.
-#ifdef RE_SKIAVK
-
#include <vk/GrVkBackendContext.h>
#include "SkiaRenderEngine.h"
@@ -59,5 +55,4 @@
} // namespace renderengine
} // namespace android
-#endif // RE_SKIAVK
-#endif // SF_SKIAVKRENDERENGINE_H_
+#endif
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 7db95a7..f3f2da8 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -112,7 +112,6 @@
virtual bool useColorManagement() const = 0;
};
-#ifdef RE_SKIAVK
class SkiaVkRenderEngineFactory : public RenderEngineFactory {
public:
std::string name() override { return "SkiaVkRenderEngineFactory"; }
@@ -153,8 +152,6 @@
public:
bool useColorManagement() const override { return true; }
};
-#endif // RE_SKIAVK
-
class SkiaGLESRenderEngineFactory : public RenderEngineFactory {
public:
std::string name() override { return "SkiaGLRenderEngineFactory"; }
@@ -1560,17 +1557,11 @@
expectBufferColor(Rect(kGreyLevels, 1), generator, 2);
}
-#ifdef RE_SKIAVK
INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest,
testing::Values(std::make_shared<SkiaGLESRenderEngineFactory>(),
std::make_shared<SkiaGLESCMRenderEngineFactory>(),
std::make_shared<SkiaVkRenderEngineFactory>(),
std::make_shared<SkiaVkCMRenderEngineFactory>()));
-#else // RE_SKIAVK
-INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest,
- testing::Values(std::make_shared<SkiaGLESRenderEngineFactory>(),
- std::make_shared<SkiaGLESCMRenderEngineFactory>()));
-#endif // RE_SKIAVK
TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) {
if (!GetParam()->typeSupported()) {
diff --git a/libs/ui/include_types/ui/DataspaceUtils.h b/libs/ui/include_types/ui/DataspaceUtils.h
index cd31167..a461cb4 100644
--- a/libs/ui/include_types/ui/DataspaceUtils.h
+++ b/libs/ui/include_types/ui/DataspaceUtils.h
@@ -22,10 +22,8 @@
inline bool isHdrDataspace(ui::Dataspace dataspace) {
const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK;
- const auto range = dataspace & HAL_DATASPACE_RANGE_MASK;
- return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG ||
- range == HAL_DATASPACE_RANGE_EXTENDED;
+ return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
}
-} // namespace android
+} // namespace android
\ No newline at end of file
diff --git a/libs/ui/tests/DataspaceUtils_test.cpp b/libs/ui/tests/DataspaceUtils_test.cpp
index ffe6438..3e09671 100644
--- a/libs/ui/tests/DataspaceUtils_test.cpp
+++ b/libs/ui/tests/DataspaceUtils_test.cpp
@@ -29,13 +29,12 @@
EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_ITU_PQ));
EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_PQ));
EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_HLG));
- // The original formulation of scRGB indicates the same white points as that
- // of sRGB, however scRGB may be used to implement HDR.
- EXPECT_TRUE(isHdrDataspace(ui::Dataspace::V0_SCRGB_LINEAR));
- EXPECT_TRUE(isHdrDataspace(ui::Dataspace::V0_SCRGB));
EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SRGB_LINEAR));
+ // scRGB defines a very wide gamut but not an expanded luminance range
+ EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SCRGB_LINEAR));
EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SRGB));
+ EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SCRGB));
EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_JFIF));
EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_BT601_625));
EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_BT601_525));
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index dd14bcf..34b1251 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -502,7 +502,6 @@
void* so = do_android_dlopen_ext(name.c_str(), RTLD_LOCAL | RTLD_NOW, &dlextinfo);
if (so) {
- ALOGD("dlopen_ext from APK (%s) success at %p", name.c_str(), so);
return so;
} else {
ALOGE("dlopen_ext(\"%s\") failed: %s", name.c_str(), dlerror());
diff --git a/services/automotive/display/Android.bp b/services/automotive/display/Android.bp
index 72bd292..614a78e 100644
--- a/services/automotive/display/Android.bp
+++ b/services/automotive/display/Android.bp
@@ -53,4 +53,6 @@
vintf_fragments: [
"manifest_android.frameworks.automotive.display@1.0.xml",
],
+
+ system_ext_specific: true,
}
diff --git a/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc b/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
index 5c7f344..ea1077a 100644
--- a/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
+++ b/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
@@ -1,4 +1,4 @@
-service automotive_display /system/bin/android.frameworks.automotive.display@1.0-service
+service automotive_display /system_ext/bin/android.frameworks.automotive.display@1.0-service
class hal
user graphics
group automotive_evs
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 906bb1b..37a451b 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -554,6 +554,68 @@
return std::nullopt;
}
+/**
+ * Compare the old touch state to the new touch state, and generate the corresponding touched
+ * windows (== input targets).
+ * If a window had the hovering pointer, but now it doesn't, produce HOVER_EXIT for that window.
+ * If the pointer just entered the new window, produce HOVER_ENTER.
+ * For pointers remaining in the window, produce HOVER_MOVE.
+ */
+std::vector<TouchedWindow> getHoveringWindowsLocked(const TouchState* oldState,
+ const TouchState& newTouchState,
+ const MotionEntry& entry) {
+ std::vector<TouchedWindow> out;
+ const int32_t maskedAction = MotionEvent::getActionMasked(entry.action);
+ if (maskedAction != AMOTION_EVENT_ACTION_HOVER_ENTER &&
+ maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE &&
+ maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
+ // Not a hover event - don't need to do anything
+ return out;
+ }
+
+ // We should consider all hovering pointers here. But for now, just use the first one
+ const int32_t pointerId = entry.pointerProperties[0].id;
+
+ std::set<sp<WindowInfoHandle>> oldWindows;
+ if (oldState != nullptr) {
+ oldWindows = oldState->getWindowsWithHoveringPointer(entry.deviceId, pointerId);
+ }
+
+ std::set<sp<WindowInfoHandle>> newWindows =
+ newTouchState.getWindowsWithHoveringPointer(entry.deviceId, pointerId);
+
+ // If the pointer is no longer in the new window set, send HOVER_EXIT.
+ for (const sp<WindowInfoHandle>& oldWindow : oldWindows) {
+ if (newWindows.find(oldWindow) == newWindows.end()) {
+ TouchedWindow touchedWindow;
+ touchedWindow.windowHandle = oldWindow;
+ touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_HOVER_EXIT;
+ touchedWindow.pointerIds.markBit(pointerId);
+ out.push_back(touchedWindow);
+ }
+ }
+
+ for (const sp<WindowInfoHandle>& newWindow : newWindows) {
+ TouchedWindow touchedWindow;
+ touchedWindow.windowHandle = newWindow;
+ if (oldWindows.find(newWindow) == oldWindows.end()) {
+ // Any windows that have this pointer now, and didn't have it before, should get
+ // HOVER_ENTER
+ touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_HOVER_ENTER;
+ } else {
+ // This pointer was already sent to the window. Use ACTION_HOVER_MOVE.
+ LOG_ALWAYS_FATAL_IF(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE);
+ touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_IS;
+ }
+ touchedWindow.pointerIds.markBit(pointerId);
+ if (canReceiveForegroundTouches(*newWindow->getInfo())) {
+ touchedWindow.targetFlags |= InputTarget::Flags::FOREGROUND;
+ }
+ out.push_back(touchedWindow);
+ }
+ return out;
+}
+
} // namespace
// --- InputDispatcher ---
@@ -2089,8 +2151,6 @@
// Update the touch state as needed based on the properties of the touch event.
outInjectionResult = InputEventInjectionResult::PENDING;
- sp<WindowInfoHandle> newHoverWindowHandle(mLastHoverWindowHandle);
- sp<WindowInfoHandle> newTouchedWindowHandle;
// Copy current touch state into tempTouchState.
// This state will be used to update mTouchStatesByDisplay at the end of this function.
@@ -2123,7 +2183,7 @@
outInjectionResult = InputEventInjectionResult::FAILED;
return touchedWindows; // wrong device
}
- tempTouchState.reset();
+ tempTouchState.clearWindowsWithoutPointers();
tempTouchState.deviceId = entry.deviceId;
tempTouchState.source = entry.source;
isSplit = false;
@@ -2136,14 +2196,21 @@
return touchedWindows; // wrong device
}
+ if (isHoverAction) {
+ // For hover actions, we will treat 'tempTouchState' as a new state, so let's erase
+ // all of the existing hovering pointers and recompute.
+ tempTouchState.clearHoveringPointers();
+ }
+
if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
/* Case 1: New splittable pointer going down, or need target for hover or scroll. */
const auto [x, y] = resolveTouchedPosition(entry);
const int32_t pointerIndex = getMotionEventActionPointerIndex(action);
const bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN;
const bool isStylus = isPointerFromStylus(entry, pointerIndex);
- newTouchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, &tempTouchState,
- isStylus, isDown /*addOutsideTargets*/);
+ sp<WindowInfoHandle> newTouchedWindowHandle =
+ findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, isStylus,
+ isDown /*addOutsideTargets*/);
// Handle the case where we did not find a window.
if (newTouchedWindowHandle == nullptr) {
@@ -2178,15 +2245,6 @@
isSplit = !isFromMouse;
}
- // Update hover state.
- if (newTouchedWindowHandle != nullptr) {
- if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) {
- newHoverWindowHandle = nullptr;
- } else if (isHoverAction) {
- newHoverWindowHandle = newTouchedWindowHandle;
- }
- }
-
std::vector<sp<WindowInfoHandle>> newTouchedWindows =
findTouchedSpyWindowsAtLocked(displayId, x, y, isStylus);
if (newTouchedWindowHandle != nullptr) {
@@ -2206,6 +2264,18 @@
continue;
}
+ if (isHoverAction) {
+ const int32_t pointerId = entry.pointerProperties[0].id;
+ if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+ // Pointer left. Remove it
+ tempTouchState.removeHoveringPointer(entry.deviceId, pointerId);
+ } else {
+ // The "windowHandle" is the target of this hovering pointer.
+ tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId,
+ pointerId);
+ }
+ }
+
// Set target flags.
ftl::Flags<InputTarget::Flags> targetFlags = InputTarget::Flags::DISPATCH_AS_IS;
@@ -2225,7 +2295,9 @@
// Update the temporary touch state.
BitSet32 pointerIds;
- pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
+ if (!isHoverAction) {
+ pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
+ }
tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds,
entry.eventTime);
@@ -2287,7 +2359,7 @@
const bool isStylus = isPointerFromStylus(entry, 0 /*pointerIndex*/);
sp<WindowInfoHandle> oldTouchedWindowHandle =
tempTouchState.getFirstForegroundWindowHandle();
- newTouchedWindowHandle =
+ sp<WindowInfoHandle> newTouchedWindowHandle =
findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, isStylus);
// Verify targeted injection.
@@ -2362,36 +2434,11 @@
}
// Update dispatching for hover enter and exit.
- if (newHoverWindowHandle != mLastHoverWindowHandle) {
- // Let the previous window know that the hover sequence is over, unless we already did
- // it when dispatching it as is to newTouchedWindowHandle.
- if (mLastHoverWindowHandle != nullptr &&
- (maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT ||
- mLastHoverWindowHandle != newTouchedWindowHandle)) {
- if (DEBUG_HOVER) {
- ALOGD("Sending hover exit event to window %s.",
- mLastHoverWindowHandle->getName().c_str());
- }
- tempTouchState.addOrUpdateWindow(mLastHoverWindowHandle,
- InputTarget::Flags::DISPATCH_AS_HOVER_EXIT,
- BitSet32(0));
- }
-
- // Let the new window know that the hover sequence is starting, unless we already did it
- // when dispatching it as is to newTouchedWindowHandle.
- if (newHoverWindowHandle != nullptr &&
- (maskedAction != AMOTION_EVENT_ACTION_HOVER_ENTER ||
- newHoverWindowHandle != newTouchedWindowHandle)) {
- if (DEBUG_HOVER) {
- ALOGD("Sending hover enter event to window %s.",
- newHoverWindowHandle->getName().c_str());
- }
- tempTouchState.addOrUpdateWindow(newHoverWindowHandle,
- InputTarget::Flags::DISPATCH_AS_HOVER_ENTER,
- BitSet32(0));
- }
+ {
+ std::vector<TouchedWindow> hoveringWindows =
+ getHoveringWindowsLocked(oldState, tempTouchState, entry);
+ touchedWindows.insert(touchedWindows.end(), hoveringWindows.begin(), hoveringWindows.end());
}
-
// Ensure that we have at least one foreground window or at least one window that cannot be a
// foreground target. If we only have windows that are not receiving foreground touches (e.g. we
// only have windows getting ACTION_OUTSIDE), then drop the event, because there is no window
@@ -2449,10 +2496,13 @@
}
}
- // Success! Output targets.
- touchedWindows = tempTouchState.windows;
- outInjectionResult = InputEventInjectionResult::SUCCEEDED;
+ // Success! Output targets for everything except hovers.
+ if (!isHoverAction) {
+ touchedWindows.insert(touchedWindows.end(), tempTouchState.windows.begin(),
+ tempTouchState.windows.end());
+ }
+ outInjectionResult = InputEventInjectionResult::SUCCEEDED;
// Drop the outside or hover touch windows since we will not care about them
// in the next iteration.
tempTouchState.filterNonAsIsTouchWindows();
@@ -2473,14 +2523,16 @@
"Conflicting pointer actions: Hover received while pointer was down.");
*outConflictingPointerActions = true;
}
- tempTouchState.reset();
if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
tempTouchState.deviceId = entry.deviceId;
tempTouchState.source = entry.source;
}
- } else if (maskedAction == AMOTION_EVENT_ACTION_UP ||
- maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
+ } else if (maskedAction == AMOTION_EVENT_ACTION_UP) {
+ // Pointer went up.
+ tempTouchState.removeTouchedPointer(entry.pointerProperties[0].id);
+ tempTouchState.clearWindowsWithoutPointers();
+ } else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
// All pointers up or canceled.
tempTouchState.reset();
} else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
@@ -2519,9 +2571,6 @@
mTouchStatesByDisplay.erase(displayId);
}
- // Update hover state.
- mLastHoverWindowHandle = newHoverWindowHandle;
-
return touchedWindows;
}
@@ -4824,14 +4873,6 @@
updateWindowHandlesForDisplayLocked(windowInfoHandles, displayId);
const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
- if (mLastHoverWindowHandle) {
- const WindowInfo* lastHoverWindowInfo = mLastHoverWindowHandle->getInfo();
- if (lastHoverWindowInfo->displayId == displayId &&
- std::find(windowHandles.begin(), windowHandles.end(), mLastHoverWindowHandle) ==
- windowHandles.end()) {
- mLastHoverWindowHandle = nullptr;
- }
- }
std::optional<FocusResolver::FocusChanges> changes =
mFocusResolver.setInputWindows(displayId, windowHandles);
@@ -5278,7 +5319,6 @@
mAnrTracker.clear();
mTouchStatesByDisplay.clear();
- mLastHoverWindowHandle.clear();
mReplacedKeys.clear();
}
@@ -6468,7 +6508,6 @@
synthesizeCancelationEventsForAllConnectionsLocked(options);
mTouchStatesByDisplay.clear();
- mLastHoverWindowHandle.clear();
}
// Wake up poll loop since there might be work to do.
mLooper->wake();
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index a32ebd3..91ca2db 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -530,9 +530,6 @@
// prevent unneeded wakeups.
AnrTracker mAnrTracker GUARDED_BY(mLock);
- // Contains the last window which received a hover event.
- sp<android::gui::WindowInfoHandle> mLastHoverWindowHandle GUARDED_BY(mLock);
-
void cancelEventsForAnrLocked(const sp<Connection>& connection) REQUIRES(mLock);
// If a focused application changes, we should stop counting down the "no focused window" time,
// because we will have no way of knowing when the previous application actually added a window.
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index c21af9e..f120fc9 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -31,10 +31,30 @@
*this = TouchState();
}
+void TouchState::removeTouchedPointer(int32_t pointerId) {
+ for (TouchedWindow& touchedWindow : windows) {
+ touchedWindow.pointerIds.clearBit(pointerId);
+ }
+}
+
+void TouchState::clearHoveringPointers() {
+ for (TouchedWindow& touchedWindow : windows) {
+ touchedWindow.clearHoveringPointers();
+ }
+}
+
+void TouchState::clearWindowsWithoutPointers() {
+ std::erase_if(windows, [](const TouchedWindow& w) {
+ return w.pointerIds.isEmpty() && !w.hasHoveringPointers();
+ });
+}
+
void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle,
ftl::Flags<InputTarget::Flags> targetFlags, BitSet32 pointerIds,
std::optional<nsecs_t> eventTime) {
for (TouchedWindow& touchedWindow : windows) {
+ // We do not compare windows by token here because two windows that share the same token
+ // may have a different transform
if (touchedWindow.windowHandle == windowHandle) {
touchedWindow.targetFlags |= targetFlags;
if (targetFlags.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT)) {
@@ -59,6 +79,21 @@
windows.push_back(touchedWindow);
}
+void TouchState::addHoveringPointerToWindow(const sp<WindowInfoHandle>& windowHandle,
+ int32_t hoveringDeviceId, int32_t hoveringPointerId) {
+ for (TouchedWindow& touchedWindow : windows) {
+ if (touchedWindow.windowHandle == windowHandle) {
+ touchedWindow.addHoveringPointer(hoveringDeviceId, hoveringPointerId);
+ return;
+ }
+ }
+
+ TouchedWindow touchedWindow;
+ touchedWindow.windowHandle = windowHandle;
+ touchedWindow.addHoveringPointer(hoveringDeviceId, hoveringPointerId);
+ windows.push_back(touchedWindow);
+}
+
void TouchState::removeWindowByToken(const sp<IBinder>& token) {
for (size_t i = 0; i < windows.size(); i++) {
if (windows[i].windowHandle->getToken() == token) {
@@ -145,6 +180,26 @@
[](const TouchedWindow& window) { return !window.pointerIds.isEmpty(); });
}
+std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(int32_t hoveringDeviceId,
+ int32_t pointerId) const {
+ std::set<sp<WindowInfoHandle>> out;
+ for (const TouchedWindow& window : windows) {
+ if (window.hasHoveringPointer(hoveringDeviceId, pointerId)) {
+ out.insert(window.windowHandle);
+ }
+ }
+ return out;
+}
+
+void TouchState::removeHoveringPointer(int32_t hoveringDeviceId, int32_t hoveringPointerId) {
+ for (TouchedWindow& window : windows) {
+ window.removeHoveringPointer(hoveringDeviceId, hoveringPointerId);
+ }
+ std::erase_if(windows, [](const TouchedWindow& w) {
+ return w.pointerIds.isEmpty() && !w.hasHoveringPointers();
+ });
+}
+
std::string TouchState::dump() const {
std::string out;
out += StringPrintf("deviceId=%d, source=%s\n", deviceId,
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 77c1cdf..b75e6ef 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -16,6 +16,7 @@
#pragma once
+#include <set>
#include "TouchedWindow.h"
namespace android {
@@ -39,9 +40,16 @@
TouchState& operator=(const TouchState&) = default;
void reset();
+ void clearWindowsWithoutPointers();
+
+ void removeTouchedPointer(int32_t pointerId);
void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
ftl::Flags<InputTarget::Flags> targetFlags, BitSet32 pointerIds,
std::optional<nsecs_t> eventTime = std::nullopt);
+ void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
+ int32_t deviceId, int32_t hoveringPointerId);
+ void removeHoveringPointer(int32_t deviceId, int32_t hoveringPointerId);
+ void clearHoveringPointers();
void removeWindowByToken(const sp<IBinder>& token);
void filterNonAsIsTouchWindows();
@@ -56,6 +64,9 @@
sp<android::gui::WindowInfoHandle> getWallpaperWindow() const;
// Whether any of the windows are currently being touched
bool isDown() const;
+
+ std::set<sp<android::gui::WindowInfoHandle>> getWindowsWithHoveringPointer(
+ int32_t deviceId, int32_t pointerId) const;
std::string dump() const;
};
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index af74598..3704edd 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -25,11 +25,49 @@
namespace inputdispatcher {
+bool TouchedWindow::hasHoveringPointers() const {
+ return !mHoveringPointerIdsByDevice.empty();
+}
+
+void TouchedWindow::clearHoveringPointers() {
+ mHoveringPointerIdsByDevice.clear();
+}
+
+bool TouchedWindow::hasHoveringPointer(int32_t deviceId, int32_t pointerId) const {
+ auto it = mHoveringPointerIdsByDevice.find(deviceId);
+ if (it == mHoveringPointerIdsByDevice.end()) {
+ return false;
+ }
+ return it->second.test(pointerId);
+}
+
+void TouchedWindow::addHoveringPointer(int32_t deviceId, int32_t pointerId) {
+ const auto [it, _] = mHoveringPointerIdsByDevice.insert({deviceId, {}});
+ it->second.set(pointerId);
+}
+
+void TouchedWindow::removeHoveringPointer(int32_t deviceId, int32_t pointerId) {
+ const auto it = mHoveringPointerIdsByDevice.find(deviceId);
+ if (it == mHoveringPointerIdsByDevice.end()) {
+ return;
+ }
+ it->second.set(pointerId, false);
+
+ if (it->second.none()) {
+ mHoveringPointerIdsByDevice.erase(deviceId);
+ }
+}
+
std::string TouchedWindow::dump() const {
- return StringPrintf("name='%s', pointerIds=0x%0x, "
- "targetFlags=%s, firstDownTimeInTarget=%s\n",
+ std::string out;
+ std::string hoveringPointers =
+ dumpMap(mHoveringPointerIdsByDevice, constToString, bitsetToString);
+ out += StringPrintf("name='%s', pointerIds=0x%0x, targetFlags=%s, firstDownTimeInTarget=%s, "
+ "mHoveringPointerIdsByDevice=%s\n",
windowHandle->getName().c_str(), pointerIds.value,
- targetFlags.string().c_str(), toString(firstDownTimeInTarget).c_str());
+ targetFlags.string().c_str(), toString(firstDownTimeInTarget).c_str(),
+ hoveringPointers.c_str());
+ return out;
}
} // namespace inputdispatcher
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index dd08323..add6b61 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -17,7 +17,9 @@
#pragma once
#include <gui/WindowInfo.h>
+#include <input/Input.h>
#include <utils/BitSet.h>
+#include <bitset>
#include "InputTarget.h"
namespace android {
@@ -33,7 +35,17 @@
// Time at which the first action down occurred on this window.
// NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE scenario.
std::optional<nsecs_t> firstDownTimeInTarget;
+
+ bool hasHoveringPointers() const;
+
+ bool hasHoveringPointer(int32_t deviceId, int32_t pointerId) const;
+ void addHoveringPointer(int32_t deviceId, int32_t pointerId);
+ void removeHoveringPointer(int32_t deviceId, int32_t pointerId);
+ void clearHoveringPointers();
std::string dump() const;
+
+private:
+ std::map<int32_t /*deviceId*/, std::bitset<MAX_POINTERS>> mHoveringPointerIdsByDevice;
};
} // namespace inputdispatcher
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index f3b680b..d29692c 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -64,6 +64,7 @@
"mapper/gestures/GestureConverter.cpp",
"mapper/gestures/GesturesLogging.cpp",
"mapper/gestures/HardwareStateConverter.cpp",
+ "mapper/gestures/PropertyProvider.cpp",
],
}
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index 43b67ca..f7b38a1 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -515,6 +515,18 @@
return deviceClasses & InputDeviceClass::JOYSTICK;
}
+// --- RawAbsoluteAxisInfo ---
+
+std::ostream& operator<<(std::ostream& out, const RawAbsoluteAxisInfo& info) {
+ if (info.valid) {
+ out << "min=" << info.minValue << ", max=" << info.maxValue << ", flat=" << info.flat
+ << ", fuzz=" << info.fuzz << ", resolution=" << info.resolution;
+ } else {
+ out << "unknown range";
+ }
+ return out;
+}
+
// --- EventHub::Device ---
EventHub::Device::Device(int fd, int32_t id, std::string path, InputDeviceIdentifier identifier,
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 6e78e82..9bd50f7 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -452,7 +452,8 @@
InputDeviceInfo InputDevice::getDeviceInfo() {
InputDeviceInfo outDeviceInfo;
outDeviceInfo.initialize(mId, mGeneration, mControllerNumber, mIdentifier, mAlias, mIsExternal,
- mHasMic);
+ mHasMic, getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE));
+
for_each_mapper(
[&outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(&outDeviceInfo); });
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index a3ecf41..86acadb 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -19,6 +19,8 @@
#include <bitset>
#include <climits>
#include <filesystem>
+#include <ostream>
+#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
@@ -77,6 +79,8 @@
inline void clear() { *this = RawAbsoluteAxisInfo(); }
};
+std::ostream& operator<<(std::ostream& out, const RawAbsoluteAxisInfo& info);
+
/*
* Input device classes.
*/
diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp
index 8e3539c..ba2ea99 100644
--- a/services/inputflinger/reader/mapper/InputMapper.cpp
+++ b/services/inputflinger/reader/mapper/InputMapper.cpp
@@ -18,6 +18,8 @@
#include "InputMapper.h"
+#include <sstream>
+
#include "InputDevice.h"
#include "input/PrintTools.h"
@@ -120,12 +122,9 @@
void InputMapper::dumpRawAbsoluteAxisInfo(std::string& dump, const RawAbsoluteAxisInfo& axis,
const char* name) {
- if (axis.valid) {
- dump += StringPrintf(INDENT4 "%s: min=%d, max=%d, flat=%d, fuzz=%d, resolution=%d\n", name,
- axis.minValue, axis.maxValue, axis.flat, axis.fuzz, axis.resolution);
- } else {
- dump += StringPrintf(INDENT4 "%s: unknown range\n", name);
- }
+ std::stringstream out;
+ out << INDENT4 << name << ": " << axis << "\n";
+ dump += out.str();
}
void InputMapper::dumpStylusState(std::string& dump, const StylusState& state) {
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 4d51aee..ddddca2 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -55,6 +55,10 @@
return p.x >= rect.left && p.x < rect.right && p.y >= rect.top && p.y < rect.bottom;
}
+static std::string toString(const InputDeviceUsiVersion& v) {
+ return base::StringPrintf("%d.%d", v.majorVersion, v.minorVersion);
+}
+
template <typename T>
inline static void swap(T& a, T& b) {
T temp = a;
@@ -188,7 +192,7 @@
info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
}
info->setButtonUnderPad(mParameters.hasButtonUnderPad);
- info->setSupportsUsi(mParameters.supportsUsi);
+ info->setUsiVersion(mParameters.usiVersion);
}
void TouchInputMapper::dump(std::string& dump) {
@@ -421,9 +425,13 @@
mParameters.wake = getDeviceContext().isExternal();
getDeviceContext().getConfiguration().tryGetProperty("touch.wake", mParameters.wake);
- mParameters.supportsUsi = false;
- getDeviceContext().getConfiguration().tryGetProperty("touch.supportsUsi",
- mParameters.supportsUsi);
+ InputDeviceUsiVersion usiVersion;
+ if (getDeviceContext().getConfiguration().tryGetProperty("touch.usiVersionMajor",
+ usiVersion.majorVersion) &&
+ getDeviceContext().getConfiguration().tryGetProperty("touch.usiVersionMinor",
+ usiVersion.minorVersion)) {
+ mParameters.usiVersion = usiVersion;
+ }
mParameters.enableForInactiveViewport = false;
getDeviceContext().getConfiguration().tryGetProperty("touch.enableForInactiveViewport",
@@ -472,7 +480,8 @@
mParameters.uniqueDisplayId.c_str());
dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware));
dump += INDENT4 "Orientation: " + ftl::enum_string(mParameters.orientation) + "\n";
- dump += StringPrintf(INDENT4 "SupportsUsi: %s\n", toString(mParameters.supportsUsi));
+ dump += StringPrintf(INDENT4 "UsiVersion: %s\n",
+ toString(mParameters.usiVersion, toString).c_str());
dump += StringPrintf(INDENT4 "EnableForInactiveViewport: %s\n",
toString(mParameters.enableForInactiveViewport));
}
@@ -2703,18 +2712,15 @@
// Update the velocity tracker.
{
- std::vector<float> positionsX;
- std::vector<float> positionsY;
for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) {
uint32_t id = idBits.clearFirstMarkedBit();
const RawPointerData::Pointer& pointer =
mCurrentRawState.rawPointerData.pointerForId(id);
- positionsX.push_back(pointer.x * mPointerXMovementScale);
- positionsY.push_back(pointer.y * mPointerYMovementScale);
+ const float x = pointer.x * mPointerXMovementScale;
+ const float y = pointer.y * mPointerYMovementScale;
+ mPointerGesture.velocityTracker.addMovement(when, id, AMOTION_EVENT_AXIS_X, x);
+ mPointerGesture.velocityTracker.addMovement(when, id, AMOTION_EVENT_AXIS_Y, y);
}
- mPointerGesture.velocityTracker.addMovement(when, mCurrentCookedState.fingerIdBits,
- {{AMOTION_EVENT_AXIS_X, positionsX},
- {AMOTION_EVENT_AXIS_Y, positionsY}});
}
// If the gesture ever enters a mode other than TAP, HOVER or TAP_DRAG, without first returning
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 6e35b46..87deb39 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -234,8 +234,8 @@
bool wake;
- // Whether the device supports the Universal Stylus Initiative (USI) protocol for styluses.
- bool supportsUsi;
+ // The Universal Stylus Initiative (USI) protocol version supported by this device.
+ std::optional<InputDeviceUsiVersion> usiVersion;
// Allows touches while the display is off.
bool enableForInactiveViewport;
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 3b51be8..b6313a1 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -19,6 +19,7 @@
#include <optional>
#include <android/input.h>
+#include <input/PrintTools.h>
#include <linux/input-event-codes.h>
#include <log/log_main.h>
#include "TouchCursorInputMapperCommon.h"
@@ -96,11 +97,12 @@
mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD);
mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext));
// Even though we don't explicitly delete copy/move semantics, it's safe to
- // give away a pointer to TouchpadInputMapper here because
+ // give away pointers to TouchpadInputMapper and its members here because
// 1) mGestureInterpreter's lifecycle is determined by TouchpadInputMapper, and
// 2) TouchpadInputMapper is stored as a unique_ptr and not moved.
+ mGestureInterpreter->SetPropProvider(const_cast<GesturesPropProvider*>(&gesturePropProvider),
+ &mPropertyProvider);
mGestureInterpreter->SetCallback(gestureInterpreterCallback, this);
- // TODO(b/251196347): set a property provider, so we can change gesture properties.
// TODO(b/251196347): set a timer provider, so the library can use timers.
}
@@ -108,12 +110,29 @@
if (mPointerController != nullptr) {
mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
}
+
+ // The gesture interpreter's destructor will call its property provider's free function for all
+ // gesture properties, in this case calling PropertyProvider::freeProperty using a raw pointer
+ // to mPropertyProvider. Depending on the declaration order in TouchpadInputMapper.h, this may
+ // happen after mPropertyProvider has been destructed, causing allocation errors. Depending on
+ // declaration order to avoid crashes seems rather fragile, so explicitly clear the property
+ // provider here to ensure all the freeProperty calls happen before mPropertyProvider is
+ // destructed.
+ mGestureInterpreter->SetPropProvider(nullptr, nullptr);
}
uint32_t TouchpadInputMapper::getSources() const {
return AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD;
}
+void TouchpadInputMapper::dump(std::string& dump) {
+ dump += INDENT2 "Touchpad Input Mapper:\n";
+ dump += INDENT3 "Gesture converter:\n";
+ dump += addLinePrefix(mGestureConverter.dump(), INDENT4);
+ dump += INDENT3 "Gesture properties:\n";
+ dump += addLinePrefix(mPropertyProvider.dump(), INDENT4);
+}
+
std::list<NotifyArgs> TouchpadInputMapper::configure(nsecs_t when,
const InputReaderConfiguration* config,
uint32_t changes) {
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index 3a92211..d693bca 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -29,6 +29,7 @@
#include "NotifyArgs.h"
#include "gestures/GestureConverter.h"
#include "gestures/HardwareStateConverter.h"
+#include "gestures/PropertyProvider.h"
#include "include/gestures.h"
@@ -40,6 +41,8 @@
~TouchpadInputMapper();
uint32_t getSources() const override;
+ void dump(std::string& dump) override;
+
[[nodiscard]] std::list<NotifyArgs> configure(nsecs_t when,
const InputReaderConfiguration* config,
uint32_t changes) override;
@@ -57,6 +60,8 @@
mGestureInterpreter;
std::shared_ptr<PointerControllerInterface> mPointerController;
+ PropertyProvider mPropertyProvider;
+
HardwareStateConverter mStateConverter;
GestureConverter mGestureConverter;
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 11ffd28..561b1f8 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -16,7 +16,11 @@
#include "gestures/GestureConverter.h"
+#include <sstream>
+
+#include <android-base/stringprintf.h>
#include <android/input.h>
+#include <ftl/enum.h>
#include <linux/input-event-codes.h>
#include <log/log_main.h>
@@ -55,6 +59,18 @@
deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mYAxisInfo);
}
+std::string GestureConverter::dump() const {
+ std::stringstream out;
+ out << "Orientation: " << ftl::enum_string(mOrientation) << "\n";
+ out << "Axis info:\n";
+ out << " X: " << mXAxisInfo << "\n";
+ out << " Y: " << mYAxisInfo << "\n";
+ out << StringPrintf("Button state: 0x%08x\n", mButtonState);
+ out << "Down time: " << mDownTime << "\n";
+ out << "Current classification: " << ftl::enum_string(mCurrentClassification) << "\n";
+ return out.str();
+}
+
void GestureConverter::reset() {
mButtonState = 0;
}
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index 8e8e3d9..2ec5841 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -40,6 +40,8 @@
GestureConverter(InputReaderContext& readerContext, const InputDeviceContext& deviceContext,
int32_t deviceId);
+ std::string dump() const;
+
void setOrientation(ui::Rotation orientation) { mOrientation = orientation; }
void reset();
diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp
new file mode 100644
index 0000000..cd18cd3
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp
@@ -0,0 +1,236 @@
+/*
+ * 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 "../Macros.h"
+
+#include "gestures/PropertyProvider.h"
+
+#include <algorithm>
+#include <utility>
+
+#include <android-base/stringprintf.h>
+#include <ftl/enum.h>
+#include <input/PrintTools.h>
+#include <log/log_main.h>
+
+namespace android {
+
+namespace {
+
+GesturesProp* createInt(void* data, const char* name, int* loc, size_t count, const int* init) {
+ return static_cast<PropertyProvider*>(data)->createIntArrayProperty(name, loc, count, init);
+}
+
+GesturesProp* createBool(void* data, const char* name, GesturesPropBool* loc, size_t count,
+ const GesturesPropBool* init) {
+ return static_cast<PropertyProvider*>(data)->createBoolArrayProperty(name, loc, count, init);
+}
+
+GesturesProp* createString(void* data, const char* name, const char** loc, const char* const init) {
+ return static_cast<PropertyProvider*>(data)->createStringProperty(name, loc, init);
+}
+
+GesturesProp* createReal(void* data, const char* name, double* loc, size_t count,
+ const double* init) {
+ return static_cast<PropertyProvider*>(data)->createRealArrayProperty(name, loc, count, init);
+}
+
+void registerHandlers(void* data, GesturesProp* prop, void* handlerData,
+ GesturesPropGetHandler getter, GesturesPropSetHandler setter) {
+ prop->registerHandlers(handlerData, getter, setter);
+}
+
+void freeProperty(void* data, GesturesProp* prop) {
+ static_cast<PropertyProvider*>(data)->freeProperty(prop);
+}
+
+} // namespace
+
+const GesturesPropProvider gesturePropProvider = {
+ .create_int_fn = createInt,
+ .create_bool_fn = createBool,
+ .create_string_fn = createString,
+ .create_real_fn = createReal,
+ .register_handlers_fn = registerHandlers,
+ .free_fn = freeProperty,
+};
+
+bool PropertyProvider::hasProperty(const std::string name) const {
+ return mProperties.find(name) != mProperties.end();
+}
+
+GesturesProp& PropertyProvider::getProperty(const std::string name) {
+ return mProperties.at(name);
+}
+
+std::string PropertyProvider::dump() const {
+ std::string dump;
+ for (const auto& [name, property] : mProperties) {
+ dump += property.dump() + "\n";
+ }
+ return dump;
+}
+
+GesturesProp* PropertyProvider::createIntArrayProperty(const std::string name, int* loc,
+ size_t count, const int* init) {
+ const auto [it, inserted] =
+ mProperties.insert(std::pair{name, GesturesProp(name, loc, count, init)});
+ LOG_ALWAYS_FATAL_IF(!inserted, "Gesture property \"%s\" already exists.", name.c_str());
+ return &it->second;
+}
+
+GesturesProp* PropertyProvider::createBoolArrayProperty(const std::string name,
+ GesturesPropBool* loc, size_t count,
+ const GesturesPropBool* init) {
+ const auto [it, inserted] =
+ mProperties.insert(std::pair{name, GesturesProp(name, loc, count, init)});
+ LOG_ALWAYS_FATAL_IF(!inserted, "Gesture property \"%s\" already exists.", name.c_str());
+ return &it->second;
+}
+
+GesturesProp* PropertyProvider::createRealArrayProperty(const std::string name, double* loc,
+ size_t count, const double* init) {
+ const auto [it, inserted] =
+ mProperties.insert(std::pair{name, GesturesProp(name, loc, count, init)});
+ LOG_ALWAYS_FATAL_IF(!inserted, "Gesture property \"%s\" already exists.", name.c_str());
+ return &it->second;
+}
+
+GesturesProp* PropertyProvider::createStringProperty(const std::string name, const char** loc,
+ const char* const init) {
+ const auto [it, inserted] = mProperties.insert(std::pair{name, GesturesProp(name, loc, init)});
+ LOG_ALWAYS_FATAL_IF(!inserted, "Gesture property \"%s\" already exists.", name.c_str());
+ return &it->second;
+}
+
+void PropertyProvider::freeProperty(GesturesProp* prop) {
+ mProperties.erase(prop->getName());
+}
+
+} // namespace android
+
+template <typename T>
+GesturesProp::GesturesProp(std::string name, T* dataPointer, size_t count, const T* initialValues)
+ : mName(name), mCount(count), mDataPointer(dataPointer) {
+ std::copy_n(initialValues, count, dataPointer);
+}
+
+GesturesProp::GesturesProp(std::string name, const char** dataPointer,
+ const char* const initialValue)
+ : mName(name), mCount(1), mDataPointer(dataPointer) {
+ *(std::get<const char**>(mDataPointer)) = initialValue;
+}
+
+std::string GesturesProp::dump() const {
+ using android::base::StringPrintf;
+ std::string type, values;
+ switch (mDataPointer.index()) {
+ case 0:
+ type = "integer";
+ values = android::dumpVector(getIntValues());
+ break;
+ case 1:
+ type = "boolean";
+ values = android::dumpVector(getBoolValues());
+ break;
+ case 2:
+ type = "string";
+ values = getStringValue();
+ break;
+ case 3:
+ type = "real";
+ values = android::dumpVector(getRealValues());
+ break;
+ }
+ std::string typeAndSize = mCount == 1 ? type : std::to_string(mCount) + " " + type + "s";
+ return StringPrintf("%s (%s): %s", mName.c_str(), typeAndSize.c_str(), values.c_str());
+}
+
+void GesturesProp::registerHandlers(void* handlerData, GesturesPropGetHandler getter,
+ GesturesPropSetHandler setter) {
+ mHandlerData = handlerData;
+ mGetter = getter;
+ mSetter = setter;
+}
+
+std::vector<int> GesturesProp::getIntValues() const {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<int*>(mDataPointer),
+ "Attempt to read ints from \"%s\" gesture property.", mName.c_str());
+ return getValues<int, int>(std::get<int*>(mDataPointer));
+}
+
+std::vector<bool> GesturesProp::getBoolValues() const {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<GesturesPropBool*>(mDataPointer),
+ "Attempt to read bools from \"%s\" gesture property.", mName.c_str());
+ return getValues<bool, GesturesPropBool>(std::get<GesturesPropBool*>(mDataPointer));
+}
+
+std::vector<double> GesturesProp::getRealValues() const {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<double*>(mDataPointer),
+ "Attempt to read reals from \"%s\" gesture property.", mName.c_str());
+ return getValues<double, double>(std::get<double*>(mDataPointer));
+}
+
+std::string GesturesProp::getStringValue() const {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<const char**>(mDataPointer),
+ "Attempt to read a string from \"%s\" gesture property.", mName.c_str());
+ if (mGetter != nullptr) {
+ mGetter(mHandlerData);
+ }
+ return std::string(*std::get<const char**>(mDataPointer));
+}
+
+void GesturesProp::setBoolValues(const std::vector<bool>& values) {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<GesturesPropBool*>(mDataPointer),
+ "Attempt to write bools to \"%s\" gesture property.", mName.c_str());
+ setValues(std::get<GesturesPropBool*>(mDataPointer), values);
+}
+
+void GesturesProp::setIntValues(const std::vector<int>& values) {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<int*>(mDataPointer),
+ "Attempt to write ints to \"%s\" gesture property.", mName.c_str());
+ setValues(std::get<int*>(mDataPointer), values);
+}
+
+void GesturesProp::setRealValues(const std::vector<double>& values) {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<double*>(mDataPointer),
+ "Attempt to write reals to \"%s\" gesture property.", mName.c_str());
+ setValues(std::get<double*>(mDataPointer), values);
+}
+
+template <typename T, typename U>
+const std::vector<T> GesturesProp::getValues(U* dataPointer) const {
+ if (mGetter != nullptr) {
+ mGetter(mHandlerData);
+ }
+ std::vector<T> values;
+ values.reserve(mCount);
+ for (size_t i = 0; i < mCount; i++) {
+ values.push_back(dataPointer[i]);
+ }
+ return values;
+}
+
+template <typename T, typename U>
+void GesturesProp::setValues(T* dataPointer, const std::vector<U>& values) {
+ LOG_ALWAYS_FATAL_IF(values.size() != mCount,
+ "Attempt to write %zu values to \"%s\" gesture property, which holds %zu.",
+ values.size(), mName.c_str(), mCount);
+ std::copy(values.begin(), values.end(), dataPointer);
+ if (mSetter != nullptr) {
+ mSetter(mHandlerData);
+ }
+}
diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.h b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h
new file mode 100644
index 0000000..c21260f
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+#include <variant>
+#include <vector>
+
+#include "include/gestures.h"
+
+namespace android {
+
+// Struct containing functions that wrap PropertyProvider in a C-compatible interface.
+extern const GesturesPropProvider gesturePropProvider;
+
+// Implementation of a gestures library property provider, which provides configuration parameters.
+class PropertyProvider {
+public:
+ bool hasProperty(const std::string name) const;
+ GesturesProp& getProperty(const std::string name);
+ std::string dump() const;
+
+ // Methods to be called by the gestures library:
+ GesturesProp* createIntArrayProperty(const std::string name, int* loc, size_t count,
+ const int* init);
+ GesturesProp* createBoolArrayProperty(const std::string name, GesturesPropBool* loc,
+ size_t count, const GesturesPropBool* init);
+ GesturesProp* createRealArrayProperty(const std::string name, double* loc, size_t count,
+ const double* init);
+ GesturesProp* createStringProperty(const std::string name, const char** loc,
+ const char* const init);
+
+ void freeProperty(GesturesProp* prop);
+
+private:
+ std::map<std::string, GesturesProp> mProperties;
+};
+
+} // namespace android
+
+// Represents a single gesture property.
+//
+// Pointers to this struct will be used by the gestures library (though it can never deference
+// them). The library's API requires this to be in the top-level namespace.
+struct GesturesProp {
+public:
+ template <typename T>
+ GesturesProp(std::string name, T* dataPointer, size_t count, const T* initialValues);
+ GesturesProp(std::string name, const char** dataPointer, const char* const initialValue);
+
+ std::string dump() const;
+
+ std::string getName() const { return mName; }
+
+ size_t getCount() const { return mCount; }
+
+ void registerHandlers(void* handlerData, GesturesPropGetHandler getter,
+ GesturesPropSetHandler setter);
+
+ std::vector<int> getIntValues() const;
+ std::vector<bool> getBoolValues() const;
+ std::vector<double> getRealValues() const;
+ std::string getStringValue() const;
+
+ void setIntValues(const std::vector<int>& values);
+ void setBoolValues(const std::vector<bool>& values);
+ void setRealValues(const std::vector<double>& values);
+ // Setting string values isn't supported since we don't have a use case yet and the memory
+ // management adds additional complexity.
+
+private:
+ // Two type parameters are required for these methods, rather than one, due to the gestures
+ // library using its own bool type.
+ template <typename T, typename U>
+ const std::vector<T> getValues(U* dataPointer) const;
+ template <typename T, typename U>
+ void setValues(T* dataPointer, const std::vector<U>& values);
+
+ std::string mName;
+ size_t mCount;
+ std::variant<int*, GesturesPropBool*, const char**, double*> mDataPointer;
+ void* mHandlerData = nullptr;
+ GesturesPropGetHandler mGetter = nullptr;
+ GesturesPropSetHandler mSetter = nullptr;
+};
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 58a5c31..af40fed 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -55,6 +55,7 @@
"LatencyTracker_test.cpp",
"NotifyArgs_test.cpp",
"PreferStylusOverTouch_test.cpp",
+ "PropertyProvider_test.cpp",
"TestInputListener.cpp",
"UinputDevice.cpp",
"UnwantedInteractionBlocker_test.cpp",
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 864aaea..3abe43a 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -2168,7 +2168,6 @@
.y(400))
.build()));
windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
- windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
// Move cursor into left window
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -2181,7 +2180,6 @@
.build()));
windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
- windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
// Inject a series of mouse events for a mouse click
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -2239,7 +2237,6 @@
.build()));
windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
- windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
// No more events
windowLeft->assertNoEvents();
@@ -2301,7 +2298,6 @@
.y(400))
.build()));
window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
-
// Inject a series of mouse events for a mouse click
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher,
@@ -2359,8 +2355,38 @@
}
/**
+ * Hover over a window, and then remove that window. Make sure that HOVER_EXIT for that event
+ * is generated.
+ */
+TEST_F(InputDispatcherTest, HoverExitIsSentToRemovedWindow) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 1200, 800));
+
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+ AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+ .x(300)
+ .y(400))
+ .build()));
+ window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+ // Remove the window, but keep the channel.
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}});
+ window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+}
+
+/**
* Inject a mouse hover event followed by a tap from touchscreen.
- * In the current implementation, the tap does not cause a HOVER_EXIT event.
+ * The tap causes a HOVER_EXIT event to be generated because the current event
+ * stream's source has been switched.
*/
TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
@@ -2380,15 +2406,16 @@
ASSERT_NO_FATAL_FAILURE(
window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
WithSource(AINPUT_SOURCE_MOUSE))));
- ASSERT_NO_FATAL_FAILURE(
- window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
- WithSource(AINPUT_SOURCE_MOUSE))));
// Tap on the window
motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, {{10, 10}});
mDispatcher->notifyMotion(&motionArgs);
ASSERT_NO_FATAL_FAILURE(
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+ WithSource(AINPUT_SOURCE_MOUSE))));
+
+ ASSERT_NO_FATAL_FAILURE(
window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
@@ -2426,7 +2453,6 @@
.y(600))
.build()));
windowDefaultDisplay->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
- windowDefaultDisplay->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
// Remove all windows in secondary display and check that no event happens on window in
// primary display.
diff --git a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
index bd05360..7265362 100644
--- a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
+++ b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
@@ -15,6 +15,7 @@
*/
#include <gtest/gtest.h>
+#include <gui/constants.h>
#include "../PreferStylusOverTouchBlocker.h"
namespace android {
@@ -438,7 +439,7 @@
InputDeviceInfo stylusDevice;
stylusDevice.initialize(STYLUS_DEVICE_ID, 1 /*generation*/, 1 /*controllerNumber*/,
{} /*identifier*/, "stylus device", false /*external*/,
- false /*hasMic*/);
+ false /*hasMic*/, ADISPLAY_ID_NONE);
notifyInputDevicesChanged({stylusDevice});
// The touchscreen device was removed, so we no longer remember anything about it. We should
// again start blocking touch events from it.
diff --git a/services/inputflinger/tests/PropertyProvider_test.cpp b/services/inputflinger/tests/PropertyProvider_test.cpp
new file mode 100644
index 0000000..42a6a9f
--- /dev/null
+++ b/services/inputflinger/tests/PropertyProvider_test.cpp
@@ -0,0 +1,286 @@
+/*
+ * 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 <gestures/PropertyProvider.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "include/gestures.h"
+
+namespace android {
+
+using testing::ElementsAre;
+
+class PropertyProviderTest : public testing::Test {
+protected:
+ PropertyProvider mProvider;
+};
+
+TEST_F(PropertyProviderTest, Int_Create) {
+ const size_t COUNT = 4;
+ int intData[COUNT] = {0, 0, 0, 0};
+ int initialValues[COUNT] = {1, 2, 3, 4};
+ gesturePropProvider.create_int_fn(&mProvider, "Some Integers", intData, COUNT, initialValues);
+
+ ASSERT_TRUE(mProvider.hasProperty("Some Integers"));
+ GesturesProp& prop = mProvider.getProperty("Some Integers");
+ EXPECT_EQ(prop.getName(), "Some Integers");
+ EXPECT_EQ(prop.getCount(), COUNT);
+ EXPECT_THAT(intData, ElementsAre(1, 2, 3, 4));
+}
+
+TEST_F(PropertyProviderTest, Int_Get) {
+ const size_t COUNT = 4;
+ int intData[COUNT] = {0, 0, 0, 0};
+ int initialValues[COUNT] = {9, 9, 9, 9};
+ GesturesProp* propPtr = gesturePropProvider.create_int_fn(&mProvider, "Some Integers", intData,
+ COUNT, initialValues);
+
+ // Get handlers are supposed to be called before the property's data is accessed, so they can
+ // update it if necessary. This getter updates the values, so that the ordering can be checked.
+ GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool {
+ int* array = static_cast<int*>(handlerData);
+ array[0] = 1;
+ array[1] = 2;
+ array[2] = 3;
+ array[3] = 4;
+ return true;
+ }};
+ gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ intData,
+ getter, nullptr);
+
+ ASSERT_TRUE(mProvider.hasProperty("Some Integers"));
+ GesturesProp& prop = mProvider.getProperty("Some Integers");
+ EXPECT_THAT(prop.getIntValues(), ElementsAre(1, 2, 3, 4));
+}
+
+TEST_F(PropertyProviderTest, Int_Set) {
+ const size_t COUNT = 4;
+ int intData[COUNT] = {0, 0, 0, 0};
+ int initialValues[COUNT] = {9, 9, 9, 9};
+ GesturesProp* propPtr = gesturePropProvider.create_int_fn(&mProvider, "Some Integers", intData,
+ COUNT, initialValues);
+
+ struct SetterData {
+ bool setterCalled;
+ int* propertyData;
+ };
+ SetterData setterData = {false, intData};
+ GesturesPropSetHandler setter{[](void* handlerData) {
+ SetterData* data = static_cast<SetterData*>(handlerData);
+ // Set handlers should be called after the property's data has changed, so check the data.
+ EXPECT_EQ(data->propertyData[0], 1);
+ EXPECT_EQ(data->propertyData[1], 2);
+ EXPECT_EQ(data->propertyData[2], 3);
+ EXPECT_EQ(data->propertyData[3], 4);
+ data->setterCalled = true;
+ }};
+ gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &setterData,
+ nullptr, setter);
+
+ ASSERT_TRUE(mProvider.hasProperty("Some Integers"));
+ GesturesProp& prop = mProvider.getProperty("Some Integers");
+ prop.setIntValues({1, 2, 3, 4});
+ EXPECT_THAT(intData, ElementsAre(1, 2, 3, 4));
+ EXPECT_TRUE(setterData.setterCalled);
+ EXPECT_THAT(prop.getIntValues(), ElementsAre(1, 2, 3, 4));
+}
+
+TEST_F(PropertyProviderTest, Bool_Create) {
+ const size_t COUNT = 3;
+ GesturesPropBool boolData[COUNT] = {false, false, false};
+ GesturesPropBool initialValues[COUNT] = {true, false, false};
+ gesturePropProvider.create_bool_fn(&mProvider, "Some Booleans", boolData, COUNT, initialValues);
+
+ ASSERT_TRUE(mProvider.hasProperty("Some Booleans"));
+ GesturesProp& prop = mProvider.getProperty("Some Booleans");
+ EXPECT_EQ(prop.getName(), "Some Booleans");
+ EXPECT_EQ(prop.getCount(), COUNT);
+ EXPECT_THAT(boolData, ElementsAre(true, false, false));
+}
+
+TEST_F(PropertyProviderTest, Bool_Get) {
+ const size_t COUNT = 3;
+ GesturesPropBool boolData[COUNT] = {false, false, false};
+ GesturesPropBool initialValues[COUNT] = {true, false, false};
+ GesturesProp* propPtr = gesturePropProvider.create_bool_fn(&mProvider, "Some Booleans",
+ boolData, COUNT, initialValues);
+
+ // Get handlers are supposed to be called before the property's data is accessed, so they can
+ // update it if necessary. This getter updates the values, so that the ordering can be checked.
+ GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool {
+ GesturesPropBool* array = static_cast<GesturesPropBool*>(handlerData);
+ array[0] = false;
+ array[1] = true;
+ array[2] = true;
+ return true;
+ }};
+ gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ boolData,
+ getter, nullptr);
+
+ ASSERT_TRUE(mProvider.hasProperty("Some Booleans"));
+ GesturesProp& prop = mProvider.getProperty("Some Booleans");
+ EXPECT_THAT(prop.getBoolValues(), ElementsAre(false, true, true));
+}
+
+TEST_F(PropertyProviderTest, Bool_Set) {
+ const size_t COUNT = 3;
+ GesturesPropBool boolData[COUNT] = {false, false, false};
+ GesturesPropBool initialValues[COUNT] = {true, false, false};
+ GesturesProp* propPtr = gesturePropProvider.create_bool_fn(&mProvider, "Some Booleans",
+ boolData, COUNT, initialValues);
+
+ struct SetterData {
+ bool setterCalled;
+ GesturesPropBool* propertyData;
+ };
+ SetterData setterData = {false, boolData};
+ GesturesPropSetHandler setter{[](void* handlerData) {
+ SetterData* data = static_cast<SetterData*>(handlerData);
+ // Set handlers should be called after the property's data has changed, so check the data.
+ EXPECT_EQ(data->propertyData[0], false);
+ EXPECT_EQ(data->propertyData[1], true);
+ EXPECT_EQ(data->propertyData[2], true);
+ data->setterCalled = true;
+ }};
+ gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &setterData,
+ nullptr, setter);
+
+ ASSERT_TRUE(mProvider.hasProperty("Some Booleans"));
+ GesturesProp& prop = mProvider.getProperty("Some Booleans");
+ prop.setBoolValues({false, true, true});
+ EXPECT_THAT(boolData, ElementsAre(false, true, true));
+ EXPECT_TRUE(setterData.setterCalled);
+ EXPECT_THAT(prop.getBoolValues(), ElementsAre(false, true, true));
+}
+
+TEST_F(PropertyProviderTest, Real_Create) {
+ const size_t COUNT = 3;
+ double realData[COUNT] = {0.0, 0.0, 0.0};
+ double initialValues[COUNT] = {3.14, 0.7, -5.0};
+ gesturePropProvider.create_real_fn(&mProvider, "Some Reals", realData, COUNT, initialValues);
+
+ ASSERT_TRUE(mProvider.hasProperty("Some Reals"));
+ GesturesProp& prop = mProvider.getProperty("Some Reals");
+ EXPECT_EQ(prop.getName(), "Some Reals");
+ EXPECT_EQ(prop.getCount(), COUNT);
+ EXPECT_THAT(realData, ElementsAre(3.14, 0.7, -5.0));
+}
+
+TEST_F(PropertyProviderTest, Real_Get) {
+ const size_t COUNT = 3;
+ double realData[COUNT] = {0.0, 0.0, 0.0};
+ double initialValues[COUNT] = {-1.0, -1.0, -1.0};
+ GesturesProp* propPtr = gesturePropProvider.create_real_fn(&mProvider, "Some Reals", realData,
+ COUNT, initialValues);
+
+ // Get handlers are supposed to be called before the property's data is accessed, so they can
+ // update it if necessary. This getter updates the values, so that the ordering can be checked.
+ GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool {
+ double* array = static_cast<double*>(handlerData);
+ array[0] = 3.14;
+ array[1] = 0.7;
+ array[2] = -5.0;
+ return true;
+ }};
+ gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ realData,
+ getter, nullptr);
+
+ ASSERT_TRUE(mProvider.hasProperty("Some Reals"));
+ GesturesProp& prop = mProvider.getProperty("Some Reals");
+ EXPECT_THAT(prop.getRealValues(), ElementsAre(3.14, 0.7, -5.0));
+}
+
+TEST_F(PropertyProviderTest, Real_Set) {
+ const size_t COUNT = 3;
+ double realData[COUNT] = {0.0, 0.0, 0.0};
+ double initialValues[COUNT] = {-1.0, -1.0, -1.0};
+ GesturesProp* propPtr = gesturePropProvider.create_real_fn(&mProvider, "Some Reals", realData,
+ COUNT, initialValues);
+
+ struct SetterData {
+ bool setterCalled;
+ double* propertyData;
+ };
+ SetterData setterData = {false, realData};
+ GesturesPropSetHandler setter{[](void* handlerData) {
+ SetterData* data = static_cast<SetterData*>(handlerData);
+ // Set handlers should be called after the property's data has changed, so check the data.
+ EXPECT_EQ(data->propertyData[0], 3.14);
+ EXPECT_EQ(data->propertyData[1], 0.7);
+ EXPECT_EQ(data->propertyData[2], -5.0);
+ data->setterCalled = true;
+ }};
+ gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &setterData,
+ nullptr, setter);
+
+ ASSERT_TRUE(mProvider.hasProperty("Some Reals"));
+ GesturesProp& prop = mProvider.getProperty("Some Reals");
+ prop.setRealValues({3.14, 0.7, -5.0});
+ EXPECT_THAT(realData, ElementsAre(3.14, 0.7, -5.0));
+ EXPECT_TRUE(setterData.setterCalled);
+ EXPECT_THAT(prop.getRealValues(), ElementsAre(3.14, 0.7, -5.0));
+}
+
+TEST_F(PropertyProviderTest, String_Create) {
+ const char* str = nullptr;
+ std::string initialValue = "Foo";
+ gesturePropProvider.create_string_fn(&mProvider, "A String", &str, initialValue.c_str());
+
+ ASSERT_TRUE(mProvider.hasProperty("A String"));
+ GesturesProp& prop = mProvider.getProperty("A String");
+ EXPECT_EQ(prop.getName(), "A String");
+ EXPECT_EQ(prop.getCount(), 1u);
+ EXPECT_STREQ(str, "Foo");
+}
+
+TEST_F(PropertyProviderTest, String_Get) {
+ const char* str = nullptr;
+ std::string initialValue = "Foo";
+ GesturesProp* propPtr = gesturePropProvider.create_string_fn(&mProvider, "A String", &str,
+ initialValue.c_str());
+
+ // Get handlers are supposed to be called before the property's data is accessed, so they can
+ // update it if necessary. This getter updates the values, so that the ordering can be checked.
+ struct GetterData {
+ const char** strPtr;
+ std::string newValue; // Have to store the new value outside getter so it stays allocated.
+ };
+ GetterData getterData = {&str, "Bar"};
+ GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool {
+ GetterData* data = static_cast<GetterData*>(handlerData);
+ *data->strPtr = data->newValue.c_str();
+ return true;
+ }};
+ gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &getterData,
+ getter, nullptr);
+
+ ASSERT_TRUE(mProvider.hasProperty("A String"));
+ GesturesProp& prop = mProvider.getProperty("A String");
+ EXPECT_EQ(prop.getStringValue(), "Bar");
+}
+
+TEST_F(PropertyProviderTest, Free) {
+ int intData = 0;
+ int initialValue = 42;
+ GesturesProp* propPtr =
+ gesturePropProvider.create_int_fn(&mProvider, "Foo", &intData, 1, &initialValue);
+ gesturePropProvider.free_fn(&mProvider, propPtr);
+
+ EXPECT_FALSE(mProvider.hasProperty("Foo"));
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
index 4c84160..e12f88e 100644
--- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
+++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
@@ -105,7 +105,7 @@
auto info = InputDeviceInfo();
info.initialize(DEVICE_ID, /*generation*/ 1, /*controllerNumber*/ 1, identifier, "alias",
- /*isExternal*/ false, /*hasMic*/ false);
+ /*isExternal*/ false, /*hasMic*/ false, ADISPLAY_ID_NONE);
info.addSource(AINPUT_SOURCE_TOUCHSCREEN);
info.addMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN, 0, 1599, /*flat*/ 0,
/*fuzz*/ 0, X_RESOLUTION);
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index e0dcab5..fe7cff7 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -157,6 +157,8 @@
"EventLog/EventLog.cpp",
"FrontEnd/LayerCreationArgs.cpp",
"FrontEnd/LayerHandle.cpp",
+ "FrontEnd/LayerSnapshot.cpp",
+ "FrontEnd/LayerSnapshotBuilder.cpp",
"FrontEnd/LayerHierarchy.cpp",
"FrontEnd/LayerLifecycleManager.cpp",
"FrontEnd/RequestedLayerState.cpp",
@@ -176,7 +178,6 @@
"RefreshRateOverlay.cpp",
"RegionSamplingThread.cpp",
"RenderArea.cpp",
- "Scheduler/DispSyncSource.cpp",
"Scheduler/EventThread.cpp",
"Scheduler/FrameRateOverrideMappings.cpp",
"Scheduler/OneShotTimer.cpp",
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 33caa7a..6199a5a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -112,6 +112,11 @@
MOCK_METHOD1(clearBootDisplayMode, status_t(PhysicalDisplayId));
MOCK_METHOD1(getPreferredBootDisplayMode, std::optional<hal::HWConfigId>(PhysicalDisplayId));
MOCK_METHOD0(getBootDisplayModeSupport, bool());
+ MOCK_CONST_METHOD0(
+ getHdrConversionCapabilities,
+ std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability>());
+ MOCK_METHOD1(setHdrConversionStrategy,
+ status_t(aidl::android::hardware::graphics::common::HdrConversionStrategy));
MOCK_METHOD2(setAutoLowLatencyMode, status_t(PhysicalDisplayId, bool));
MOCK_METHOD(status_t, getSupportedContentTypes,
(PhysicalDisplayId, std::vector<hal::ContentType>*), (const, override));
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 96ae77f..2b6a519 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -171,7 +171,7 @@
void DisplayDevice::setPowerMode(hal::PowerMode mode) {
if (mode == hal::PowerMode::OFF || mode == hal::PowerMode::ON) {
- if (mStagedBrightness && mBrightness != *mStagedBrightness) {
+ if (mStagedBrightness && mBrightness != mStagedBrightness) {
getCompositionDisplay()->setNextBrightness(*mStagedBrightness);
mBrightness = *mStagedBrightness;
}
@@ -298,7 +298,7 @@
}
void DisplayDevice::persistBrightness(bool needsComposite) {
- if (mStagedBrightness && mBrightness != *mStagedBrightness) {
+ if (mStagedBrightness && mBrightness != mStagedBrightness) {
if (needsComposite) {
getCompositionDisplay()->setNextBrightness(*mStagedBrightness);
}
@@ -410,7 +410,8 @@
capabilities.getDesiredMinLuminance());
}
-void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinner, bool showRenderRate) {
+void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinner, bool showRenderRate,
+ bool showInMiddle) {
if (!enable) {
mRefreshRateOverlay.reset();
return;
@@ -425,6 +426,10 @@
features |= RefreshRateOverlay::Features::RenderRate;
}
+ if (showInMiddle) {
+ features |= RefreshRateOverlay::Features::ShowInMiddle;
+ }
+
const auto fpsRange = mRefreshRateSelector->getSupportedRefreshRateRange();
mRefreshRateOverlay = std::make_unique<RefreshRateOverlay>(fpsRange, features);
mRefreshRateOverlay->setLayerStack(getLayerStack());
@@ -474,8 +479,9 @@
const auto& desiredMode = *info.modeOpt->modePtr;
// Check if we are already at the desired mode
- if (!force && refreshRateSelector().getActiveMode().modePtr->getId() == desiredMode.getId()) {
- if (refreshRateSelector().getActiveMode() == info.modeOpt) {
+ const auto currentMode = refreshRateSelector().getActiveMode();
+ if (!force && currentMode.modePtr->getId() == desiredMode.getId()) {
+ if (currentMode == info.modeOpt) {
return DesiredActiveModeAction::None;
}
@@ -483,6 +489,11 @@
return DesiredActiveModeAction::InitiateRenderRateSwitch;
}
+ // Set the render frame rate to the current physical refresh rate to schedule the next
+ // frame as soon as possible.
+ setActiveMode(currentMode.modePtr->getId(), currentMode.modePtr->getFps(),
+ currentMode.modePtr->getFps());
+
// Initiate a mode change.
mDesiredActiveModeChanged = true;
mDesiredActiveMode = info;
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index d757673..370bd66 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -237,8 +237,8 @@
}
// Enables an overlay to be displayed with the current refresh rate
- void enableRefreshRateOverlay(bool enable, bool showSpinner, bool showRenderRate)
- REQUIRES(kMainThreadContext);
+ void enableRefreshRateOverlay(bool enable, bool showSpinner, bool showRenderRate,
+ bool showInMiddle) REQUIRES(kMainThreadContext);
bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; }
bool onKernelTimerChanged(std::optional<DisplayModeId>, bool timerExpired);
void animateRefreshRateOverlay();
@@ -272,7 +272,7 @@
std::optional<hardware::graphics::composer::hal::PowerMode> mPowerMode;
std::optional<float> mStagedBrightness;
- float mBrightness = -1.f;
+ std::optional<float> mBrightness;
// TODO(b/182939859): Remove special cases for primary display.
const bool mIsPrimary;
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index e372b72..9470552 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -56,6 +56,9 @@
using AidlDisplayAttribute = aidl::android::hardware::graphics::composer3::DisplayAttribute;
using AidlDisplayCapability = aidl::android::hardware::graphics::composer3::DisplayCapability;
using AidlHdrCapabilities = aidl::android::hardware::graphics::composer3::HdrCapabilities;
+using AidlHdrConversionCapability =
+ aidl::android::hardware::graphics::common::HdrConversionCapability;
+using AidlHdrConversionStrategy = aidl::android::hardware::graphics::common::HdrConversionStrategy;
using AidlOverlayProperties = aidl::android::hardware::graphics::composer3::OverlayProperties;
using AidlPerFrameMetadata = aidl::android::hardware::graphics::composer3::PerFrameMetadata;
using AidlPerFrameMetadataKey = aidl::android::hardware::graphics::composer3::PerFrameMetadataKey;
@@ -1396,6 +1399,27 @@
return Error::NONE;
}
+Error AidlComposer::getHdrConversionCapabilities(
+ std::vector<AidlHdrConversionCapability>* hdrConversionCapabilities) {
+ const auto status =
+ mAidlComposerClient->getHdrConversionCapabilities(hdrConversionCapabilities);
+ if (!status.isOk()) {
+ hdrConversionCapabilities = {};
+ ALOGE("getHdrConversionCapabilities failed %s", status.getDescription().c_str());
+ return static_cast<Error>(status.getServiceSpecificError());
+ }
+ return Error::NONE;
+}
+
+Error AidlComposer::setHdrConversionStrategy(AidlHdrConversionStrategy hdrConversionStrategy) {
+ const auto status = mAidlComposerClient->setHdrConversionStrategy(hdrConversionStrategy);
+ if (!status.isOk()) {
+ ALOGE("setHdrConversionStrategy failed %s", status.getDescription().c_str());
+ return static_cast<Error>(status.getServiceSpecificError());
+ }
+ return Error::NONE;
+}
+
Error AidlComposer::getClientTargetProperty(
Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) {
Error error = Error::NONE;
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index 9a7ade7..a5ddf74 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -48,6 +48,8 @@
namespace android::Hwc2 {
using aidl::android::hardware::graphics::common::DisplayDecorationSupport;
+using aidl::android::hardware::graphics::common::HdrConversionCapability;
+using aidl::android::hardware::graphics::common::HdrConversionStrategy;
using aidl::android::hardware::graphics::composer3::ComposerClientReader;
using aidl::android::hardware::graphics::composer3::ComposerClientWriter;
using aidl::android::hardware::graphics::composer3::OverlayProperties;
@@ -235,6 +237,8 @@
AidlTransform* outDisplayOrientation) override;
void onHotplugConnect(Display) override;
void onHotplugDisconnect(Display) override;
+ Error getHdrConversionCapabilities(std::vector<HdrConversionCapability>*) override;
+ Error setHdrConversionStrategy(HdrConversionStrategy) override;
private:
// Many public functions above simply write a command into the command
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index 1c2b8b5..82b677e 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -32,6 +32,8 @@
#include <utils/StrongPointer.h>
#include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+#include <aidl/android/hardware/graphics/common/HdrConversionCapability.h>
+#include <aidl/android/hardware/graphics/common/HdrConversionStrategy.h>
#include <aidl/android/hardware/graphics/composer3/Capability.h>
#include <aidl/android/hardware/graphics/composer3/ClientTargetPropertyWithBrightness.h>
#include <aidl/android/hardware/graphics/composer3/Color.h>
@@ -288,6 +290,10 @@
virtual Error getOverlaySupport(V3_0::OverlayProperties* outProperties) = 0;
virtual void onHotplugConnect(Display) = 0;
virtual void onHotplugDisconnect(Display) = 0;
+ virtual Error getHdrConversionCapabilities(
+ std::vector<::aidl::android::hardware::graphics::common::HdrConversionCapability>*) = 0;
+ virtual Error setHdrConversionStrategy(
+ ::aidl::android::hardware::graphics::common::HdrConversionStrategy) = 0;
};
} // namespace Hwc2
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index 6738f00..aaf2523 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -842,25 +842,24 @@
mHdrMetadata.cta8613.maxFrameAverageLightLevel}});
}
- Error error = static_cast<Error>(
- mComposer.setLayerPerFrameMetadata(mDisplay->getId(), mId, perFrameMetadatas));
+ const Error error = static_cast<Error>(
+ mComposer.setLayerPerFrameMetadata(mDisplay->getId(), mId, perFrameMetadatas));
+ if (error != Error::NONE) {
+ return error;
+ }
+ std::vector<Hwc2::PerFrameMetadataBlob> perFrameMetadataBlobs;
if (validTypes & HdrMetadata::HDR10PLUS) {
if (CC_UNLIKELY(mHdrMetadata.hdr10plus.size() == 0)) {
return Error::BAD_PARAMETER;
}
- std::vector<Hwc2::PerFrameMetadataBlob> perFrameMetadataBlobs;
perFrameMetadataBlobs.push_back(
{Hwc2::PerFrameMetadataKey::HDR10_PLUS_SEI, mHdrMetadata.hdr10plus});
- Error setMetadataBlobsError =
- static_cast<Error>(mComposer.setLayerPerFrameMetadataBlobs(mDisplay->getId(), mId,
- perFrameMetadataBlobs));
- if (error == Error::NONE) {
- return setMetadataBlobsError;
- }
}
- return error;
+
+ return static_cast<Error>(
+ mComposer.setLayerPerFrameMetadataBlobs(mDisplay->getId(), mId, perFrameMetadataBlobs));
}
Error Layer::setDisplayFrame(const Rect& frame)
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 10fde2a..7dde6b4 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -70,6 +70,8 @@
#define RETURN_IF_HWC_ERROR(error, displayId, ...) \
RETURN_IF_HWC_ERROR_FOR(__FUNCTION__, error, displayId, __VA_ARGS__)
+using aidl::android::hardware::graphics::common::HdrConversionCapability;
+using aidl::android::hardware::graphics::common::HdrConversionStrategy;
using aidl::android::hardware::graphics::composer3::Capability;
using aidl::android::hardware::graphics::composer3::DisplayCapability;
namespace hal = android::hardware::graphics::composer::hal;
@@ -97,6 +99,7 @@
loadCapabilities();
loadLayerMetadataSupport();
loadOverlayProperties();
+ loadHdrConversionCapabilities();
if (mRegisteredCallback) {
ALOGW("Callback already registered. Ignored extra registration attempt.");
@@ -787,6 +790,18 @@
return displayModeId;
}
+std::vector<HdrConversionCapability> HWComposer::getHdrConversionCapabilities() const {
+ return mHdrConversionCapabilities;
+}
+
+status_t HWComposer::setHdrConversionStrategy(HdrConversionStrategy hdrConversionStrategy) {
+ const auto error = mComposer->setHdrConversionStrategy(hdrConversionStrategy);
+ if (error != hal::Error::NONE) {
+ ALOGE("Error in setting HDR conversion strategy %s", to_string(error).c_str());
+ }
+ return NO_ERROR;
+}
+
status_t HWComposer::getDisplayDecorationSupport(
PhysicalDisplayId displayId,
std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
@@ -979,6 +994,14 @@
mComposer->getOverlaySupport(&mOverlayProperties);
}
+void HWComposer::loadHdrConversionCapabilities() {
+ const auto error = mComposer->getHdrConversionCapabilities(&mHdrConversionCapabilities);
+ if (error != hal::Error::NONE) {
+ ALOGE("Error in fetching HDR conversion capabilities %s", to_string(error).c_str());
+ mHdrConversionCapabilities = {};
+ }
+}
+
status_t HWComposer::setIdleTimerEnabled(PhysicalDisplayId displayId,
std::chrono::milliseconds timeout) {
ATRACE_CALL();
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 78d4a68..f6155d2 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -44,6 +44,8 @@
#include "Hal.h"
#include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+#include <aidl/android/hardware/graphics/common/HdrConversionCapability.h>
+#include <aidl/android/hardware/graphics/common/HdrConversionStrategy.h>
#include <aidl/android/hardware/graphics/composer3/Capability.h>
#include <aidl/android/hardware/graphics/composer3/ClientTargetPropertyWithBrightness.h>
#include <aidl/android/hardware/graphics/composer3/Composition.h>
@@ -286,6 +288,10 @@
virtual status_t setIdleTimerEnabled(PhysicalDisplayId, std::chrono::milliseconds timeout) = 0;
virtual bool hasDisplayIdleTimerCapability(PhysicalDisplayId) const = 0;
virtual Hwc2::AidlTransform getPhysicalDisplayOrientation(PhysicalDisplayId) const = 0;
+ virtual std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability>
+ getHdrConversionCapabilities() const = 0;
+ virtual status_t setHdrConversionStrategy(
+ aidl::android::hardware::graphics::common::HdrConversionStrategy) = 0;
};
static inline bool operator==(const android::HWComposer::DeviceRequestedChanges& lhs,
@@ -437,6 +443,10 @@
status_t setIdleTimerEnabled(PhysicalDisplayId, std::chrono::milliseconds timeout) override;
bool hasDisplayIdleTimerCapability(PhysicalDisplayId) const override;
Hwc2::AidlTransform getPhysicalDisplayOrientation(PhysicalDisplayId) const override;
+ std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability>
+ getHdrConversionCapabilities() const override;
+ status_t setHdrConversionStrategy(
+ aidl::android::hardware::graphics::common::HdrConversionStrategy) override;
// for debugging ----------------------------------------------------------
void dump(std::string& out) const override;
@@ -490,12 +500,16 @@
void loadCapabilities();
void loadLayerMetadataSupport();
void loadOverlayProperties();
+ void loadHdrConversionCapabilities();
std::unordered_map<HalDisplayId, DisplayData> mDisplayData;
std::unique_ptr<android::Hwc2::Composer> mComposer;
std::unordered_set<aidl::android::hardware::graphics::composer3::Capability> mCapabilities;
aidl::android::hardware::graphics::composer3::OverlayProperties mOverlayProperties;
+ std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability>
+ mHdrConversionCapabilities = {};
+
std::unordered_map<std::string, bool> mSupportedLayerGenericMetadata;
bool mRegisteredCallback = false;
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index c9e1e79..6fdb2d7 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -36,6 +36,8 @@
#include <algorithm>
#include <cinttypes>
+using aidl::android::hardware::graphics::common::HdrConversionCapability;
+using aidl::android::hardware::graphics::common::HdrConversionStrategy;
using aidl::android::hardware::graphics::composer3::Capability;
using aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness;
using aidl::android::hardware::graphics::composer3::DimmingStage;
@@ -1348,6 +1350,14 @@
return Error::UNSUPPORTED;
}
+Error HidlComposer::getHdrConversionCapabilities(std::vector<HdrConversionCapability>*) {
+ return Error::UNSUPPORTED;
+}
+
+Error HidlComposer::setHdrConversionStrategy(HdrConversionStrategy) {
+ return Error::UNSUPPORTED;
+}
+
Error HidlComposer::getClientTargetProperty(
Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) {
IComposerClient::ClientTargetProperty property;
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index 921add5..8280af2 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -343,6 +343,11 @@
AidlTransform* outDisplayOrientation) override;
void onHotplugConnect(Display) override;
void onHotplugDisconnect(Display) override;
+ Error getHdrConversionCapabilities(
+ std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability>*)
+ override;
+ Error setHdrConversionStrategy(
+ aidl::android::hardware::graphics::common::HdrConversionStrategy) override;
private:
class CommandWriter : public CommandWriterBase {
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index 31074b1..d54d22d 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -314,12 +314,12 @@
virtual void parseArgs(const Vector<String16>& args, std::string& result) = 0;
// Sets the max number of display frames that can be stored. Called by SF backdoor.
- virtual void setMaxDisplayFrames(uint32_t size);
+ virtual void setMaxDisplayFrames(uint32_t size) = 0;
// Computes the historical fps for the provided set of layer IDs
// The fps is compted from the linear timeline of present timestamps for DisplayFrames
// containing at least one layer ID.
- virtual float computeFps(const std::unordered_set<int32_t>& layerIds);
+ virtual float computeFps(const std::unordered_set<int32_t>& layerIds) = 0;
// Restores the max number of display frames to default. Called by SF backdoor.
virtual void reset() = 0;
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
index db4e8af..514a642 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -131,7 +131,7 @@
std::string LayerHierarchy::getDebugStringShort() const {
std::string debug = "LayerHierarchy{";
- debug += ((mLayer) ? mLayer->getDebugStringShort() : "root") + " ";
+ debug += ((mLayer) ? mLayer->getDebugString() : "root") + " ";
if (mChildren.empty()) {
debug += "no children";
} else {
@@ -401,10 +401,13 @@
return it->second;
}
-LayerHierarchy::TraversalPath LayerHierarchy::TraversalPath::ROOT_TRAVERSAL_ID =
+const LayerHierarchy::TraversalPath LayerHierarchy::TraversalPath::ROOT =
{.id = UNASSIGNED_LAYER_ID, .variant = LayerHierarchy::Attached};
std::string LayerHierarchy::TraversalPath::toString() const {
+ if (id == UNASSIGNED_LAYER_ID) {
+ return "TraversalPath{ROOT}";
+ }
std::string debugString = "TraversalPath{.id = " + std::to_string(id);
if (!mirrorRootIds.empty()) {
@@ -437,20 +440,22 @@
LayerHierarchy::Variant variant)
: mTraversalPath(traversalPath),
mParentId(traversalPath.id),
- mParentVariant(traversalPath.variant) {
+ mParentVariant(traversalPath.variant),
+ mParentDetached(traversalPath.detached) {
// Update the traversal id with the child layer id and variant. Parent id and variant are
// stored to reset the id upon destruction.
traversalPath.id = layerId;
traversalPath.variant = variant;
if (variant == LayerHierarchy::Variant::Mirror) {
traversalPath.mirrorRootIds.emplace_back(layerId);
- }
- if (variant == LayerHierarchy::Variant::Relative) {
+ } else if (variant == LayerHierarchy::Variant::Relative) {
if (std::find(traversalPath.relativeRootIds.begin(), traversalPath.relativeRootIds.end(),
layerId) != traversalPath.relativeRootIds.end()) {
traversalPath.invalidRelativeRootId = layerId;
}
traversalPath.relativeRootIds.emplace_back(layerId);
+ } else if (variant == LayerHierarchy::Variant::Detached) {
+ traversalPath.detached = true;
}
}
LayerHierarchy::ScopedAddToTraversalPath::~ScopedAddToTraversalPath() {
@@ -458,8 +463,7 @@
// the constructor.
if (mTraversalPath.variant == LayerHierarchy::Variant::Mirror) {
mTraversalPath.mirrorRootIds.pop_back();
- }
- if (mTraversalPath.variant == LayerHierarchy::Variant::Relative) {
+ } else if (mTraversalPath.variant == LayerHierarchy::Variant::Relative) {
mTraversalPath.relativeRootIds.pop_back();
}
if (mTraversalPath.invalidRelativeRootId == mTraversalPath.id) {
@@ -467,6 +471,7 @@
}
mTraversalPath.id = mParentId;
mTraversalPath.variant = mParentVariant;
+ mTraversalPath.detached = mParentDetached;
}
} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
index f83a859..8cdc240 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
@@ -63,15 +63,22 @@
// First duplicate relative root id found. If this is a valid layer id that means we are
// in a loop.
uint32_t invalidRelativeRootId = UNASSIGNED_LAYER_ID;
+ // See isAttached()
+ bool detached = false;
bool hasRelZLoop() const { return invalidRelativeRootId != UNASSIGNED_LAYER_ID; }
- bool isRelative() { return !relativeRootIds.empty(); }
+ // Returns true if this node is reached via one or more relative parents.
+ bool isRelative() const { return !relativeRootIds.empty(); }
+ // Returns true if the node or its parents are not Detached.
+ bool isAttached() const { return !detached; }
+ // Returns true if the node is a clone.
+ bool isClone() const { return !mirrorRootIds.empty(); }
bool operator==(const TraversalPath& other) const {
return id == other.id && mirrorRootIds == other.mirrorRootIds;
}
std::string toString() const;
- static TraversalPath ROOT_TRAVERSAL_ID;
+ static const TraversalPath ROOT;
};
// Helper class to add nodes to an existing traversal id and removes the
@@ -86,6 +93,7 @@
TraversalPath& mTraversalPath;
uint32_t mParentId;
LayerHierarchy::Variant mParentVariant;
+ bool mParentDetached;
};
LayerHierarchy(RequestedLayerState* layer);
@@ -98,12 +106,14 @@
// Traverse the hierarchy and visit all child variants.
void traverse(const Visitor& visitor) const {
- traverse(visitor, TraversalPath::ROOT_TRAVERSAL_ID);
+ TraversalPath root = TraversalPath::ROOT;
+ traverse(visitor, root);
}
// Traverse the hierarchy in z-order, skipping children that have relative parents.
void traverseInZOrder(const Visitor& visitor) const {
- traverseInZOrder(visitor, TraversalPath::ROOT_TRAVERSAL_ID);
+ TraversalPath root = TraversalPath::ROOT;
+ traverseInZOrder(visitor, root);
}
const RequestedLayerState* getLayer() const;
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
index fdf60b3..5514c06 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -210,10 +210,7 @@
layer->touchCropId = linkLayer(layer->touchCropId, layer->id);
}
- mGlobalChanges |= layer->changes &
- (RequestedLayerState::Changes::Hierarchy |
- RequestedLayerState::Changes::Geometry |
- RequestedLayerState::Changes::Content);
+ mGlobalChanges |= layer->changes;
}
}
}
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
new file mode 100644
index 0000000..d483a99
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2022 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 ATRACE_TAG ATRACE_TAG_GRAPHICS
+#undef LOG_TAG
+#define LOG_TAG "LayerSnapshot"
+
+#include "LayerSnapshot.h"
+
+namespace android::surfaceflinger::frontend {
+
+using namespace ftl::flag_operators;
+
+LayerSnapshot::LayerSnapshot(const RequestedLayerState& state,
+ const LayerHierarchy::TraversalPath& path)
+ : path(path) {
+ sequence = static_cast<int32_t>(state.id);
+ name = state.name;
+ textureName = state.textureName;
+ premultipliedAlpha = state.premultipliedAlpha;
+ inputInfo.name = state.name;
+ inputInfo.id = static_cast<int32_t>(state.id);
+ inputInfo.ownerUid = static_cast<int32_t>(state.ownerUid);
+ inputInfo.ownerPid = state.ownerPid;
+}
+
+// As documented in libhardware header, formats in the range
+// 0x100 - 0x1FF are specific to the HAL implementation, and
+// are known to have no alpha channel
+// TODO: move definition for device-specific range into
+// hardware.h, instead of using hard-coded values here.
+#define HARDWARE_IS_DEVICE_FORMAT(f) ((f) >= 0x100 && (f) <= 0x1FF)
+
+bool LayerSnapshot::isOpaqueFormat(PixelFormat format) {
+ if (HARDWARE_IS_DEVICE_FORMAT(format)) {
+ return true;
+ }
+ switch (format) {
+ case PIXEL_FORMAT_RGBA_8888:
+ case PIXEL_FORMAT_BGRA_8888:
+ case PIXEL_FORMAT_RGBA_FP16:
+ case PIXEL_FORMAT_RGBA_1010102:
+ case PIXEL_FORMAT_R_8:
+ return false;
+ }
+ // in all other case, we have no blending (also for unknown formats)
+ return true;
+}
+
+bool LayerSnapshot::hasBufferOrSidebandStream() const {
+ return ((sidebandStream != nullptr) || (buffer != nullptr));
+}
+
+bool LayerSnapshot::drawShadows() const {
+ return shadowSettings.length > 0.f;
+}
+
+bool LayerSnapshot::fillsColor() const {
+ return !hasBufferOrSidebandStream() && color.r >= 0.0_hf && color.g >= 0.0_hf &&
+ color.b >= 0.0_hf;
+}
+
+bool LayerSnapshot::hasBlur() const {
+ return backgroundBlurRadius > 0 || blurRegions.size() > 0;
+}
+
+bool LayerSnapshot::hasEffect() const {
+ return fillsColor() || drawShadows() || hasBlur();
+}
+
+bool LayerSnapshot::hasSomethingToDraw() const {
+ return hasEffect() || hasBufferOrSidebandStream();
+}
+
+bool LayerSnapshot::isContentOpaque() const {
+ // if we don't have a buffer or sidebandStream yet, we're translucent regardless of the
+ // layer's opaque flag.
+ if (!hasSomethingToDraw()) {
+ return false;
+ }
+
+ // if the layer has the opaque flag, then we're always opaque
+ if (layerOpaqueFlagSet) {
+ return true;
+ }
+
+ // If the buffer has no alpha channel, then we are opaque
+ if (hasBufferOrSidebandStream() &&
+ isOpaqueFormat(buffer ? buffer->getPixelFormat() : PIXEL_FORMAT_NONE)) {
+ return true;
+ }
+
+ // Lastly consider the layer opaque if drawing a color with alpha == 1.0
+ return fillsColor() && color.a == 1.0_hf;
+}
+
+bool LayerSnapshot::isHiddenByPolicy() const {
+ if (CC_UNLIKELY(invalidTransform)) {
+ ALOGW("Hide layer %s because it has invalid transformation.", name.c_str());
+ return true;
+ }
+ return isHiddenByPolicyFromParent || isHiddenByPolicyFromRelativeParent;
+}
+
+bool LayerSnapshot::getIsVisible() const {
+ if (!hasSomethingToDraw()) {
+ return false;
+ }
+
+ if (isHiddenByPolicy()) {
+ return false;
+ }
+
+ return color.a > 0.0f || hasBlur();
+}
+
+std::string LayerSnapshot::getIsVisibleReason() const {
+ if (!hasSomethingToDraw()) {
+ return "!hasSomethingToDraw";
+ }
+
+ if (isHiddenByPolicy()) {
+ return "isHiddenByPolicy";
+ }
+
+ if (color.a > 0.0f || hasBlur()) {
+ return "";
+ }
+
+ return "alpha = 0 and !hasBlur";
+}
+
+bool LayerSnapshot::canReceiveInput() const {
+ return !isHiddenByPolicy() && (!hasBufferOrSidebandStream() || color.a > 0.0f);
+}
+
+bool LayerSnapshot::isTransformValid(const ui::Transform& t) {
+ float transformDet = t.det();
+ return transformDet != 0 && !isinf(transformDet) && !isnan(transformDet);
+}
+
+std::string LayerSnapshot::getDebugString() const {
+ return "Snapshot(" + base::StringPrintf("%p", this) + "){" + path.toString() + name +
+ " isHidden=" + std::to_string(isHiddenByPolicyFromParent) +
+ " isHiddenRelative=" + std::to_string(isHiddenByPolicyFromRelativeParent) +
+ " isVisible=" + std::to_string(isVisible) + " " + getIsVisibleReason() + "}";
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
new file mode 100644
index 0000000..d14bd3a
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <compositionengine/LayerFECompositionState.h>
+#include <renderengine/LayerSettings.h>
+#include "LayerHierarchy.h"
+#include "RequestedLayerState.h"
+#include "android-base/stringprintf.h"
+
+namespace android::surfaceflinger::frontend {
+
+struct RoundedCornerState {
+ RoundedCornerState() = default;
+ RoundedCornerState(const FloatRect& cropRect, const vec2& radius)
+ : cropRect(cropRect), radius(radius) {}
+
+ // Rounded rectangle in local layer coordinate space.
+ FloatRect cropRect = FloatRect();
+ // Radius of the rounded rectangle.
+ vec2 radius;
+ bool hasRoundedCorners() const { return radius.x > 0.0f && radius.y > 0.0f; }
+ bool operator==(RoundedCornerState const& rhs) const {
+ return cropRect == rhs.cropRect && radius == rhs.radius;
+ }
+};
+
+// LayerSnapshot stores Layer state used by CompositionEngine and RenderEngine. Composition
+// Engine uses a pointer to LayerSnapshot (as LayerFECompositionState*) and the LayerSettings
+// passed to Render Engine are created using properties stored on this struct.
+struct LayerSnapshot : public compositionengine::LayerFECompositionState {
+ LayerSnapshot() = default;
+ LayerSnapshot(const RequestedLayerState&, const LayerHierarchy::TraversalPath&);
+
+ LayerHierarchy::TraversalPath path;
+ size_t globalZ = std::numeric_limits<ssize_t>::max();
+ bool invalidTransform = false;
+ bool isHiddenByPolicyFromParent = false;
+ bool isHiddenByPolicyFromRelativeParent = false;
+ ftl::Flags<RequestedLayerState::Changes> changes;
+ int32_t sequence;
+ std::string name;
+ uint32_t textureName;
+ bool contentOpaque;
+ bool layerOpaqueFlagSet;
+ RoundedCornerState roundedCorner;
+ FloatRect transformedBounds;
+ renderengine::ShadowSettings shadowSettings;
+ bool premultipliedAlpha;
+ bool isHdrY410;
+ bool bufferNeedsFiltering;
+ ui::Transform parentTransform;
+ Rect bufferSize;
+ Rect croppedBufferSize;
+ std::shared_ptr<renderengine::ExternalTexture> externalTexture;
+ gui::LayerMetadata layerMetadata;
+ gui::LayerMetadata relativeLayerMetadata;
+ bool hasReadyFrame;
+ ui::Transform localTransformInverse;
+ gui::WindowInfo inputInfo;
+ ui::Transform localTransform;
+ gui::DropInputMode dropInputMode;
+ bool isTrustedOverlay;
+
+ static bool isOpaqueFormat(PixelFormat format);
+ static bool isTransformValid(const ui::Transform& t);
+
+ bool canReceiveInput() const;
+ bool drawShadows() const;
+ bool fillsColor() const;
+ bool getIsVisible() const;
+ bool hasBlur() const;
+ bool hasBufferOrSidebandStream() const;
+ bool hasEffect() const;
+ bool hasSomethingToDraw() const;
+ bool isContentOpaque() const;
+ bool isHiddenByPolicy() const;
+ std::string getDebugString() const;
+ std::string getIsVisibleReason() const;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
new file mode 100644
index 0000000..bff12d7
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -0,0 +1,833 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// #define LOG_NDEBUG 0
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#undef LOG_TAG
+#define LOG_TAG "LayerSnapshotBuilder"
+
+#include "LayerSnapshotBuilder.h"
+#include <gui/TraceUtils.h>
+#include <numeric>
+#include "DisplayHardware/HWC2.h"
+#include "DisplayHardware/Hal.h"
+#include "ftl/small_map.h"
+
+namespace android::surfaceflinger::frontend {
+
+using namespace ftl::flag_operators;
+
+namespace {
+FloatRect getMaxDisplayBounds(
+ const display::DisplayMap<ui::LayerStack, frontend::DisplayInfo>& displays) {
+ const ui::Size maxSize = [&displays] {
+ if (displays.empty()) return ui::Size{5000, 5000};
+
+ return std::accumulate(displays.begin(), displays.end(), ui::kEmptySize,
+ [](ui::Size size, const auto& pair) -> ui::Size {
+ const auto& display = pair.second;
+ return {std::max(size.getWidth(), display.info.logicalWidth),
+ std::max(size.getHeight(), display.info.logicalHeight)};
+ });
+ }();
+
+ // Ignore display bounds for now since they will be computed later. Use a large Rect bound
+ // to ensure it's bigger than an actual display will be.
+ const float xMax = static_cast<float>(maxSize.getWidth()) * 10.f;
+ const float yMax = static_cast<float>(maxSize.getHeight()) * 10.f;
+
+ return {-xMax, -yMax, xMax, yMax};
+}
+
+// Applies the given transform to the region, while protecting against overflows caused by any
+// offsets. If applying the offset in the transform to any of the Rects in the region would result
+// in an overflow, they are not added to the output Region.
+Region transformTouchableRegionSafely(const ui::Transform& t, const Region& r,
+ const std::string& debugWindowName) {
+ // Round the translation using the same rounding strategy used by ui::Transform.
+ const auto tx = static_cast<int32_t>(t.tx() + 0.5);
+ const auto ty = static_cast<int32_t>(t.ty() + 0.5);
+
+ ui::Transform transformWithoutOffset = t;
+ transformWithoutOffset.set(0.f, 0.f);
+
+ const Region transformed = transformWithoutOffset.transform(r);
+
+ // Apply the translation to each of the Rects in the region while discarding any that overflow.
+ Region ret;
+ for (const auto& rect : transformed) {
+ Rect newRect;
+ if (__builtin_add_overflow(rect.left, tx, &newRect.left) ||
+ __builtin_add_overflow(rect.top, ty, &newRect.top) ||
+ __builtin_add_overflow(rect.right, tx, &newRect.right) ||
+ __builtin_add_overflow(rect.bottom, ty, &newRect.bottom)) {
+ ALOGE("Applying transform to touchable region of window '%s' resulted in an overflow.",
+ debugWindowName.c_str());
+ continue;
+ }
+ ret.orSelf(newRect);
+ }
+ return ret;
+}
+
+/*
+ * We don't want to send the layer's transform to input, but rather the
+ * parent's transform. This is because Layer's transform is
+ * information about how the buffer is placed on screen. The parent's
+ * transform makes more sense to send since it's information about how the
+ * layer is placed on screen. This transform is used by input to determine
+ * how to go from screen space back to window space.
+ */
+ui::Transform getInputTransform(const LayerSnapshot& snapshot) {
+ if (!snapshot.hasBufferOrSidebandStream()) {
+ return snapshot.geomLayerTransform;
+ }
+ return snapshot.parentTransform;
+}
+
+/**
+ * Similar to getInputTransform, we need to update the bounds to include the transform.
+ * This is because bounds don't include the buffer transform, where the input assumes
+ * that's already included.
+ */
+Rect getInputBounds(const LayerSnapshot& snapshot) {
+ if (!snapshot.hasBufferOrSidebandStream()) {
+ return snapshot.croppedBufferSize;
+ }
+
+ if (snapshot.localTransform.getType() == ui::Transform::IDENTITY ||
+ !snapshot.croppedBufferSize.isValid()) {
+ return snapshot.croppedBufferSize;
+ }
+ return snapshot.localTransform.transform(snapshot.croppedBufferSize);
+}
+
+void fillInputFrameInfo(gui::WindowInfo& info, const ui::Transform& screenToDisplay,
+ const LayerSnapshot& snapshot) {
+ Rect tmpBounds = getInputBounds(snapshot);
+ if (!tmpBounds.isValid()) {
+ info.touchableRegion.clear();
+ // A layer could have invalid input bounds and still expect to receive touch input if it has
+ // replaceTouchableRegionWithCrop. For that case, the input transform needs to be calculated
+ // correctly to determine the coordinate space for input events. Use an empty rect so that
+ // the layer will receive input in its own layer space.
+ tmpBounds = Rect::EMPTY_RECT;
+ }
+
+ // InputDispatcher works in the display device's coordinate space. Here, we calculate the
+ // frame and transform used for the layer, which determines the bounds and the coordinate space
+ // within which the layer will receive input.
+ //
+ // The coordinate space within which each of the bounds are specified is explicitly documented
+ // in the variable name. For example "inputBoundsInLayer" is specified in layer space. A
+ // Transform converts one coordinate space to another, which is apparent in its naming. For
+ // example, "layerToDisplay" transforms layer space to display space.
+ //
+ // Coordinate space definitions:
+ // - display: The display device's coordinate space. Correlates to pixels on the display.
+ // - screen: The post-rotation coordinate space for the display, a.k.a. logical display space.
+ // - layer: The coordinate space of this layer.
+ // - input: The coordinate space in which this layer will receive input events. This could be
+ // different than layer space if a surfaceInset is used, which changes the origin
+ // of the input space.
+ const FloatRect inputBoundsInLayer = tmpBounds.toFloatRect();
+
+ // Clamp surface inset to the input bounds.
+ const auto surfaceInset = static_cast<float>(info.surfaceInset);
+ const float xSurfaceInset =
+ std::max(0.f, std::min(surfaceInset, inputBoundsInLayer.getWidth() / 2.f));
+ const float ySurfaceInset =
+ std::max(0.f, std::min(surfaceInset, inputBoundsInLayer.getHeight() / 2.f));
+
+ // Apply the insets to the input bounds.
+ const FloatRect insetBoundsInLayer(inputBoundsInLayer.left + xSurfaceInset,
+ inputBoundsInLayer.top + ySurfaceInset,
+ inputBoundsInLayer.right - xSurfaceInset,
+ inputBoundsInLayer.bottom - ySurfaceInset);
+
+ // Crop the input bounds to ensure it is within the parent's bounds.
+ const FloatRect croppedInsetBoundsInLayer =
+ snapshot.geomLayerBounds.intersect(insetBoundsInLayer);
+
+ const ui::Transform layerToScreen = getInputTransform(snapshot);
+ const ui::Transform layerToDisplay = screenToDisplay * layerToScreen;
+
+ const Rect roundedFrameInDisplay{layerToDisplay.transform(croppedInsetBoundsInLayer)};
+ info.frameLeft = roundedFrameInDisplay.left;
+ info.frameTop = roundedFrameInDisplay.top;
+ info.frameRight = roundedFrameInDisplay.right;
+ info.frameBottom = roundedFrameInDisplay.bottom;
+
+ ui::Transform inputToLayer;
+ inputToLayer.set(insetBoundsInLayer.left, insetBoundsInLayer.top);
+ const ui::Transform inputToDisplay = layerToDisplay * inputToLayer;
+
+ // InputDispatcher expects a display-to-input transform.
+ info.transform = inputToDisplay.inverse();
+
+ // The touchable region is specified in the input coordinate space. Change it to display space.
+ info.touchableRegion =
+ transformTouchableRegionSafely(inputToDisplay, info.touchableRegion, snapshot.name);
+}
+
+void handleDropInputMode(LayerSnapshot& snapshot, const LayerSnapshot& parentSnapshot) {
+ if (snapshot.inputInfo.inputConfig.test(gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL)) {
+ return;
+ }
+
+ // Check if we need to drop input unconditionally
+ const gui::DropInputMode dropInputMode = snapshot.dropInputMode;
+ if (dropInputMode == gui::DropInputMode::ALL) {
+ snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT;
+ ALOGV("Dropping input for %s as requested by policy.", snapshot.name.c_str());
+ return;
+ }
+
+ // Check if we need to check if the window is obscured by parent
+ if (dropInputMode != gui::DropInputMode::OBSCURED) {
+ return;
+ }
+
+ // Check if the parent has set an alpha on the layer
+ if (parentSnapshot.color.a != 1.0_hf) {
+ snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT;
+ ALOGV("Dropping input for %s as requested by policy because alpha=%f",
+ snapshot.name.c_str(), static_cast<float>(parentSnapshot.color.a));
+ }
+
+ // Check if the parent has cropped the buffer
+ Rect bufferSize = snapshot.croppedBufferSize;
+ if (!bufferSize.isValid()) {
+ snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED;
+ return;
+ }
+
+ // Screenbounds are the layer bounds cropped by parents, transformed to screenspace.
+ // To check if the layer has been cropped, we take the buffer bounds, apply the local
+ // layer crop and apply the same set of transforms to move to screenspace. If the bounds
+ // match then the layer has not been cropped by its parents.
+ Rect bufferInScreenSpace(snapshot.geomLayerTransform.transform(bufferSize));
+ bool croppedByParent = bufferInScreenSpace != Rect{snapshot.transformedBounds};
+
+ if (croppedByParent) {
+ snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT;
+ ALOGV("Dropping input for %s as requested by policy because buffer is cropped by parent",
+ snapshot.name.c_str());
+ } else {
+ // If the layer is not obscured by its parents (by setting an alpha or crop), then only drop
+ // input if the window is obscured. This check should be done in surfaceflinger but the
+ // logic currently resides in inputflinger. So pass the if_obscured check to input to only
+ // drop input events if the window is obscured.
+ snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED;
+ }
+}
+
+bool getBufferNeedsFiltering(const LayerSnapshot& snapshot, const ui::Size& unrotatedBufferSize) {
+ const int32_t layerWidth = static_cast<int32_t>(snapshot.geomLayerBounds.getWidth());
+ const int32_t layerHeight = static_cast<int32_t>(snapshot.geomLayerBounds.getHeight());
+ return layerWidth != unrotatedBufferSize.width || layerHeight != unrotatedBufferSize.height;
+}
+
+auto getBlendMode(const LayerSnapshot& snapshot, const RequestedLayerState& requested) {
+ auto blendMode = Hwc2::IComposerClient::BlendMode::NONE;
+ if (snapshot.alpha != 1.0f || !snapshot.isContentOpaque()) {
+ blendMode = requested.premultipliedAlpha ? Hwc2::IComposerClient::BlendMode::PREMULTIPLIED
+ : Hwc2::IComposerClient::BlendMode::COVERAGE;
+ }
+ return blendMode;
+}
+
+} // namespace
+
+LayerSnapshot LayerSnapshotBuilder::getRootSnapshot() {
+ LayerSnapshot snapshot;
+ snapshot.changes = ftl::Flags<RequestedLayerState::Changes>();
+ snapshot.isHiddenByPolicyFromParent = false;
+ snapshot.isHiddenByPolicyFromRelativeParent = false;
+ snapshot.parentTransform.reset();
+ snapshot.geomLayerTransform.reset();
+ snapshot.geomInverseLayerTransform.reset();
+ snapshot.geomLayerBounds = getMaxDisplayBounds({});
+ snapshot.roundedCorner = RoundedCornerState();
+ snapshot.stretchEffect = {};
+ snapshot.outputFilter.layerStack = ui::DEFAULT_LAYER_STACK;
+ snapshot.outputFilter.toInternalDisplay = false;
+ snapshot.isSecure = false;
+ snapshot.color.a = 1.0_hf;
+ snapshot.colorTransformIsIdentity = true;
+ snapshot.shadowRadius = 0.f;
+ snapshot.layerMetadata.mMap.clear();
+ snapshot.relativeLayerMetadata.mMap.clear();
+ snapshot.inputInfo.touchOcclusionMode = gui::TouchOcclusionMode::BLOCK_UNTRUSTED;
+ snapshot.dropInputMode = gui::DropInputMode::NONE;
+ snapshot.isTrustedOverlay = false;
+ return snapshot;
+}
+
+LayerSnapshotBuilder::LayerSnapshotBuilder() : mRootSnapshot(getRootSnapshot()) {}
+
+LayerSnapshotBuilder::LayerSnapshotBuilder(Args args) : LayerSnapshotBuilder() {
+ args.forceUpdate = true;
+ updateSnapshots(args);
+}
+
+bool LayerSnapshotBuilder::tryFastUpdate(const Args& args) {
+ if (args.forceUpdate) {
+ // force update requested, so skip the fast path
+ return false;
+ }
+
+ if (args.layerLifecycleManager.getGlobalChanges().get() == 0) {
+ // there are no changes, so just clear the change flags from before.
+ for (auto& snapshot : mSnapshots) {
+ snapshot->changes.clear();
+ snapshot->contentDirty = false;
+ }
+ return true;
+ }
+
+ if (args.layerLifecycleManager.getGlobalChanges() != RequestedLayerState::Changes::Content) {
+ // We have changes that require us to walk the hierarchy and update child layers.
+ // No fast path for you.
+ return false;
+ }
+
+ // There are only content changes which do not require any child layer snapshots to be updated.
+ ALOGV("%s", __func__);
+ ATRACE_NAME("FastPath");
+
+ // Collect layers with changes
+ ftl::SmallMap<uint32_t, RequestedLayerState*, 10> layersWithChanges;
+ for (auto& layer : args.layerLifecycleManager.getLayers()) {
+ if (layer->changes.test(RequestedLayerState::Changes::Content)) {
+ layersWithChanges.emplace_or_replace(layer->id, layer.get());
+ }
+ }
+
+ // Walk through the snapshots, clearing previous change flags and updating the snapshots
+ // if needed.
+ for (auto& snapshot : mSnapshots) {
+ snapshot->changes.clear();
+ snapshot->contentDirty = false;
+ auto it = layersWithChanges.find(snapshot->path.id);
+ if (it != layersWithChanges.end()) {
+ ALOGV("%s fast path snapshot changes = %s", __func__,
+ mRootSnapshot.changes.string().c_str());
+ LayerHierarchy::TraversalPath root = LayerHierarchy::TraversalPath::ROOT;
+ updateSnapshot(*snapshot, args, *it->second, mRootSnapshot, root);
+ }
+ }
+ return true;
+}
+
+void LayerSnapshotBuilder::updateSnapshots(const Args& args) {
+ ATRACE_NAME("UpdateSnapshots");
+ ALOGV("%s updateSnapshots force = %s", __func__, std::to_string(args.forceUpdate).c_str());
+ if (args.forceUpdate || args.displayChanges) {
+ mRootSnapshot.geomLayerBounds = getMaxDisplayBounds(args.displays);
+ }
+ if (args.displayChanges) {
+ mRootSnapshot.changes = RequestedLayerState::Changes::AffectsChildren |
+ RequestedLayerState::Changes::Geometry;
+ }
+ LayerHierarchy::TraversalPath root = LayerHierarchy::TraversalPath::ROOT;
+ for (auto& [childHierarchy, variant] : args.root.mChildren) {
+ LayerHierarchy::ScopedAddToTraversalPath addChildToPath(root,
+ childHierarchy->getLayer()->id,
+ variant);
+ updateSnapshotsInHierarchy(args, *childHierarchy, root, mRootSnapshot);
+ }
+
+ sortSnapshotsByZ(args);
+ mRootSnapshot.changes.clear();
+
+ // Destroy unreachable snapshots
+ if (args.layerLifecycleManager.getDestroyedLayers().empty()) {
+ return;
+ }
+
+ std::unordered_set<uint32_t> destroyedLayerIds;
+ for (auto& destroyedLayer : args.layerLifecycleManager.getDestroyedLayers()) {
+ destroyedLayerIds.emplace(destroyedLayer->id);
+ }
+ auto it = mSnapshots.begin();
+ while (it < mSnapshots.end()) {
+ auto& traversalPath = it->get()->path;
+ if (destroyedLayerIds.find(traversalPath.id) == destroyedLayerIds.end()) {
+ it++;
+ continue;
+ }
+
+ mIdToSnapshot.erase(traversalPath);
+ std::iter_swap(it, mSnapshots.end() - 1);
+ mSnapshots.erase(mSnapshots.end() - 1);
+ }
+}
+
+void LayerSnapshotBuilder::update(const Args& args) {
+ if (tryFastUpdate(args)) {
+ return;
+ }
+ updateSnapshots(args);
+}
+
+void LayerSnapshotBuilder::updateSnapshotsInHierarchy(const Args& args,
+ const LayerHierarchy& hierarchy,
+ LayerHierarchy::TraversalPath& traversalPath,
+ const LayerSnapshot& parentSnapshot) {
+ const RequestedLayerState* layer = hierarchy.getLayer();
+ LayerSnapshot* snapshot = getOrCreateSnapshot(traversalPath, *layer);
+ if (traversalPath.isRelative()) {
+ bool parentIsRelative = traversalPath.variant == LayerHierarchy::Variant::Relative;
+ updateRelativeState(*snapshot, parentSnapshot, parentIsRelative, args);
+ } else {
+ if (traversalPath.isAttached()) {
+ resetRelativeState(*snapshot);
+ }
+ updateSnapshot(*snapshot, args, *layer, parentSnapshot, traversalPath);
+ }
+
+ // If layer is hidden by policy we can avoid update its children. If the visibility
+ // changed this update, then we still need to set the visibility on all the children.
+ if (snapshot->isHiddenByPolicy() &&
+ (!snapshot->changes.any(RequestedLayerState::Changes::Visibility |
+ RequestedLayerState::Changes::Hierarchy))) {
+ return;
+ }
+
+ for (auto& [childHierarchy, variant] : hierarchy.mChildren) {
+ LayerHierarchy::ScopedAddToTraversalPath addChildToPath(traversalPath,
+ childHierarchy->getLayer()->id,
+ variant);
+ updateSnapshotsInHierarchy(args, *childHierarchy, traversalPath, *snapshot);
+ }
+}
+
+LayerSnapshot* LayerSnapshotBuilder::getSnapshot(uint32_t layerId) const {
+ if (layerId == UNASSIGNED_LAYER_ID) {
+ return nullptr;
+ }
+ LayerHierarchy::TraversalPath path{.id = layerId};
+ return getSnapshot(path);
+}
+
+LayerSnapshot* LayerSnapshotBuilder::getSnapshot(const LayerHierarchy::TraversalPath& id) const {
+ auto it = mIdToSnapshot.find(id);
+ return it == mIdToSnapshot.end() ? nullptr : it->second;
+}
+
+LayerSnapshot* LayerSnapshotBuilder::getOrCreateSnapshot(const LayerHierarchy::TraversalPath& id,
+ const RequestedLayerState& layer) {
+ auto snapshot = getSnapshot(id);
+ if (snapshot) {
+ return snapshot;
+ }
+
+ mSnapshots.emplace_back(std::make_unique<LayerSnapshot>(layer, id));
+ snapshot = mSnapshots.back().get();
+ snapshot->globalZ = static_cast<size_t>(mSnapshots.size()) - 1;
+ mIdToSnapshot[id] = snapshot;
+ return snapshot;
+}
+
+void LayerSnapshotBuilder::sortSnapshotsByZ(const Args& args) {
+ if (!args.forceUpdate &&
+ !args.layerLifecycleManager.getGlobalChanges().any(
+ RequestedLayerState::Changes::Hierarchy |
+ RequestedLayerState::Changes::Visibility)) {
+ // We are not force updating and there are no hierarchy or visibility changes. Avoid sorting
+ // the snapshots.
+ return;
+ }
+
+ size_t globalZ = 0;
+ args.root.traverseInZOrder(
+ [this, &globalZ](const LayerHierarchy&,
+ const LayerHierarchy::TraversalPath& traversalPath) -> bool {
+ LayerSnapshot* snapshot = getSnapshot(traversalPath);
+ if (!snapshot) {
+ return false;
+ }
+
+ if (snapshot->isHiddenByPolicy() &&
+ !snapshot->changes.test(RequestedLayerState::Changes::Visibility)) {
+ return false;
+ }
+
+ if (snapshot->isVisible) {
+ size_t oldZ = snapshot->globalZ;
+ size_t newZ = globalZ++;
+ snapshot->globalZ = newZ;
+ if (oldZ == newZ) {
+ return true;
+ }
+ mSnapshots[newZ]->globalZ = oldZ;
+ std::iter_swap(mSnapshots.begin() + static_cast<ssize_t>(oldZ),
+ mSnapshots.begin() + static_cast<ssize_t>(newZ));
+ }
+
+ return true;
+ });
+
+ while (globalZ < mSnapshots.size()) {
+ mSnapshots[globalZ]->globalZ = globalZ;
+ mSnapshots[globalZ]->isVisible = false;
+ globalZ++;
+ }
+}
+
+void LayerSnapshotBuilder::updateRelativeState(LayerSnapshot& snapshot,
+ const LayerSnapshot& parentSnapshot,
+ bool parentIsRelative, const Args& args) {
+ if (parentIsRelative) {
+ snapshot.isHiddenByPolicyFromRelativeParent = parentSnapshot.isHiddenByPolicyFromParent;
+ if (args.includeMetadata) {
+ snapshot.relativeLayerMetadata = parentSnapshot.layerMetadata;
+ }
+ } else {
+ snapshot.isHiddenByPolicyFromRelativeParent =
+ parentSnapshot.isHiddenByPolicyFromRelativeParent;
+ if (args.includeMetadata) {
+ snapshot.relativeLayerMetadata = parentSnapshot.relativeLayerMetadata;
+ }
+ }
+ snapshot.isVisible = snapshot.getIsVisible();
+}
+
+void LayerSnapshotBuilder::resetRelativeState(LayerSnapshot& snapshot) {
+ snapshot.isHiddenByPolicyFromRelativeParent = false;
+ snapshot.relativeLayerMetadata.mMap.clear();
+}
+
+uint32_t getDisplayRotationFlags(
+ const display::DisplayMap<ui::LayerStack, frontend::DisplayInfo>& displays,
+ const ui::LayerStack& layerStack) {
+ static frontend::DisplayInfo sDefaultDisplayInfo = {.isPrimary = false};
+ auto display = displays.get(layerStack).value_or(sDefaultDisplayInfo).get();
+ return display.isPrimary ? display.rotationFlags : 0;
+}
+
+void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& args,
+ const RequestedLayerState& requested,
+ const LayerSnapshot& parentSnapshot,
+ const LayerHierarchy::TraversalPath& path) {
+ // Always update flags and visibility
+ ftl::Flags<RequestedLayerState::Changes> parentChanges = parentSnapshot.changes &
+ (RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Geometry |
+ RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Metadata |
+ RequestedLayerState::Changes::AffectsChildren);
+ snapshot.changes = parentChanges | requested.changes;
+ snapshot.isHiddenByPolicyFromParent =
+ parentSnapshot.isHiddenByPolicyFromParent || requested.isHiddenByPolicy();
+ snapshot.contentDirty = requested.what & layer_state_t::CONTENT_DIRTY;
+ if (snapshot.isHiddenByPolicyFromParent) {
+ snapshot.isVisible = false;
+ return;
+ }
+
+ uint32_t displayRotationFlags =
+ getDisplayRotationFlags(args.displays, snapshot.outputFilter.layerStack);
+
+ const bool forceUpdate = args.forceUpdate ||
+ snapshot.changes.any(RequestedLayerState::Changes::Visibility |
+ RequestedLayerState::Changes::Created);
+
+ if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::AffectsChildren)) {
+ // If root layer, use the layer stack otherwise get the parent's layer stack.
+ snapshot.color.a = parentSnapshot.color.a * requested.color.a;
+ snapshot.alpha = snapshot.color.a;
+ snapshot.isSecure =
+ parentSnapshot.isSecure || (requested.flags & layer_state_t::eLayerSecure);
+ snapshot.isTrustedOverlay = parentSnapshot.isTrustedOverlay || requested.isTrustedOverlay;
+ snapshot.outputFilter.layerStack = requested.parentId != UNASSIGNED_LAYER_ID
+ ? parentSnapshot.outputFilter.layerStack
+ : requested.layerStack;
+ snapshot.outputFilter.toInternalDisplay = parentSnapshot.outputFilter.toInternalDisplay ||
+ (requested.flags & layer_state_t::eLayerSkipScreenshot);
+ snapshot.stretchEffect = (requested.stretchEffect.hasEffect())
+ ? requested.stretchEffect
+ : parentSnapshot.stretchEffect;
+ if (!parentSnapshot.colorTransformIsIdentity) {
+ snapshot.colorTransform = parentSnapshot.colorTransform * requested.colorTransform;
+ snapshot.colorTransformIsIdentity = false;
+ } else {
+ snapshot.colorTransform = requested.colorTransform;
+ snapshot.colorTransformIsIdentity = !requested.hasColorTransform;
+ }
+ }
+
+ if (forceUpdate || requested.changes.get() != 0) {
+ snapshot.compositionType = requested.getCompositionType();
+ snapshot.dimmingEnabled = requested.dimmingEnabled;
+ snapshot.layerOpaqueFlagSet =
+ (requested.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque;
+ }
+
+ if (forceUpdate || requested.what & layer_state_t::BUFFER_CHANGES) {
+ snapshot.acquireFence =
+ (requested.bufferData) ? requested.bufferData->acquireFence : Fence::NO_FENCE;
+ snapshot.buffer =
+ requested.externalTexture ? requested.externalTexture->getBuffer() : nullptr;
+ snapshot.bufferSize = requested.getBufferSize(displayRotationFlags);
+ snapshot.geomBufferSize = snapshot.bufferSize;
+ snapshot.croppedBufferSize = requested.getCroppedBufferSize(snapshot.bufferSize);
+ snapshot.dataspace = requested.dataspace;
+ snapshot.externalTexture = requested.externalTexture;
+ snapshot.frameNumber = (requested.bufferData) ? requested.bufferData->frameNumber : 0;
+ snapshot.geomBufferTransform = requested.bufferTransform;
+ snapshot.geomBufferUsesDisplayInverseTransform = requested.transformToDisplayInverse;
+ snapshot.geomContentCrop = requested.getBufferCrop();
+ snapshot.geomUsesSourceCrop = snapshot.hasBufferOrSidebandStream();
+ snapshot.hasProtectedContent = requested.externalTexture &&
+ requested.externalTexture->getUsage() & GRALLOC_USAGE_PROTECTED;
+ snapshot.isHdrY410 = requested.dataspace == ui::Dataspace::BT2020_ITU_PQ &&
+ requested.api == NATIVE_WINDOW_API_MEDIA &&
+ requested.bufferData->getPixelFormat() == HAL_PIXEL_FORMAT_RGBA_1010102;
+ snapshot.sidebandStream = requested.sidebandStream;
+ snapshot.surfaceDamage = requested.surfaceDamageRegion;
+ snapshot.transparentRegionHint = requested.transparentRegion;
+ }
+
+ if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Content)) {
+ snapshot.color.rgb = requested.getColor().rgb;
+ snapshot.isColorspaceAgnostic = requested.colorSpaceAgnostic;
+ snapshot.backgroundBlurRadius = static_cast<int>(requested.backgroundBlurRadius);
+ snapshot.blurRegions = requested.blurRegions;
+ snapshot.hdrMetadata = requested.hdrMetadata;
+ }
+
+ if (forceUpdate ||
+ snapshot.changes.any(RequestedLayerState::Changes::Hierarchy |
+ RequestedLayerState::Changes::Geometry)) {
+ updateLayerBounds(snapshot, requested, parentSnapshot, displayRotationFlags);
+ updateRoundedCorner(snapshot, requested, parentSnapshot);
+ }
+
+ if (forceUpdate ||
+ snapshot.changes.any(RequestedLayerState::Changes::Hierarchy |
+ RequestedLayerState::Changes::Geometry |
+ RequestedLayerState::Changes::Input)) {
+ static frontend::DisplayInfo sDefaultInfo = {.isSecure = false};
+ const std::optional<frontend::DisplayInfo> displayInfo =
+ args.displays.get(snapshot.outputFilter.layerStack);
+ bool noValidDisplay = !displayInfo.has_value();
+ updateInput(snapshot, requested, parentSnapshot, displayInfo.value_or(sDefaultInfo),
+ noValidDisplay, path);
+ }
+
+ // computed snapshot properties
+ updateShadows(snapshot, requested, args.globalShadowSettings);
+ if (args.includeMetadata) {
+ snapshot.layerMetadata = parentSnapshot.layerMetadata;
+ snapshot.layerMetadata.merge(requested.metadata);
+ }
+ snapshot.forceClientComposition = snapshot.isHdrY410 || snapshot.shadowSettings.length > 0 ||
+ requested.blurRegions.size() > 0 || snapshot.stretchEffect.hasEffect();
+ snapshot.isVisible = snapshot.getIsVisible();
+ snapshot.isOpaque = snapshot.isContentOpaque() && !snapshot.roundedCorner.hasRoundedCorners() &&
+ snapshot.color.a == 1.f;
+ snapshot.blendMode = getBlendMode(snapshot, requested);
+
+ ALOGV("%supdated [%d]%s changes parent:%s global:%s local:%s requested:%s %s from parent %s",
+ args.forceUpdate ? "Force " : "", requested.id, requested.name.c_str(),
+ parentSnapshot.changes.string().c_str(), snapshot.changes.string().c_str(),
+ requested.changes.string().c_str(), std::to_string(requested.what).c_str(),
+ snapshot.getDebugString().c_str(), parentSnapshot.getDebugString().c_str());
+}
+
+void LayerSnapshotBuilder::updateRoundedCorner(LayerSnapshot& snapshot,
+ const RequestedLayerState& requested,
+ const LayerSnapshot& parentSnapshot) {
+ snapshot.roundedCorner = RoundedCornerState();
+ RoundedCornerState parentRoundedCorner;
+ if (parentSnapshot.roundedCorner.hasRoundedCorners()) {
+ parentRoundedCorner = parentSnapshot.roundedCorner;
+ ui::Transform t = snapshot.localTransform.inverse();
+ parentRoundedCorner.cropRect = t.transform(parentRoundedCorner.cropRect);
+ parentRoundedCorner.radius.x *= t.getScaleX();
+ parentRoundedCorner.radius.y *= t.getScaleY();
+ }
+
+ FloatRect layerCropRect = snapshot.croppedBufferSize.toFloatRect();
+ const vec2 radius(requested.cornerRadius, requested.cornerRadius);
+ RoundedCornerState layerSettings(layerCropRect, radius);
+ const bool layerSettingsValid = layerSettings.hasRoundedCorners() && !layerCropRect.isEmpty();
+ const bool parentRoundedCornerValid = parentRoundedCorner.hasRoundedCorners();
+ if (layerSettingsValid && parentRoundedCornerValid) {
+ // If the parent and the layer have rounded corner settings, use the parent settings if
+ // the parent crop is entirely inside the layer crop. This has limitations and cause
+ // rendering artifacts. See b/200300845 for correct fix.
+ if (parentRoundedCorner.cropRect.left > layerCropRect.left &&
+ parentRoundedCorner.cropRect.top > layerCropRect.top &&
+ parentRoundedCorner.cropRect.right < layerCropRect.right &&
+ parentRoundedCorner.cropRect.bottom < layerCropRect.bottom) {
+ snapshot.roundedCorner = parentRoundedCorner;
+ } else {
+ snapshot.roundedCorner = layerSettings;
+ }
+ } else if (layerSettingsValid) {
+ snapshot.roundedCorner = layerSettings;
+ } else if (parentRoundedCornerValid) {
+ snapshot.roundedCorner = parentRoundedCorner;
+ }
+}
+
+void LayerSnapshotBuilder::updateLayerBounds(LayerSnapshot& snapshot,
+ const RequestedLayerState& requested,
+ const LayerSnapshot& parentSnapshot,
+ uint32_t displayRotationFlags) {
+ snapshot.croppedBufferSize = requested.getCroppedBufferSize(snapshot.bufferSize);
+ snapshot.geomCrop = requested.crop;
+ snapshot.localTransform = requested.getTransform(displayRotationFlags);
+ snapshot.localTransformInverse = snapshot.localTransform.inverse();
+ snapshot.geomLayerTransform = parentSnapshot.geomLayerTransform * snapshot.localTransform;
+ snapshot.invalidTransform = !LayerSnapshot::isTransformValid(snapshot.geomLayerTransform);
+ if (snapshot.invalidTransform) {
+ ALOGW("Resetting transform for %s because it has an invalid transformation.",
+ requested.getDebugStringShort().c_str());
+ snapshot.geomLayerTransform.reset();
+ }
+ snapshot.geomInverseLayerTransform = snapshot.geomLayerTransform.inverse();
+
+ FloatRect parentBounds = parentSnapshot.geomLayerBounds;
+ parentBounds = snapshot.localTransform.inverse().transform(parentBounds);
+ snapshot.geomLayerBounds =
+ (requested.externalTexture) ? snapshot.bufferSize.toFloatRect() : parentBounds;
+ if (!requested.crop.isEmpty()) {
+ snapshot.geomLayerBounds = snapshot.geomLayerBounds.intersect(requested.crop.toFloatRect());
+ }
+ snapshot.geomLayerBounds = snapshot.geomLayerBounds.intersect(parentBounds);
+ snapshot.transformedBounds = snapshot.geomLayerTransform.transform(snapshot.geomLayerBounds);
+ snapshot.parentTransform = parentSnapshot.geomLayerTransform;
+
+ // Subtract the transparent region and snap to the bounds
+ Rect bounds =
+ RequestedLayerState::reduce(snapshot.croppedBufferSize, requested.transparentRegion);
+ snapshot.cursorFrame = snapshot.geomLayerTransform.transform(bounds);
+
+ // TODO(b/238781169) use dest vs src
+ snapshot.bufferNeedsFiltering = snapshot.externalTexture &&
+ getBufferNeedsFiltering(snapshot,
+ requested.getUnrotatedBufferSize(displayRotationFlags));
+}
+
+void LayerSnapshotBuilder::updateShadows(LayerSnapshot& snapshot,
+ const RequestedLayerState& requested,
+ const renderengine::ShadowSettings& globalShadowSettings) {
+ snapshot.shadowRadius = requested.shadowRadius;
+ snapshot.shadowSettings.length = requested.shadowRadius;
+ if (snapshot.shadowRadius > 0.f) {
+ snapshot.shadowSettings = globalShadowSettings;
+
+ // Note: this preserves existing behavior of shadowing the entire layer and not cropping
+ // it if transparent regions are present. This may not be necessary since shadows are
+ // typically cast by layers without transparent regions.
+ snapshot.shadowSettings.boundaries = snapshot.geomLayerBounds;
+
+ // If the casting layer is translucent, we need to fill in the shadow underneath the
+ // layer. Otherwise the generated shadow will only be shown around the casting layer.
+ snapshot.shadowSettings.casterIsTranslucent =
+ !snapshot.isContentOpaque() || (snapshot.alpha < 1.0f);
+ snapshot.shadowSettings.ambientColor *= snapshot.alpha;
+ snapshot.shadowSettings.spotColor *= snapshot.alpha;
+ }
+}
+
+void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot,
+ const RequestedLayerState& requested,
+ const LayerSnapshot& parentSnapshot,
+ const frontend::DisplayInfo& displayInfo,
+ bool noValidDisplay,
+ const LayerHierarchy::TraversalPath& path) {
+ snapshot.inputInfo.displayId = static_cast<int32_t>(snapshot.outputFilter.layerStack.id);
+ if (!requested.hasInputInfo()) {
+ snapshot.inputInfo.inputConfig = gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL;
+ return;
+ }
+
+ fillInputFrameInfo(snapshot.inputInfo, displayInfo.transform, snapshot);
+
+ if (noValidDisplay) {
+ // Do not let the window receive touches if it is not associated with a valid display
+ // transform. We still allow the window to receive keys and prevent ANRs.
+ snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::NOT_TOUCHABLE;
+ }
+
+ // For compatibility reasons we let layers which can receive input
+ // receive input before they have actually submitted a buffer. Because
+ // of this we use canReceiveInput instead of isVisible to check the
+ // policy-visibility, ignoring the buffer state. However for layers with
+ // hasInputInfo()==false we can use the real visibility state.
+ // We are just using these layers for occlusion detection in
+ // InputDispatcher, and obviously if they aren't visible they can't occlude
+ // anything.
+ const bool visible = requested.hasInputInfo() ? snapshot.canReceiveInput() : snapshot.isVisible;
+ snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE, !visible);
+
+ snapshot.inputInfo.alpha = snapshot.color.a;
+ snapshot.inputInfo.touchOcclusionMode = parentSnapshot.inputInfo.touchOcclusionMode;
+ if (requested.dropInputMode == gui::DropInputMode::ALL ||
+ parentSnapshot.dropInputMode == gui::DropInputMode::ALL) {
+ snapshot.dropInputMode = gui::DropInputMode::ALL;
+ } else if (requested.dropInputMode == gui::DropInputMode::OBSCURED ||
+ parentSnapshot.dropInputMode == gui::DropInputMode::OBSCURED) {
+ snapshot.dropInputMode = gui::DropInputMode::OBSCURED;
+ } else {
+ snapshot.dropInputMode = gui::DropInputMode::NONE;
+ }
+
+ handleDropInputMode(snapshot, parentSnapshot);
+
+ // If the window will be blacked out on a display because the display does not have the secure
+ // flag and the layer has the secure flag set, then drop input.
+ if (!displayInfo.isSecure && snapshot.isSecure) {
+ snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT;
+ }
+
+ auto cropLayerSnapshot = getSnapshot(requested.touchCropId);
+ if (snapshot.inputInfo.replaceTouchableRegionWithCrop) {
+ const Rect bounds(cropLayerSnapshot ? cropLayerSnapshot->transformedBounds
+ : snapshot.transformedBounds);
+ snapshot.inputInfo.touchableRegion = Region(displayInfo.transform.transform(bounds));
+ } else if (cropLayerSnapshot) {
+ snapshot.inputInfo.touchableRegion = snapshot.inputInfo.touchableRegion.intersect(
+ displayInfo.transform.transform(Rect{cropLayerSnapshot->transformedBounds}));
+ }
+
+ // Inherit the trusted state from the parent hierarchy, but don't clobber the trusted state
+ // if it was set by WM for a known system overlay
+ if (snapshot.isTrustedOverlay) {
+ snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::TRUSTED_OVERLAY;
+ }
+
+ // If the layer is a clone, we need to crop the input region to cloned root to prevent
+ // touches from going outside the cloned area.
+ if (path.isClone()) {
+ snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::CLONE;
+ auto clonedRootSnapshot = getSnapshot(path.mirrorRootIds.back());
+ if (clonedRootSnapshot) {
+ const Rect rect =
+ displayInfo.transform.transform(Rect{clonedRootSnapshot->transformedBounds});
+ snapshot.inputInfo.touchableRegion = snapshot.inputInfo.touchableRegion.intersect(rect);
+ }
+ }
+}
+
+std::vector<std::unique_ptr<LayerSnapshot>>& LayerSnapshotBuilder::getSnapshots() {
+ return mSnapshots;
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
new file mode 100644
index 0000000..33b250c
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "Display/DisplayMap.h"
+#include "FrontEnd/DisplayInfo.h"
+#include "FrontEnd/LayerLifecycleManager.h"
+#include "LayerHierarchy.h"
+#include "LayerSnapshot.h"
+#include "RequestedLayerState.h"
+
+namespace android::surfaceflinger::frontend {
+
+// Walks through the layer hierarchy to build an ordered list
+// of LayerSnapshots that can be passed on to CompositionEngine.
+// This builder does a minimum amount of work to update
+// an existing set of snapshots based on hierarchy changes
+// and RequestedLayerState changes.
+
+// The builder also uses a fast path to update
+// snapshots when there are only buffer updates.
+class LayerSnapshotBuilder {
+public:
+ struct Args {
+ const LayerHierarchy& root;
+ const LayerLifecycleManager& layerLifecycleManager;
+ bool forceUpdate = false;
+ bool includeMetadata = false;
+ const display::DisplayMap<ui::LayerStack, frontend::DisplayInfo>& displays;
+ // Set to true if there were display changes since last update.
+ bool displayChanges = false;
+ const renderengine::ShadowSettings& globalShadowSettings;
+ };
+ LayerSnapshotBuilder();
+
+ // Rebuild the snapshots from scratch.
+ LayerSnapshotBuilder(Args);
+
+ // Update an existing set of snapshot using change flags in RequestedLayerState
+ // and LayerLifecycleManager. This needs to be called before
+ // LayerLifecycleManager.commitChanges is called as that function will clear all
+ // change flags.
+ void update(const Args&);
+ std::vector<std::unique_ptr<LayerSnapshot>>& getSnapshots();
+
+private:
+ friend class LayerSnapshotTest;
+ LayerSnapshot* getSnapshot(uint32_t layerId) const;
+ LayerSnapshot* getSnapshot(const LayerHierarchy::TraversalPath& id) const;
+ static LayerSnapshot getRootSnapshot();
+
+ // return true if we were able to successfully update the snapshots via
+ // the fast path.
+ bool tryFastUpdate(const Args& args);
+
+ void updateSnapshots(const Args& args);
+
+ void updateSnapshotsInHierarchy(const Args&, const LayerHierarchy& hierarchy,
+ LayerHierarchy::TraversalPath& traversalPath,
+ const LayerSnapshot& parentSnapshot);
+ void updateSnapshot(LayerSnapshot& snapshot, const Args& args, const RequestedLayerState&,
+ const LayerSnapshot& parentSnapshot,
+ const LayerHierarchy::TraversalPath& path);
+ static void updateRelativeState(LayerSnapshot& snapshot, const LayerSnapshot& parentSnapshot,
+ bool parentIsRelative, const Args& args);
+ static void resetRelativeState(LayerSnapshot& snapshot);
+ static void updateRoundedCorner(LayerSnapshot& snapshot, const RequestedLayerState& layerState,
+ const LayerSnapshot& parentSnapshot);
+ static void updateLayerBounds(LayerSnapshot& snapshot, const RequestedLayerState& layerState,
+ const LayerSnapshot& parentSnapshot,
+ uint32_t displayRotationFlags);
+ static void updateShadows(LayerSnapshot& snapshot, const RequestedLayerState& requested,
+ const renderengine::ShadowSettings& globalShadowSettings);
+ void updateInput(LayerSnapshot& snapshot, const RequestedLayerState& requested,
+ const LayerSnapshot& parentSnapshot, const frontend::DisplayInfo& displayInfo,
+ bool noValidDisplay, const LayerHierarchy::TraversalPath& path);
+ void sortSnapshotsByZ(const Args& args);
+ LayerSnapshot* getOrCreateSnapshot(const LayerHierarchy::TraversalPath& id,
+ const RequestedLayerState& layer);
+
+ struct TraversalPathHash {
+ std::size_t operator()(const LayerHierarchy::TraversalPath& key) const {
+ uint32_t hashCode = key.id * 31;
+ for (auto mirrorRoot : key.mirrorRootIds) {
+ hashCode += mirrorRoot * 31;
+ }
+ return std::hash<size_t>{}(hashCode);
+ }
+ };
+ std::unordered_map<LayerHierarchy::TraversalPath, LayerSnapshot*, TraversalPathHash>
+ mIdToSnapshot;
+ std::vector<std::unique_ptr<LayerSnapshot>> mSnapshots;
+ LayerSnapshot mRootSnapshot;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 054382c..dcc16e8 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -118,7 +118,7 @@
void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerState) {
bool oldFlags = flags;
- Rect oldBufferSize = getBufferSize();
+ Rect oldBufferSize = getBufferSize(0);
const layer_state_t& clientState = resolvedComposerState.state;
uint64_t clientChanges = what | layer_state_t::diff(clientState);
@@ -133,7 +133,7 @@
changes |= RequestedLayerState::Changes::Geometry;
}
}
- if (clientState.what & layer_state_t::eBufferChanged && oldBufferSize != getBufferSize()) {
+ if (clientState.what & layer_state_t::eBufferChanged && oldBufferSize != getBufferSize(0)) {
changes |= RequestedLayerState::Changes::Geometry;
}
if (clientChanges & layer_state_t::HIERARCHY_CHANGES)
@@ -142,6 +142,8 @@
changes |= RequestedLayerState::Changes::Content;
if (clientChanges & layer_state_t::GEOMETRY_CHANGES)
changes |= RequestedLayerState::Changes::Geometry;
+ if (clientChanges & layer_state_t::AFFECTS_CHILDREN)
+ changes |= RequestedLayerState::Changes::AffectsChildren;
if (clientState.what & layer_state_t::eColorTransformChanged) {
static const mat4 identityMatrix = mat4();
@@ -205,7 +207,22 @@
}
}
-ui::Transform RequestedLayerState::getTransform() const {
+ui::Size RequestedLayerState::getUnrotatedBufferSize(uint32_t displayRotationFlags) const {
+ uint32_t bufferWidth = externalTexture->getWidth();
+ uint32_t bufferHeight = externalTexture->getHeight();
+ // Undo any transformations on the buffer.
+ if (bufferTransform & ui::Transform::ROT_90) {
+ std::swap(bufferWidth, bufferHeight);
+ }
+ if (transformToDisplayInverse) {
+ if (displayRotationFlags & ui::Transform::ROT_90) {
+ std::swap(bufferWidth, bufferHeight);
+ }
+ }
+ return {bufferWidth, bufferHeight};
+}
+
+ui::Transform RequestedLayerState::getTransform(uint32_t displayRotationFlags) const {
if ((flags & layer_state_t::eIgnoreDestinationFrame) || destinationFrame.isEmpty()) {
// If destination frame is not set, use the requested transform set via
// Transaction::setPosition and Transaction::setMatrix.
@@ -230,22 +247,10 @@
return transform;
}
- uint32_t bufferWidth = externalTexture->getWidth();
- uint32_t bufferHeight = externalTexture->getHeight();
- // Undo any transformations on the buffer.
- if (bufferTransform & ui::Transform::ROT_90) {
- std::swap(bufferWidth, bufferHeight);
- }
- // TODO(b/238781169) remove dep
- uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags();
- if (transformToDisplayInverse) {
- if (invTransform & ui::Transform::ROT_90) {
- std::swap(bufferWidth, bufferHeight);
- }
- }
+ ui::Size bufferSize = getUnrotatedBufferSize(displayRotationFlags);
- float sx = static_cast<float>(destW) / static_cast<float>(bufferWidth);
- float sy = static_cast<float>(destH) / static_cast<float>(bufferHeight);
+ float sx = static_cast<float>(destW) / static_cast<float>(bufferSize.width);
+ float sy = static_cast<float>(destH) / static_cast<float>(bufferSize.height);
ui::Transform transform;
transform.set(sx, 0, 0, sy);
transform.set(static_cast<float>(destRect.left), static_cast<float>(destRect.top));
@@ -279,7 +284,7 @@
}
return color;
}
-Rect RequestedLayerState::getBufferSize() const {
+Rect RequestedLayerState::getBufferSize(uint32_t displayRotationFlags) const {
// for buffer state layers we use the display frame size as the buffer size.
if (!externalTexture) {
return Rect::INVALID_RECT;
@@ -294,8 +299,7 @@
}
if (transformToDisplayInverse) {
- // TODO(b/238781169) pass in display metrics (would be useful for input info as well
- uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags();
+ uint32_t invTransform = displayRotationFlags;
if (invTransform & ui::Transform::ROT_90) {
std::swap(bufWidth, bufHeight);
}
@@ -304,8 +308,8 @@
return Rect(0, 0, static_cast<int32_t>(bufWidth), static_cast<int32_t>(bufHeight));
}
-Rect RequestedLayerState::getCroppedBufferSize() const {
- Rect size = getBufferSize();
+Rect RequestedLayerState::getCroppedBufferSize(const Rect& bufferSize) const {
+ Rect size = bufferSize;
if (!crop.isEmpty() && size.isValid()) {
size.intersect(crop, &size);
} else if (!crop.isEmpty()) {
@@ -367,4 +371,13 @@
return isRelativeOf && parentId != relativeParentId;
}
+bool RequestedLayerState::hasInputInfo() const {
+ if (!windowInfoHandle) {
+ return false;
+ }
+ const auto windowInfo = windowInfoHandle->getInfo();
+ return windowInfo->token != nullptr ||
+ windowInfo->inputConfig.test(gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL);
+}
+
} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
index 7849165..95240d0 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -47,22 +47,26 @@
RelativeParent = 1u << 9,
Metadata = 1u << 10,
Visibility = 1u << 11,
+ AffectsChildren = 1u << 12,
};
static Rect reduce(const Rect& win, const Region& exclude);
RequestedLayerState(const LayerCreationArgs&);
void merge(const ResolvedComposerState&);
- ui::Transform getTransform() const;
+ // Currently we only care about the primary display
+ ui::Transform getTransform(uint32_t displayRotationFlags) const;
+ ui::Size getUnrotatedBufferSize(uint32_t displayRotationFlags) const;
bool canBeDestroyed() const;
bool isRoot() const;
bool isHiddenByPolicy() const;
half4 getColor() const;
- Rect getBufferSize() const;
- Rect getCroppedBufferSize() const;
+ Rect getBufferSize(uint32_t displayRotationFlags) const;
+ Rect getCroppedBufferSize(const Rect& bufferSize) const;
Rect getBufferCrop() const;
std::string getDebugString() const;
std::string getDebugStringShort() const;
aidl::android::hardware::graphics::composer3::Composition getCompositionType() const;
bool hasValidRelativeParent() const;
+ bool hasInputInfo() 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 c2109b3..8629671 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
@@ -177,7 +177,7 @@
}
mStalledTransactions.push_back(transactionId);
- listener->onTransactionQueueStalled(reason);
+ listener->onTransactionQueueStalled(String8(reason.c_str()));
}
void TransactionHandler::removeFromStalledTransactions(uint64_t id) {
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h
index 475ff1b..a06b870 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.h
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h
@@ -29,7 +29,6 @@
namespace android {
class TestableSurfaceFlinger;
-using gui::IListenerHash;
namespace surfaceflinger::frontend {
class TransactionHandler {
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 1f159ae..0179d62 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -128,6 +128,8 @@
using namespace ftl::flag_operators;
using base::StringAppendF;
+using frontend::LayerSnapshot;
+using frontend::RoundedCornerState;
using gui::GameMode;
using gui::LayerMetadata;
using gui::WindowInfo;
@@ -208,7 +210,7 @@
mSnapshot->name = getDebugName();
mSnapshot->textureName = mTextureName;
mSnapshot->premultipliedAlpha = mPremultipliedAlpha;
- mSnapshot->transform = {};
+ mSnapshot->parentTransform = {};
}
void Layer::onFirstRef() {
@@ -473,7 +475,7 @@
snapshot->geomLayerTransform = getTransform();
snapshot->geomInverseLayerTransform = snapshot->geomLayerTransform.inverse();
snapshot->transparentRegionHint = getActiveTransparentRegion(drawingState);
- snapshot->blurRegionTransform = getActiveTransform(drawingState).inverse();
+ snapshot->localTransformInverse = getActiveTransform(drawingState).inverse();
snapshot->blendMode = static_cast<Hwc2::IComposerClient::BlendMode>(blendMode);
snapshot->alpha = alpha;
snapshot->backgroundBlurRadius = drawingState.backgroundBlurRadius;
@@ -2604,12 +2606,9 @@
return;
}
ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64, getDebugName(), framenumber);
- std::optional<os::ParcelFileDescriptor> fenceFd;
- if (releaseFence) {
- fenceFd = os::ParcelFileDescriptor(base::unique_fd(::dup(releaseFence->get())));
- }
- listener->onReleaseBuffer({buffer->getId(), framenumber}, fenceFd,
- static_cast<int32_t>(currentMaxAcquiredBufferCount));
+ listener->onReleaseBuffer({buffer->getId(), framenumber},
+ releaseFence ? releaseFence : Fence::NO_FENCE,
+ currentMaxAcquiredBufferCount);
}
void Layer::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult) {
@@ -3529,7 +3528,7 @@
}
// If the buffer has no alpha channel, then we are opaque
- if (hasBufferOrSidebandStream() && isOpaqueFormat(getPixelFormat())) {
+ if (hasBufferOrSidebandStream() && LayerSnapshot::isOpaqueFormat(getPixelFormat())) {
return true;
}
@@ -3703,29 +3702,6 @@
(mBufferInfo.mBuffer->getUsage() & GRALLOC_USAGE_PROTECTED);
}
-// As documented in libhardware header, formats in the range
-// 0x100 - 0x1FF are specific to the HAL implementation, and
-// are known to have no alpha channel
-// TODO: move definition for device-specific range into
-// hardware.h, instead of using hard-coded values here.
-#define HARDWARE_IS_DEVICE_FORMAT(f) ((f) >= 0x100 && (f) <= 0x1FF)
-
-bool Layer::isOpaqueFormat(PixelFormat format) {
- if (HARDWARE_IS_DEVICE_FORMAT(format)) {
- return true;
- }
- switch (format) {
- case PIXEL_FORMAT_RGBA_8888:
- case PIXEL_FORMAT_BGRA_8888:
- case PIXEL_FORMAT_RGBA_FP16:
- case PIXEL_FORMAT_RGBA_1010102:
- case PIXEL_FORMAT_R_8:
- return false;
- }
- // in all other case, we have no blending (also for unknown formats)
- return true;
-}
-
bool Layer::needsFiltering(const DisplayDevice* display) const {
if (!hasBufferOrSidebandStream()) {
return false;
@@ -3916,13 +3892,15 @@
snapshot->shadowSettings.length = mEffectiveShadowRadius;
}
snapshot->contentOpaque = isOpaque(mDrawingState);
+ snapshot->layerOpaqueFlagSet =
+ (mDrawingState.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque;
snapshot->isHdrY410 = isHdrY410();
snapshot->bufferNeedsFiltering = bufferNeedsFiltering();
sp<Layer> p = mDrawingParent.promote();
if (p != nullptr) {
- snapshot->transform = p->getTransform();
+ snapshot->parentTransform = p->getTransform();
} else {
- snapshot->transform.reset();
+ snapshot->parentTransform.reset();
}
snapshot->bufferSize = getBufferSize(mDrawingState);
snapshot->externalTexture = mBufferInfo.mBuffer;
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 08a13a3..8281140 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -324,8 +324,8 @@
virtual sp<LayerFE> getCompositionEngineLayerFE() const;
virtual sp<LayerFE> copyCompositionEngineLayerFE() const;
- const LayerSnapshot* getLayerSnapshot() const;
- LayerSnapshot* editLayerSnapshot();
+ const frontend::LayerSnapshot* getLayerSnapshot() const;
+ frontend::LayerSnapshot* editLayerSnapshot();
// If we have received a new buffer this frame, we will pass its surface
// damage down to hardware composer. Otherwise, we must send a region with
@@ -464,7 +464,7 @@
// Returns how rounded corners should be drawn for this layer.
// A layer can override its parent's rounded corner settings if the parent's rounded
// corner crop does not intersect with its own rounded corner crop.
- virtual RoundedCornerState getRoundedCornerState() const;
+ virtual frontend::RoundedCornerState getRoundedCornerState() const;
bool hasRoundedCorners() const { return getRoundedCornerState().hasRoundedCorners(); }
@@ -746,12 +746,12 @@
*/
bool hasInputInfo() const;
- // Sets the GameMode for the tree rooted at this layer. A layer in the tree inherits this
- // GameMode unless it (or an ancestor) has GAME_MODE_METADATA.
- void setGameModeForTree(GameMode);
+ // Sets the gui::GameMode for the tree rooted at this layer. A layer in the tree inherits this
+ // gui::GameMode unless it (or an ancestor) has GAME_MODE_METADATA.
+ void setGameModeForTree(gui::GameMode);
- void setGameMode(GameMode gameMode) { mGameMode = gameMode; }
- GameMode getGameMode() const { return mGameMode; }
+ void setGameMode(gui::GameMode gameMode) { mGameMode = gameMode; }
+ gui::GameMode getGameMode() const { return mGameMode; }
virtual uid_t getOwnerUid() const { return mOwnerUid; }
@@ -827,7 +827,9 @@
void gatherBufferInfo();
void onSurfaceFrameCreated(const std::shared_ptr<frametimeline::SurfaceFrame>&);
- sp<Layer> getClonedFrom() { return mClonedFrom != nullptr ? mClonedFrom.promote() : nullptr; }
+ sp<Layer> getClonedFrom() const {
+ return mClonedFrom != nullptr ? mClonedFrom.promote() : nullptr;
+ }
bool isClone() { return mClonedFrom != nullptr; }
bool isClonedFromAlive() { return getClonedFrom() != nullptr; }
@@ -1064,7 +1066,7 @@
float mEffectiveShadowRadius = 0.f;
// Game mode for the layer. Set by WindowManagerShell and recorded by SurfaceFlingerStats.
- GameMode mGameMode = GameMode::Unsupported;
+ gui::GameMode mGameMode = gui::GameMode::Unsupported;
// A list of regions on this layer that should have blurs.
const std::vector<BlurRegion> getBlurRegions() const;
@@ -1120,7 +1122,8 @@
ui::Transform mRequestedTransform;
sp<LayerFE> mLayerFE;
- std::unique_ptr<LayerSnapshot> mSnapshot = std::make_unique<LayerSnapshot>();
+ std::unique_ptr<frontend::LayerSnapshot> mSnapshot =
+ std::make_unique<frontend::LayerSnapshot>();
friend class LayerSnapshotGuard;
};
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index 363adc6..c31a2e3 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -148,14 +148,14 @@
case LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled:
layerSettings.backgroundBlurRadius = mSnapshot->backgroundBlurRadius;
layerSettings.blurRegions = mSnapshot->blurRegions;
- layerSettings.blurRegionTransform = mSnapshot->blurRegionTransform.asMatrix4();
+ layerSettings.blurRegionTransform = mSnapshot->localTransformInverse.asMatrix4();
break;
case LayerFE::ClientCompositionTargetSettings::BlurSetting::BackgroundBlurOnly:
layerSettings.backgroundBlurRadius = mSnapshot->backgroundBlurRadius;
break;
case LayerFE::ClientCompositionTargetSettings::BlurSetting::BlurRegionsOnly:
layerSettings.blurRegions = mSnapshot->blurRegions;
- layerSettings.blurRegionTransform = mSnapshot->blurRegionTransform.asMatrix4();
+ layerSettings.blurRegionTransform = mSnapshot->localTransformInverse.asMatrix4();
break;
case LayerFE::ClientCompositionTargetSettings::BlurSetting::Disabled:
default:
@@ -275,7 +275,7 @@
* of a camera where the buffer remains in native orientation,
* we want the pixels to always be upright.
*/
- const auto parentTransform = mSnapshot->transform;
+ const auto parentTransform = mSnapshot->parentTransform;
tr = tr * inverseOrientation(parentTransform.getOrientation());
// and finally apply it to the original texture matrix
diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h
index 822bcb7..01da019 100644
--- a/services/surfaceflinger/LayerFE.h
+++ b/services/surfaceflinger/LayerFE.h
@@ -17,48 +17,12 @@
#pragma once
#include <gui/LayerMetadata.h>
-
+#include "FrontEnd/LayerSnapshot.h"
#include "compositionengine/LayerFE.h"
#include "compositionengine/LayerFECompositionState.h"
#include "renderengine/LayerSettings.h"
namespace android {
-struct RoundedCornerState {
- RoundedCornerState() = default;
- RoundedCornerState(const FloatRect& cropRect, const vec2& radius)
- : cropRect(cropRect), radius(radius) {}
-
- // Rounded rectangle in local layer coordinate space.
- FloatRect cropRect = FloatRect();
- // Radius of the rounded rectangle.
- vec2 radius;
- bool hasRoundedCorners() const { return radius.x > 0.0f && radius.y > 0.0f; }
-};
-
-// LayerSnapshot stores Layer state used by CompositionEngine and RenderEngine. Composition
-// Engine uses a pointer to LayerSnapshot (as LayerFECompositionState*) and the LayerSettings
-// passed to Render Engine are created using properties stored on this struct.
-struct LayerSnapshot : public compositionengine::LayerFECompositionState {
- int32_t sequence;
- std::string name;
- uint32_t textureName;
- bool contentOpaque;
- RoundedCornerState roundedCorner;
- StretchEffect stretchEffect;
- FloatRect transformedBounds;
- renderengine::ShadowSettings shadowSettings;
- bool premultipliedAlpha;
- bool isHdrY410;
- bool bufferNeedsFiltering;
- ui::Transform transform;
- Rect bufferSize;
- std::shared_ptr<renderengine::ExternalTexture> externalTexture;
- gui::LayerMetadata layerMetadata;
- gui::LayerMetadata relativeLayerMetadata;
- bool contentDirty;
- bool hasReadyFrame;
- ui::Transform blurRegionTransform;
-};
struct CompositionResult {
// TODO(b/238781169) update CE to no longer pass refreshStartTime to LayerFE::onPreComposition
@@ -86,7 +50,7 @@
compositionengine::LayerFE::ClientCompositionTargetSettings&) const;
CompositionResult&& stealCompositionResult();
- std::unique_ptr<LayerSnapshot> mSnapshot;
+ std::unique_ptr<surfaceflinger::frontend::LayerSnapshot> mSnapshot;
private:
std::optional<compositionengine::LayerFE::LayerSettings> prepareClientCompositionInternal(
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index 7aa7e17..0ade467 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -320,7 +320,12 @@
const auto width = std::min({kMaxWidth, viewport.width, viewport.height});
const auto height = 2 * width;
Rect frame((5 * width) >> 4, height >> 5);
- frame.offsetBy(width >> 5, height >> 4);
+
+ if (!mFeatures.test(Features::ShowInMiddle)) {
+ frame.offsetBy(width >> 5, height >> 4);
+ } else {
+ frame.offsetBy(width >> 1, height >> 4);
+ }
createTransaction(mSurfaceControl->get())
.setMatrix(mSurfaceControl->get(), frame.getWidth() / static_cast<float>(kBufferWidth),
diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h
index d6f828f..b68a88c 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -54,6 +54,7 @@
enum class Features {
Spinner = 1 << 0,
RenderRate = 1 << 1,
+ ShowInMiddle = 1 << 2,
};
RefreshRateOverlay(FpsRange, ftl::Flags<Features>);
diff --git a/services/surfaceflinger/Scheduler/DispSyncSource.cpp b/services/surfaceflinger/Scheduler/DispSyncSource.cpp
deleted file mode 100644
index 4af1f5c..0000000
--- a/services/surfaceflinger/Scheduler/DispSyncSource.cpp
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright 2018 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 ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "DispSyncSource.h"
-
-#include <android-base/stringprintf.h>
-#include <utils/Trace.h>
-#include <mutex>
-
-#include "EventThread.h"
-#include "VSyncTracker.h"
-#include "VsyncController.h"
-
-namespace android::scheduler {
-using base::StringAppendF;
-using namespace std::chrono_literals;
-
-class CallbackRepeater {
-public:
- CallbackRepeater(VSyncDispatch& dispatch, VSyncDispatch::Callback cb, const char* name,
- std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration,
- std::chrono::nanoseconds notBefore)
- : mName(name),
- mCallback(cb),
- mRegistration(dispatch,
- std::bind(&CallbackRepeater::callback, this, std::placeholders::_1,
- std::placeholders::_2, std::placeholders::_3),
- mName),
- mStarted(false),
- mWorkDuration(workDuration),
- mReadyDuration(readyDuration),
- mLastCallTime(notBefore) {}
-
- ~CallbackRepeater() {
- std::lock_guard lock(mMutex);
- mRegistration.cancel();
- }
-
- void start(std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration) {
- std::lock_guard lock(mMutex);
- mStarted = true;
- mWorkDuration = workDuration;
- mReadyDuration = readyDuration;
-
- auto const scheduleResult =
- mRegistration.schedule({.workDuration = mWorkDuration.count(),
- .readyDuration = mReadyDuration.count(),
- .earliestVsync = mLastCallTime.count()});
- LOG_ALWAYS_FATAL_IF((!scheduleResult.has_value()), "Error scheduling callback");
- }
-
- void stop() {
- std::lock_guard lock(mMutex);
- LOG_ALWAYS_FATAL_IF(!mStarted, "DispSyncInterface misuse: callback already stopped");
- mStarted = false;
- mRegistration.cancel();
- }
-
- void dump(std::string& result) const {
- std::lock_guard lock(mMutex);
- const auto relativeLastCallTime =
- mLastCallTime - std::chrono::steady_clock::now().time_since_epoch();
- StringAppendF(&result, "\t%s: ", mName.c_str());
- StringAppendF(&result, "mWorkDuration=%.2f mReadyDuration=%.2f last vsync time ",
- mWorkDuration.count() / 1e6f, mReadyDuration.count() / 1e6f);
- StringAppendF(&result, "%.2fms relative to now (%s)\n", relativeLastCallTime.count() / 1e6f,
- mStarted ? "running" : "stopped");
- }
-
-private:
- void callback(nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) {
- {
- std::lock_guard lock(mMutex);
- mLastCallTime = std::chrono::nanoseconds(vsyncTime);
- }
-
- mCallback(vsyncTime, wakeupTime, readyTime);
-
- {
- std::lock_guard lock(mMutex);
- if (!mStarted) {
- return;
- }
- auto const scheduleResult =
- mRegistration.schedule({.workDuration = mWorkDuration.count(),
- .readyDuration = mReadyDuration.count(),
- .earliestVsync = vsyncTime});
- LOG_ALWAYS_FATAL_IF(!scheduleResult.has_value(), "Error rescheduling callback");
- }
- }
-
- const std::string mName;
- scheduler::VSyncDispatch::Callback mCallback;
-
- mutable std::mutex mMutex;
- VSyncCallbackRegistration mRegistration GUARDED_BY(mMutex);
- bool mStarted GUARDED_BY(mMutex) = false;
- std::chrono::nanoseconds mWorkDuration GUARDED_BY(mMutex) = 0ns;
- std::chrono::nanoseconds mReadyDuration GUARDED_BY(mMutex) = 0ns;
- std::chrono::nanoseconds mLastCallTime GUARDED_BY(mMutex) = 0ns;
-};
-
-DispSyncSource::DispSyncSource(VSyncDispatch& vSyncDispatch, VSyncTracker& vSyncTracker,
- std::chrono::nanoseconds workDuration,
- std::chrono::nanoseconds readyDuration, bool traceVsync,
- const char* name)
- : mName(name),
- mValue(base::StringPrintf("VSYNC-%s", name), 0),
- mTraceVsync(traceVsync),
- mVsyncOnLabel(base::StringPrintf("VsyncOn-%s", name)),
- mVSyncTracker(vSyncTracker),
- mWorkDuration(base::StringPrintf("VsyncWorkDuration-%s", name), workDuration),
- mReadyDuration(readyDuration) {
- mCallbackRepeater =
- std::make_unique<CallbackRepeater>(vSyncDispatch,
- std::bind(&DispSyncSource::onVsyncCallback, this,
- std::placeholders::_1,
- std::placeholders::_2,
- std::placeholders::_3),
- name, workDuration, readyDuration,
- std::chrono::steady_clock::now().time_since_epoch());
-}
-
-DispSyncSource::~DispSyncSource() = default;
-
-void DispSyncSource::setVSyncEnabled(bool enable) {
- std::lock_guard lock(mVsyncMutex);
- if (enable) {
- mCallbackRepeater->start(mWorkDuration, mReadyDuration);
- // ATRACE_INT(mVsyncOnLabel.c_str(), 1);
- } else {
- mCallbackRepeater->stop();
- // ATRACE_INT(mVsyncOnLabel.c_str(), 0);
- }
- mEnabled = enable;
-}
-
-void DispSyncSource::setCallback(VSyncSource::Callback* callback) {
- std::lock_guard lock(mCallbackMutex);
- mCallback = callback;
-}
-
-void DispSyncSource::setDuration(std::chrono::nanoseconds workDuration,
- std::chrono::nanoseconds readyDuration) {
- std::lock_guard lock(mVsyncMutex);
- mWorkDuration = workDuration;
- mReadyDuration = readyDuration;
-
- // If we're not enabled, we don't need to mess with the listeners
- if (!mEnabled) {
- return;
- }
-
- mCallbackRepeater->start(mWorkDuration, mReadyDuration);
-}
-
-void DispSyncSource::onVsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime,
- nsecs_t readyTime) {
- VSyncSource::Callback* callback;
- {
- std::lock_guard lock(mCallbackMutex);
- callback = mCallback;
- }
-
- if (mTraceVsync) {
- mValue = (mValue + 1) % 2;
- }
-
- if (callback != nullptr) {
- callback->onVSyncEvent(targetWakeupTime, {vsyncTime, readyTime});
- }
-}
-
-VSyncSource::VSyncData DispSyncSource::getLatestVSyncData() const {
- std::lock_guard lock(mVsyncMutex);
- nsecs_t expectedPresentationTime = mVSyncTracker.nextAnticipatedVSyncTimeFrom(
- systemTime() + mWorkDuration.get().count() + mReadyDuration.count());
- nsecs_t deadline = expectedPresentationTime - mReadyDuration.count();
- return {expectedPresentationTime, deadline};
-}
-
-void DispSyncSource::dump(std::string& result) const {
- std::lock_guard lock(mVsyncMutex);
- StringAppendF(&result, "DispSyncSource: %s(%s)\n", mName, mEnabled ? "enabled" : "disabled");
-}
-
-} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/DispSyncSource.h b/services/surfaceflinger/Scheduler/DispSyncSource.h
deleted file mode 100644
index edcd3ac..0000000
--- a/services/surfaceflinger/Scheduler/DispSyncSource.h
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#pragma once
-
-#include <mutex>
-#include <string>
-
-#include "EventThread.h"
-#include "TracedOrdinal.h"
-#include "VSyncDispatch.h"
-
-namespace android::scheduler {
-class CallbackRepeater;
-class VSyncTracker;
-
-class DispSyncSource final : public VSyncSource {
-public:
- DispSyncSource(VSyncDispatch& vSyncDispatch, VSyncTracker& vSyncTracker,
- std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration,
- bool traceVsync, const char* name);
-
- ~DispSyncSource() override;
-
- // The following methods are implementation of VSyncSource.
- const char* getName() const override { return mName; }
- void setVSyncEnabled(bool enable) override;
- void setCallback(VSyncSource::Callback* callback) override;
- void setDuration(std::chrono::nanoseconds workDuration,
- std::chrono::nanoseconds readyDuration) override;
- VSyncData getLatestVSyncData() const override;
-
- void dump(std::string&) const override;
-
-private:
- void onVsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, nsecs_t readyTime);
-
- const char* const mName;
- TracedOrdinal<int> mValue;
-
- const bool mTraceVsync;
- const std::string mVsyncOnLabel;
-
- const VSyncTracker& mVSyncTracker;
-
- std::unique_ptr<CallbackRepeater> mCallbackRepeater;
-
- std::mutex mCallbackMutex;
- VSyncSource::Callback* mCallback GUARDED_BY(mCallbackMutex) = nullptr;
-
- mutable std::mutex mVsyncMutex;
- TracedOrdinal<std::chrono::nanoseconds> mWorkDuration GUARDED_BY(mVsyncMutex);
- std::chrono::nanoseconds mReadyDuration GUARDED_BY(mVsyncMutex);
- bool mEnabled GUARDED_BY(mVsyncMutex) = false;
-};
-
-} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 008d8c4..a902a8e 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -28,6 +28,7 @@
#include <cstdint>
#include <optional>
#include <type_traits>
+#include <utility>
#include <android-base/stringprintf.h>
@@ -43,6 +44,8 @@
#include "DisplayHardware/DisplayMode.h"
#include "FrameTimeline.h"
+#include "VSyncDispatch.h"
+#include "VSyncTracker.h"
#include "EventThread.h"
@@ -235,20 +238,29 @@
namespace impl {
-EventThread::EventThread(std::unique_ptr<VSyncSource> vsyncSource,
+EventThread::EventThread(const char* name, scheduler::VsyncSchedule& vsyncSchedule,
android::frametimeline::TokenManager* tokenManager,
ThrottleVsyncCallback throttleVsyncCallback,
- GetVsyncPeriodFunction getVsyncPeriodFunction)
- : mVSyncSource(std::move(vsyncSource)),
+ GetVsyncPeriodFunction getVsyncPeriodFunction,
+ std::chrono::nanoseconds workDuration,
+ std::chrono::nanoseconds readyDuration)
+ : mThreadName(name),
+ mVsyncTracer(base::StringPrintf("VSYNC-%s", name), 0),
+ mWorkDuration(base::StringPrintf("VsyncWorkDuration-%s", name), workDuration),
+ mReadyDuration(readyDuration),
+ mVsyncSchedule(vsyncSchedule),
+ mVsyncRegistration(
+ vsyncSchedule.getDispatch(),
+ [this](nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) {
+ onVsync(vsyncTime, wakeupTime, readyTime);
+ },
+ name),
mTokenManager(tokenManager),
mThrottleVsyncCallback(std::move(throttleVsyncCallback)),
- mGetVsyncPeriodFunction(std::move(getVsyncPeriodFunction)),
- mThreadName(mVSyncSource->getName()) {
+ mGetVsyncPeriodFunction(std::move(getVsyncPeriodFunction)) {
LOG_ALWAYS_FATAL_IF(getVsyncPeriodFunction == nullptr,
"getVsyncPeriodFunction must not be null");
- mVSyncSource->setCallback(this);
-
mThread = std::thread([this]() NO_THREAD_SAFETY_ANALYSIS {
std::unique_lock<std::mutex> lock(mMutex);
threadMain(lock);
@@ -270,8 +282,6 @@
}
EventThread::~EventThread() {
- mVSyncSource->setCallback(nullptr);
-
{
std::lock_guard<std::mutex> lock(mMutex);
mState = State::Quit;
@@ -283,7 +293,12 @@
void EventThread::setDuration(std::chrono::nanoseconds workDuration,
std::chrono::nanoseconds readyDuration) {
std::lock_guard<std::mutex> lock(mMutex);
- mVSyncSource->setDuration(workDuration, readyDuration);
+ mWorkDuration = workDuration;
+ mReadyDuration = readyDuration;
+
+ mVsyncRegistration.update({.workDuration = mWorkDuration.get().count(),
+ .readyDuration = mReadyDuration.count(),
+ .earliestVsync = mLastVsyncCallbackTime.ns()});
}
sp<EventThreadConnection> EventThread::createEventConnection(
@@ -358,13 +373,14 @@
VsyncEventData vsyncEventData;
nsecs_t frameInterval = mGetVsyncPeriodFunction(connection->mOwnerUid);
vsyncEventData.frameInterval = frameInterval;
- VSyncSource::VSyncData vsyncData;
- {
+ const auto [presentTime, deadline] = [&]() -> std::pair<nsecs_t, nsecs_t> {
std::lock_guard<std::mutex> lock(mMutex);
- vsyncData = mVSyncSource->getLatestVSyncData();
- }
+ const auto vsyncTime = mVsyncSchedule.getTracker().nextAnticipatedVSyncTimeFrom(
+ systemTime() + mWorkDuration.get().count() + mReadyDuration.count());
+ return {vsyncTime, vsyncTime - mReadyDuration.count()};
+ }();
generateFrameTimeline(vsyncEventData, frameInterval, systemTime(SYSTEM_TIME_MONOTONIC),
- vsyncData.expectedPresentationTime, vsyncData.deadlineTimestamp);
+ presentTime, deadline);
return vsyncEventData;
}
@@ -388,13 +404,14 @@
mCondition.notify_all();
}
-void EventThread::onVSyncEvent(nsecs_t timestamp, VSyncSource::VSyncData vsyncData) {
+void EventThread::onVsync(nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) {
std::lock_guard<std::mutex> lock(mMutex);
+ mLastVsyncCallbackTime = TimePoint::fromNs(vsyncTime);
LOG_FATAL_IF(!mVSyncState);
- mPendingEvents.push_back(makeVSync(mVSyncState->displayId, timestamp, ++mVSyncState->count,
- vsyncData.expectedPresentationTime,
- vsyncData.deadlineTimestamp));
+ mVsyncTracer = (mVsyncTracer + 1) % 2;
+ mPendingEvents.push_back(makeVSync(mVSyncState->displayId, wakeupTime, ++mVSyncState->count,
+ vsyncTime, readyTime));
mCondition.notify_all();
}
@@ -456,12 +473,12 @@
auto it = mDisplayEventConnections.begin();
while (it != mDisplayEventConnections.end()) {
if (const auto connection = it->promote()) {
- vsyncRequested |= connection->vsyncRequest != VSyncRequest::None;
-
if (event && shouldConsumeEvent(*event, connection)) {
consumers.push_back(connection);
}
+ vsyncRequested |= connection->vsyncRequest != VSyncRequest::None;
+
++it;
} else {
it = mDisplayEventConnections.erase(it);
@@ -473,25 +490,24 @@
consumers.clear();
}
- State nextState;
if (mVSyncState && vsyncRequested) {
- nextState = mVSyncState->synthetic ? State::SyntheticVSync : State::VSync;
+ mState = mVSyncState->synthetic ? State::SyntheticVSync : State::VSync;
} else {
ALOGW_IF(!mVSyncState, "Ignoring VSYNC request while display is disconnected");
- nextState = State::Idle;
+ mState = State::Idle;
}
- if (mState != nextState) {
- if (mState == State::VSync) {
- mVSyncSource->setVSyncEnabled(false);
- } else if (nextState == State::VSync) {
- mVSyncSource->setVSyncEnabled(true);
- }
-
- mState = nextState;
+ if (mState == State::VSync) {
+ const auto scheduleResult =
+ mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(),
+ .readyDuration = mReadyDuration.count(),
+ .earliestVsync = mLastVsyncCallbackTime.ns()});
+ LOG_ALWAYS_FATAL_IF(!scheduleResult, "Error scheduling callback");
+ } else {
+ mVsyncRegistration.cancel();
}
- if (event) {
+ if (!mPendingEvents.empty()) {
continue;
}
@@ -506,15 +522,6 @@
if (mCondition.wait_for(lock, timeout) == std::cv_status::timeout) {
if (mState == State::VSync) {
ALOGW("Faking VSYNC due to driver stall for thread %s", mThreadName);
- std::string debugInfo = "VsyncSource debug info:\n";
- mVSyncSource->dump(debugInfo);
- // Log the debug info line-by-line to avoid logcat overflow
- auto pos = debugInfo.find('\n');
- while (pos != std::string::npos) {
- ALOGW("%s", debugInfo.substr(0, pos).c_str());
- debugInfo = debugInfo.substr(pos + 1);
- pos = debugInfo.find('\n');
- }
}
LOG_FATAL_IF(!mVSyncState);
@@ -527,6 +534,8 @@
}
}
}
+ // cancel any pending vsync event before exiting
+ mVsyncRegistration.cancel();
}
bool EventThread::shouldConsumeEvent(const DisplayEventReceiver::Event& event,
@@ -657,6 +666,12 @@
StringAppendF(&result, "none\n");
}
+ const auto relativeLastCallTime =
+ ticks<std::milli, float>(mLastVsyncCallbackTime - TimePoint::now());
+ StringAppendF(&result, "mWorkDuration=%.2f mReadyDuration=%.2f last vsync time ",
+ mWorkDuration.get().count() / 1e6f, mReadyDuration.count() / 1e6f);
+ StringAppendF(&result, "%.2fms relative to now\n", relativeLastCallTime);
+
StringAppendF(&result, " pending events (count=%zu):\n", mPendingEvents.size());
for (const auto& event : mPendingEvents) {
StringAppendF(&result, " %s\n", toString(event).c_str());
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 43c3598..ab9085e 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -33,6 +33,9 @@
#include <vector>
#include "DisplayHardware/DisplayMode.h"
+#include "TracedOrdinal.h"
+#include "VSyncDispatch.h"
+#include "VsyncSchedule.h"
// ---------------------------------------------------------------------------
namespace android {
@@ -64,32 +67,6 @@
// Subsequent values are periods.
};
-class VSyncSource {
-public:
- class VSyncData {
- public:
- nsecs_t expectedPresentationTime;
- nsecs_t deadlineTimestamp;
- };
-
- class Callback {
- public:
- virtual ~Callback() {}
- virtual void onVSyncEvent(nsecs_t when, VSyncData vsyncData) = 0;
- };
-
- virtual ~VSyncSource() {}
-
- virtual const char* getName() const = 0;
- virtual void setVSyncEnabled(bool enable) = 0;
- virtual void setCallback(Callback* callback) = 0;
- virtual void setDuration(std::chrono::nanoseconds workDuration,
- std::chrono::nanoseconds readyDuration) = 0;
- virtual VSyncData getLatestVSyncData() const = 0;
-
- virtual void dump(std::string& result) const = 0;
-};
-
class EventThreadConnection : public gui::BnDisplayEventConnection {
public:
EventThreadConnection(EventThread*, uid_t callingUid, ResyncCallback,
@@ -160,13 +137,14 @@
namespace impl {
-class EventThread : public android::EventThread, private VSyncSource::Callback {
+class EventThread : public android::EventThread {
public:
using ThrottleVsyncCallback = std::function<bool(nsecs_t, uid_t)>;
using GetVsyncPeriodFunction = std::function<nsecs_t(uid_t)>;
- EventThread(std::unique_ptr<VSyncSource>, frametimeline::TokenManager*, ThrottleVsyncCallback,
- GetVsyncPeriodFunction);
+ EventThread(const char* name, scheduler::VsyncSchedule&, frametimeline::TokenManager*,
+ ThrottleVsyncCallback, GetVsyncPeriodFunction,
+ std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration);
~EventThread();
sp<EventThreadConnection> createEventConnection(
@@ -213,8 +191,7 @@
void removeDisplayEventConnectionLocked(const wp<EventThreadConnection>& connection)
REQUIRES(mMutex);
- // Implements VSyncSource::Callback
- void onVSyncEvent(nsecs_t timestamp, VSyncSource::VSyncData vsyncData) override;
+ void onVsync(nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime);
int64_t generateToken(nsecs_t timestamp, nsecs_t deadlineTimestamp,
nsecs_t expectedPresentationTime) const;
@@ -222,12 +199,17 @@
nsecs_t timestamp, nsecs_t preferredExpectedPresentationTime,
nsecs_t preferredDeadlineTimestamp) const;
- const std::unique_ptr<VSyncSource> mVSyncSource GUARDED_BY(mMutex);
+ const char* const mThreadName;
+ TracedOrdinal<int> mVsyncTracer;
+ TracedOrdinal<std::chrono::nanoseconds> mWorkDuration GUARDED_BY(mMutex);
+ std::chrono::nanoseconds mReadyDuration GUARDED_BY(mMutex);
+ scheduler::VsyncSchedule& mVsyncSchedule;
+ TimePoint mLastVsyncCallbackTime GUARDED_BY(mMutex) = TimePoint::now();
+ scheduler::VSyncCallbackRegistration mVsyncRegistration GUARDED_BY(mMutex);
frametimeline::TokenManager* const mTokenManager;
const ThrottleVsyncCallback mThrottleVsyncCallback;
const GetVsyncPeriodFunction mGetVsyncPeriodFunction;
- const char* const mThreadName;
std::thread mThread;
mutable std::mutex mMutex;
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp
index ae10ff4..e827c12 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.cpp
+++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp
@@ -78,7 +78,8 @@
void MessageQueue::initVsync(scheduler::VSyncDispatch& dispatch,
frametimeline::TokenManager& tokenManager,
std::chrono::nanoseconds workDuration) {
- setDuration(workDuration);
+ std::lock_guard lock(mVsync.mutex);
+ mVsync.workDuration = workDuration;
mVsync.tokenManager = &tokenManager;
mVsync.registration = std::make_unique<
scheduler::VSyncCallbackRegistration>(dispatch,
@@ -89,16 +90,20 @@
"sf");
}
+void MessageQueue::destroyVsync() {
+ std::lock_guard lock(mVsync.mutex);
+ mVsync.tokenManager = nullptr;
+ mVsync.registration.reset();
+}
+
void MessageQueue::setDuration(std::chrono::nanoseconds workDuration) {
ATRACE_CALL();
std::lock_guard lock(mVsync.mutex);
mVsync.workDuration = workDuration;
- if (mVsync.scheduledFrameTime) {
- mVsync.scheduledFrameTime =
- mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(),
- .readyDuration = 0,
- .earliestVsync = mVsync.lastCallbackTime.ns()});
- }
+ mVsync.scheduledFrameTime =
+ mVsync.registration->update({.workDuration = mVsync.workDuration.get().count(),
+ .readyDuration = 0,
+ .earliestVsync = mVsync.lastCallbackTime.ns()});
}
void MessageQueue::waitMessage() {
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h
index 04de492..71f8645 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.h
+++ b/services/surfaceflinger/Scheduler/MessageQueue.h
@@ -75,6 +75,7 @@
virtual void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&,
std::chrono::nanoseconds workDuration) = 0;
+ virtual void destroyVsync() = 0;
virtual void setDuration(std::chrono::nanoseconds workDuration) = 0;
virtual void waitMessage() = 0;
virtual void postMessage(sp<MessageHandler>&&) = 0;
@@ -138,6 +139,7 @@
void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&,
std::chrono::nanoseconds workDuration) override;
+ void destroyVsync() override;
void setDuration(std::chrono::nanoseconds workDuration) override;
void waitMessage() override;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 04c2d41..30821d8 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -32,6 +32,7 @@
#include <ftl/fake_guard.h>
#include <ftl/match.h>
#include <ftl/unit.h>
+#include <gui/TraceUtils.h>
#include <scheduler/FrameRateMode.h>
#include <utils/Trace.h>
@@ -416,8 +417,10 @@
// Keep the display at max frame rate for the duration of powering on the display.
if (signals.powerOnImminent) {
ALOGV("Power On Imminent");
- return {rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Descending),
- GlobalSignals{.powerOnImminent = true}};
+ const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Descending);
+ ATRACE_FORMAT_INSTANT("%s (Power On Imminent)",
+ to_string(ranking.front().frameRateMode.fps).c_str());
+ return {ranking, GlobalSignals{.powerOnImminent = true}};
}
int noVoteLayers = 0;
@@ -476,8 +479,10 @@
// selected a refresh rate to see if we should apply touch boost.
if (signals.touch && !hasExplicitVoteLayers) {
ALOGV("Touch Boost");
- return {rankFrameRates(anchorGroup, RefreshRateOrder::Descending),
- GlobalSignals{.touch = true}};
+ const auto ranking = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
+ ATRACE_FORMAT_INSTANT("%s (Touch Boost)",
+ to_string(ranking.front().frameRateMode.fps).c_str());
+ return {ranking, GlobalSignals{.touch = true}};
}
// If the primary range consists of a single refresh rate then we can only
@@ -488,19 +493,26 @@
if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
ALOGV("Idle");
- return {rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending),
- GlobalSignals{.idle = true}};
+ const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending);
+ ATRACE_FORMAT_INSTANT("%s (Idle)", to_string(ranking.front().frameRateMode.fps).c_str());
+ return {ranking, GlobalSignals{.idle = true}};
}
if (layers.empty() || noVoteLayers == layers.size()) {
ALOGV("No layers with votes");
- return {rankFrameRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
+ const auto ranking = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
+ ATRACE_FORMAT_INSTANT("%s (No layers with votes)",
+ to_string(ranking.front().frameRateMode.fps).c_str());
+ return {ranking, kNoSignals};
}
// Only if all layers want Min we should return Min
if (noVoteLayers + minVoteLayers == layers.size()) {
ALOGV("All layers Min");
- return {rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending), kNoSignals};
+ const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending);
+ ATRACE_FORMAT_INSTANT("%s (All layers Min)",
+ to_string(ranking.front().frameRateMode.fps).c_str());
+ return {ranking, kNoSignals};
}
// Find the best refresh rate based on score
@@ -670,8 +682,13 @@
// range instead of picking a random score from the app range.
if (noLayerScore) {
ALOGV("Layers not scored");
- return {rankFrameRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
+ const auto descending = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
+ ATRACE_FORMAT_INSTANT("%s (Layers not scored)",
+ to_string(descending.front().frameRateMode.fps).c_str());
+ return {descending, kNoSignals};
} else {
+ ATRACE_FORMAT_INSTANT("%s (primaryRangeIsSingleRate)",
+ to_string(ranking.front().frameRateMode.fps).c_str());
return {ranking, kNoSignals};
}
}
@@ -696,17 +713,22 @@
if (signals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) {
ALOGV("Touch Boost");
+ ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])",
+ to_string(touchRefreshRates.front().frameRateMode.fps).c_str());
return {touchRefreshRates, GlobalSignals{.touch = true}};
}
// If we never scored any layers, and we don't favor high refresh rates, prefer to stay with the
// current config
if (noLayerScore && refreshRateOrder == RefreshRateOrder::Ascending) {
- const auto preferredDisplayMode = activeMode.getId();
- return {rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, preferredDisplayMode),
- kNoSignals};
+ const auto ascendingWithPreferred =
+ rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId());
+ ATRACE_FORMAT_INSTANT("%s (preferredDisplayMode)",
+ to_string(ascendingWithPreferred.front().frameRateMode.fps).c_str());
+ return {ascendingWithPreferred, kNoSignals};
}
+ ATRACE_FORMAT_INSTANT("%s (scored))", to_string(ranking.front().frameRateMode.fps).c_str());
return {ranking, kNoSignals};
}
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 74a81b7..bc465ce 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -42,7 +42,6 @@
#include <numeric>
#include "../Layer.h"
-#include "DispSyncSource.h"
#include "Display/DisplayMap.h"
#include "EventThread.h"
#include "FrameRateOverrideMappings.h"
@@ -65,6 +64,11 @@
: impl::MessageQueue(compositor), mFeatures(features), mSchedulerCallback(callback) {}
Scheduler::~Scheduler() {
+ // MessageQueue depends on VsyncSchedule, so first destroy it.
+ // Otherwise, MessageQueue will get destroyed after Scheduler's dtor,
+ // which will cause a use-after-free issue.
+ Impl::destroyVsync();
+
// Stop timers and wait for their threads to exit.
mDisplayPowerTimer.reset();
mTouchTimer.reset();
@@ -142,14 +146,6 @@
mVsyncSchedule.emplace(features);
}
-std::unique_ptr<VSyncSource> Scheduler::makePrimaryDispSyncSource(
- const char* name, std::chrono::nanoseconds workDuration,
- std::chrono::nanoseconds readyDuration, bool traceVsync) {
- return std::make_unique<scheduler::DispSyncSource>(mVsyncSchedule->getDispatch(),
- mVsyncSchedule->getTracker(), workDuration,
- readyDuration, traceVsync, name);
-}
-
std::optional<Fps> Scheduler::getFrameRateOverride(uid_t uid) const {
const bool supportsFrameRateOverrideByContent =
leaderSelectorPtr()->supportsAppFrameRateOverrideByContent();
@@ -194,12 +190,12 @@
frametimeline::TokenManager* tokenManager,
std::chrono::nanoseconds workDuration,
std::chrono::nanoseconds readyDuration) {
- auto vsyncSource = makePrimaryDispSyncSource(connectionName, workDuration, readyDuration);
auto throttleVsync = makeThrottleVsyncCallback();
auto getVsyncPeriod = makeGetVsyncPeriodFunction();
- auto eventThread = std::make_unique<impl::EventThread>(std::move(vsyncSource), tokenManager,
- std::move(throttleVsync),
- std::move(getVsyncPeriod));
+ auto eventThread =
+ std::make_unique<impl::EventThread>(connectionName, *mVsyncSchedule, tokenManager,
+ std::move(throttleVsync), std::move(getVsyncPeriod),
+ workDuration, readyDuration);
return createConnection(std::move(eventThread));
}
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index f189426..20221d1 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -223,11 +223,6 @@
size_t getEventThreadConnectionCount(ConnectionHandle handle);
- std::unique_ptr<VSyncSource> makePrimaryDispSyncSource(const char* name,
- std::chrono::nanoseconds workDuration,
- std::chrono::nanoseconds readyDuration,
- bool traceVsync = true);
-
// Stores the preferred refresh rate that an app should run at.
// FrameRateOverride.refreshRateHz == 0 means no preference.
void setPreferredRefreshRateForUid(FrameRateOverride);
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatch.h b/services/surfaceflinger/Scheduler/VSyncDispatch.h
index 2bfe204..9520131 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatch.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatch.h
@@ -126,6 +126,17 @@
*/
virtual ScheduleResult schedule(CallbackToken token, ScheduleTiming scheduleTiming) = 0;
+ /*
+ * Update the timing information for a scheduled callback.
+ * If the callback is not scheduled, then this function does nothing.
+ *
+ * \param [in] token The callback to schedule.
+ * \param [in] scheduleTiming The timing information for this schedule call
+ * \return The expected callback time if a callback was scheduled.
+ * std::nullopt if the callback is not registered.
+ */
+ virtual ScheduleResult update(CallbackToken token, ScheduleTiming scheduleTiming) = 0;
+
/* Cancels a scheduled callback, if possible.
*
* \param [in] token The callback to cancel.
@@ -159,6 +170,9 @@
// See documentation for VSyncDispatch::schedule.
ScheduleResult schedule(VSyncDispatch::ScheduleTiming scheduleTiming);
+ // See documentation for VSyncDispatch::update.
+ ScheduleResult update(VSyncDispatch::ScheduleTiming scheduleTiming);
+
// See documentation for VSyncDispatch::cancel.
CancelResult cancel();
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index cc9f7cf..73d52cf 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -347,38 +347,54 @@
ScheduleResult VSyncDispatchTimerQueue::schedule(CallbackToken token,
ScheduleTiming scheduleTiming) {
- ScheduleResult result;
- {
- std::lock_guard lock(mMutex);
+ std::lock_guard lock(mMutex);
+ return scheduleLocked(token, scheduleTiming);
+}
- auto it = mCallbacks.find(token);
- if (it == mCallbacks.end()) {
- return result;
- }
- auto& callback = it->second;
- auto const now = mTimeKeeper->now();
+ScheduleResult VSyncDispatchTimerQueue::scheduleLocked(CallbackToken token,
+ ScheduleTiming scheduleTiming) {
+ auto it = mCallbacks.find(token);
+ if (it == mCallbacks.end()) {
+ return {};
+ }
+ auto& callback = it->second;
+ auto const now = mTimeKeeper->now();
- /* If the timer thread will run soon, we'll apply this work update via the callback
- * timer recalculation to avoid cancelling a callback that is about to fire. */
- auto const rearmImminent = now > mIntendedWakeupTime;
- if (CC_UNLIKELY(rearmImminent)) {
- callback->addPendingWorkloadUpdate(scheduleTiming);
- return getExpectedCallbackTime(mTracker, now, scheduleTiming);
- }
+ /* If the timer thread will run soon, we'll apply this work update via the callback
+ * timer recalculation to avoid cancelling a callback that is about to fire. */
+ auto const rearmImminent = now > mIntendedWakeupTime;
+ if (CC_UNLIKELY(rearmImminent)) {
+ callback->addPendingWorkloadUpdate(scheduleTiming);
+ return getExpectedCallbackTime(mTracker, now, scheduleTiming);
+ }
- result = callback->schedule(scheduleTiming, mTracker, now);
- if (!result.has_value()) {
- return result;
- }
+ const ScheduleResult result = callback->schedule(scheduleTiming, mTracker, now);
+ if (!result.has_value()) {
+ return {};
+ }
- if (callback->wakeupTime() < mIntendedWakeupTime - mTimerSlack) {
- rearmTimerSkippingUpdateFor(now, it);
- }
+ if (callback->wakeupTime() < mIntendedWakeupTime - mTimerSlack) {
+ rearmTimerSkippingUpdateFor(now, it);
}
return result;
}
+ScheduleResult VSyncDispatchTimerQueue::update(CallbackToken token, ScheduleTiming scheduleTiming) {
+ std::lock_guard lock(mMutex);
+ const auto it = mCallbacks.find(token);
+ if (it == mCallbacks.end()) {
+ return {};
+ }
+
+ auto& callback = it->second;
+ if (!callback->targetVsync().has_value()) {
+ return {};
+ }
+
+ return scheduleLocked(token, scheduleTiming);
+}
+
CancelResult VSyncDispatchTimerQueue::cancel(CallbackToken token) {
std::lock_guard lock(mMutex);
@@ -451,6 +467,13 @@
return mDispatch.get().schedule(mToken, scheduleTiming);
}
+ScheduleResult VSyncCallbackRegistration::update(VSyncDispatch::ScheduleTiming scheduleTiming) {
+ if (!mValidToken) {
+ return std::nullopt;
+ }
+ return mDispatch.get().update(mToken, scheduleTiming);
+}
+
CancelResult VSyncCallbackRegistration::cancel() {
if (!mValidToken) {
return CancelResult::Error;
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
index 4f2f87a..c3af136 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
@@ -127,6 +127,7 @@
CallbackToken registerCallback(Callback, std::string callbackName) final;
void unregisterCallback(CallbackToken) final;
ScheduleResult schedule(CallbackToken, ScheduleTiming) final;
+ ScheduleResult update(CallbackToken, ScheduleTiming) final;
CancelResult cancel(CallbackToken) final;
void dump(std::string&) const final;
@@ -143,6 +144,7 @@
void rearmTimerSkippingUpdateFor(nsecs_t now, CallbackMap::iterator const& skipUpdate)
REQUIRES(mMutex);
void cancelTimer() REQUIRES(mMutex);
+ ScheduleResult scheduleLocked(CallbackToken, ScheduleTiming) REQUIRES(mMutex);
static constexpr nsecs_t kInvalidTime = std::numeric_limits<int64_t>::max();
std::unique_ptr<TimeKeeper> const mTimeKeeper;
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index ed4d25e..02e12fd 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -47,7 +47,7 @@
VSyncPredictor::VSyncPredictor(nsecs_t idealPeriod, size_t historySize,
size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent)
- : mTraceOn(property_get_bool("debug.sf.vsp_trace", true)),
+ : mTraceOn(property_get_bool("debug.sf.vsp_trace", false)),
kHistorySize(historySize),
kMinimumSamplesForPrediction(minimumSamplesForPrediction),
kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
@@ -61,6 +61,10 @@
}
}
+inline void VSyncPredictor::traceInt64(const char* name, int64_t value) const {
+ ATRACE_INT64(name, value);
+}
+
inline size_t VSyncPredictor::next(size_t i) const {
return (i + 1) % mTimestamps.size();
}
@@ -124,6 +128,8 @@
mTimestamps[mLastTimestampIndex] = timestamp;
}
+ traceInt64If("VSP-ts", timestamp);
+
const size_t numSamples = mTimestamps.size();
if (numSamples < kMinimumSamplesForPrediction) {
mRateMap[mIdealPeriod] = {mIdealPeriod, 0};
@@ -161,8 +167,6 @@
nsecs_t meanOrdinal = 0;
for (size_t i = 0; i < numSamples; i++) {
- traceInt64If("VSP-ts", mTimestamps[i]);
-
const auto timestamp = mTimestamps[i] - oldestTS;
vsyncTS[i] = timestamp;
meanTS += timestamp;
@@ -219,7 +223,7 @@
auto const [slope, intercept] = getVSyncPredictionModelLocked();
if (mTimestamps.empty()) {
- traceInt64If("VSP-mode", 1);
+ traceInt64("VSP-mode", 1);
auto const knownTimestamp = mKnownTimestamp ? *mKnownTimestamp : timePoint;
auto const numPeriodsOut = ((timePoint - knownTimestamp) / mIdealPeriod) + 1;
return knownTimestamp + numPeriodsOut * mIdealPeriod;
@@ -232,7 +236,7 @@
auto const ordinalRequest = (timePoint - zeroPoint + slope) / slope;
auto const prediction = (ordinalRequest * slope) + intercept + oldest;
- traceInt64If("VSP-mode", 0);
+ traceInt64("VSP-mode", 0);
traceInt64If("VSP-timePoint", timePoint);
traceInt64If("VSP-prediction", prediction);
@@ -340,6 +344,7 @@
void VSyncPredictor::setPeriod(nsecs_t period) {
ATRACE_CALL();
+ traceInt64("VSP-setPeriod", period);
std::lock_guard lock(mMutex);
static constexpr size_t kSizeLimit = 30;
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 4a3ba67..305cdb0 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -77,6 +77,7 @@
void clearTimestamps() REQUIRES(mMutex);
inline void traceInt64If(const char* name, int64_t value) const;
+ inline void traceInt64(const char* name, int64_t value) const;
bool const mTraceOn;
size_t const kHistorySize;
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index e23945d..b5f212e 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -129,7 +129,7 @@
}
void VSyncReactor::startPeriodTransition(nsecs_t period) {
- ATRACE_INT64("VSR-setPeriod", period);
+ ATRACE_INT64("VSR-startPeriodTransition", period);
std::lock_guard lock(mMutex);
mLastHwVsync.reset();
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index 8c17409..173b1d0 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -22,6 +22,14 @@
#include <scheduler/Features.h>
#include <scheduler/Time.h>
+namespace android {
+class EventThreadTest;
+}
+
+namespace android::fuzz {
+class SchedulerFuzzer;
+}
+
namespace android::scheduler {
// TODO(b/185535769): Rename classes, and remove aliases.
@@ -54,6 +62,8 @@
private:
friend class TestableScheduler;
+ friend class android::EventThreadTest;
+ friend class android::fuzz::SchedulerFuzzer;
using TrackerPtr = std::unique_ptr<VsyncTracker>;
using DispatchPtr = std::unique_ptr<VsyncDispatch>;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index ba5c4d7..40de4d6 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -461,6 +461,8 @@
mRefreshRateOverlaySpinner = property_get_bool("debug.sf.show_refresh_rate_overlay_spinner", 0);
mRefreshRateOverlayRenderRate =
property_get_bool("debug.sf.show_refresh_rate_overlay_render_rate", 0);
+ mRefreshRateOverlayShowInMiddle =
+ property_get_bool("debug.sf.show_refresh_rate_overlay_in_middle", 0);
if (!mIsUserBuild && base::GetBoolProperty("debug.sf.enable_transaction_tracing"s, true)) {
mTransactionTracing.emplace();
@@ -1150,6 +1152,10 @@
switch (display->setDesiredActiveMode(DisplayDevice::ActiveModeInfo(std::move(request)),
force)) {
case DisplayDevice::DesiredActiveModeAction::InitiateDisplayModeSwitch:
+ // Set the render rate as setDesiredActiveMode updated it.
+ mScheduler->setRenderRate(display->refreshRateSelector().getActiveMode().fps);
+
+ // Schedule a new frame to initiate the display mode switch.
scheduleComposite(FrameHint::kNone);
// Start receiving vsync samples now, so that we can detect a period
@@ -1538,6 +1544,80 @@
return future.get();
}
+status_t SurfaceFlinger::getHdrConversionCapabilities(
+ std::vector<gui::HdrConversionCapability>* hdrConversionCapabilities) const {
+ bool hdrOutputConversionSupport;
+ getHdrOutputConversionSupport(&hdrOutputConversionSupport);
+ if (hdrOutputConversionSupport == false) {
+ ALOGE("hdrOutputConversion is not supported by this device.");
+ return INVALID_OPERATION;
+ }
+ const auto aidlConversionCapability = getHwComposer().getHdrConversionCapabilities();
+ for (auto capability : aidlConversionCapability) {
+ gui::HdrConversionCapability tempCapability;
+ tempCapability.sourceType = static_cast<int>(capability.sourceType.hdr);
+ tempCapability.outputType = static_cast<int>(capability.outputType->hdr);
+ tempCapability.addsLatency = capability.addsLatency;
+ hdrConversionCapabilities->push_back(tempCapability);
+ }
+ return NO_ERROR;
+}
+
+status_t SurfaceFlinger::setHdrConversionStrategy(
+ const gui::HdrConversionStrategy& hdrConversionStrategy) {
+ bool hdrOutputConversionSupport;
+ getHdrOutputConversionSupport(&hdrOutputConversionSupport);
+ if (hdrOutputConversionSupport == false) {
+ ALOGE("hdrOutputConversion is not supported by this device.");
+ return INVALID_OPERATION;
+ }
+ auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) mutable -> status_t {
+ using AidlHdrConversionStrategy =
+ aidl::android::hardware::graphics::common::HdrConversionStrategy;
+ using GuiHdrConversionStrategyTag = gui::HdrConversionStrategy::Tag;
+ AidlHdrConversionStrategy aidlConversionStrategy;
+ switch (hdrConversionStrategy.getTag()) {
+ case GuiHdrConversionStrategyTag::passthrough: {
+ aidlConversionStrategy.set<AidlHdrConversionStrategy::Tag::passthrough>(
+ hdrConversionStrategy.get<GuiHdrConversionStrategyTag::passthrough>());
+ return getHwComposer().setHdrConversionStrategy(aidlConversionStrategy);
+ }
+ case GuiHdrConversionStrategyTag::autoAllowedHdrTypes: {
+ auto autoHdrTypes =
+ hdrConversionStrategy
+ .get<GuiHdrConversionStrategyTag::autoAllowedHdrTypes>();
+ std::vector<aidl::android::hardware::graphics::common::Hdr> aidlAutoHdrTypes;
+ for (auto type : autoHdrTypes) {
+ aidlAutoHdrTypes.push_back(
+ static_cast<aidl::android::hardware::graphics::common::Hdr>(type));
+ }
+ aidlConversionStrategy.set<AidlHdrConversionStrategy::Tag::autoAllowedHdrTypes>(
+ aidlAutoHdrTypes);
+ return getHwComposer().setHdrConversionStrategy(aidlConversionStrategy);
+ }
+ case GuiHdrConversionStrategyTag::forceHdrConversion: {
+ auto forceHdrConversion =
+ hdrConversionStrategy
+ .get<GuiHdrConversionStrategyTag::forceHdrConversion>();
+ aidlConversionStrategy.set<AidlHdrConversionStrategy::Tag::forceHdrConversion>(
+ static_cast<aidl::android::hardware::graphics::common::Hdr>(
+ forceHdrConversion));
+ return getHwComposer().setHdrConversionStrategy(aidlConversionStrategy);
+ }
+ }
+ });
+ return future.get();
+}
+
+status_t SurfaceFlinger::getHdrOutputConversionSupport(bool* outSupport) const {
+ auto future = mScheduler->schedule([this] {
+ return getHwComposer().hasCapability(Capability::HDR_OUTPUT_CONVERSION_CONFIG);
+ });
+
+ *outSupport = future.get();
+ return NO_ERROR;
+}
+
void SurfaceFlinger::setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on) {
const char* const whence = __func__;
static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
@@ -1757,17 +1837,6 @@
return NO_ERROR;
}
-bool SurfaceFlinger::hasVisibleHdrLayer(const sp<DisplayDevice>& display) {
- bool hasHdrLayers = false;
- mDrawingState.traverse([&,
- compositionDisplay = display->getCompositionDisplay()](Layer* layer) {
- hasHdrLayers |= (layer->isVisible() &&
- compositionDisplay->includesLayer(layer->getCompositionEngineLayerFE()) &&
- isHdrDataspace(layer->getDataSpace()));
- });
- return hasHdrLayers;
-}
-
status_t SurfaceFlinger::setDisplayBrightness(const sp<IBinder>& displayToken,
const gui::DisplayBrightness& brightness) {
if (!displayToken) {
@@ -2562,7 +2631,8 @@
int32_t maxArea = 0;
mDrawingState.traverse([&, compositionDisplay = compositionDisplay](Layer* layer) {
const auto layerFe = layer->getCompositionEngineLayerFE();
- if (layer->isVisible() && compositionDisplay->includesLayer(layerFe)) {
+ if (layer->isVisible() &&
+ compositionDisplay->includesLayer(layer->getOutputFilter())) {
if (isHdrLayer(layer)) {
const auto* outputLayer =
compositionDisplay->getOutputLayerForLayer(layerFe);
@@ -6995,7 +7065,8 @@
if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal) {
if (const auto device = getDisplayDeviceLocked(id)) {
device->enableRefreshRateOverlay(enable, mRefreshRateOverlaySpinner,
- mRefreshRateOverlayRenderRate);
+ mRefreshRateOverlayRenderRate,
+ mRefreshRateOverlayShowInMiddle);
}
}
}
@@ -7157,7 +7228,8 @@
layerName, static_cast<uint32_t>(mMaxRenderTargetSize));
ALOGD("%s", errorMessage.c_str());
if (bufferData.releaseBufferListener) {
- bufferData.releaseBufferListener->onTransactionQueueStalled(errorMessage);
+ bufferData.releaseBufferListener->onTransactionQueueStalled(
+ String8(errorMessage.c_str()));
}
return nullptr;
}
@@ -7175,7 +7247,7 @@
if (bufferData.releaseBufferListener) {
bufferData.releaseBufferListener->onTransactionQueueStalled(
- "Buffer processing hung due to full buffer cache");
+ String8("Buffer processing hung due to full buffer cache"));
}
}
@@ -7581,6 +7653,32 @@
return binderStatusFromStatusT(status);
}
+binder::Status SurfaceComposerAIDL::getHdrConversionCapabilities(
+ std::vector<gui::HdrConversionCapability>* hdrConversionCapabilities) {
+ status_t status = checkAccessPermission();
+ if (status == OK) {
+ status = mFlinger->getHdrConversionCapabilities(hdrConversionCapabilities);
+ }
+ return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::setHdrConversionStrategy(
+ const gui::HdrConversionStrategy& hdrConversionStrategy) {
+ status_t status = checkAccessPermission();
+ if (status == OK) {
+ status = mFlinger->setHdrConversionStrategy(hdrConversionStrategy);
+ }
+ return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::getHdrOutputConversionSupport(bool* outMode) {
+ status_t status = checkAccessPermission();
+ if (status == OK) {
+ status = mFlinger->getHdrOutputConversionSupport(outMode);
+ }
+ return binderStatusFromStatusT(status);
+}
+
binder::Status SurfaceComposerAIDL::setAutoLowLatencyMode(const sp<IBinder>& display, bool on) {
status_t status = checkAccessPermission();
if (status != OK) {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 37aa3d5..5457be8 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -26,7 +26,6 @@
#include <android/gui/DisplayStatInfo.h>
#include <android/gui/DisplayState.h>
#include <android/gui/ISurfaceComposerClient.h>
-#include <android/gui/ITransactionCompletedListener.h>
#include <cutils/atomic.h>
#include <cutils/compiler.h>
#include <ftl/future.h>
@@ -35,8 +34,8 @@
#include <gui/CompositorTiming.h>
#include <gui/FrameTimestamps.h>
#include <gui/ISurfaceComposer.h>
+#include <gui/ITransactionCompletedListener.h>
#include <gui/LayerDebugInfo.h>
-
#include <gui/LayerState.h>
#include <layerproto/LayerProtoHeader.h>
#include <math/mat4.h>
@@ -127,9 +126,7 @@
using gui::CaptureArgs;
using gui::DisplayCaptureArgs;
using gui::IRegionSamplingListener;
-using gui::ITransactionCompletedListener;
using gui::LayerCaptureArgs;
-
using gui::ScreenCaptureResults;
namespace frametimeline {
@@ -534,6 +531,10 @@
status_t setBootDisplayMode(const sp<display::DisplayToken>&, DisplayModeId);
status_t getOverlaySupport(gui::OverlayProperties* outProperties) const;
status_t clearBootDisplayMode(const sp<IBinder>& displayToken);
+ status_t getHdrConversionCapabilities(
+ std::vector<gui::HdrConversionCapability>* hdrConversionCapaabilities) const;
+ status_t setHdrConversionStrategy(const gui::HdrConversionStrategy& hdrConversionStrategy);
+ status_t getHdrOutputConversionSupport(bool* outSupport) const;
void setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on);
void setGameContentType(const sp<IBinder>& displayToken, bool on);
void setPowerMode(const sp<IBinder>& displayToken, int mode);
@@ -653,6 +654,8 @@
bool mRefreshRateOverlaySpinner = false;
// Show render rate with refresh rate overlay
bool mRefreshRateOverlayRenderRate = false;
+ // Show render rate overlay offseted to the middle of the screen (e.g. for circular displays)
+ bool mRefreshRateOverlayShowInMiddle = false;
void setDesiredActiveMode(display::DisplayModeRequest&&, bool force = false)
REQUIRES(mStateLock);
@@ -670,9 +673,6 @@
void setPowerModeInternal(const sp<DisplayDevice>& display, hal::PowerMode mode)
REQUIRES(mStateLock, kMainThreadContext);
- // Returns true if the display has a visible HDR layer in its layer stack.
- bool hasVisibleHdrLayer(const sp<DisplayDevice>& display) REQUIRES(mStateLock);
-
// Returns the preferred mode for PhysicalDisplayId if the Scheduler has selected one for that
// display. Falls back to the display's defaultModeId otherwise.
ftl::Optional<scheduler::FrameRateMode> getPreferredDisplayMode(
@@ -1432,6 +1432,11 @@
binder::Status clearBootDisplayMode(const sp<IBinder>& display) override;
binder::Status getBootDisplayModeSupport(bool* outMode) override;
binder::Status getOverlaySupport(gui::OverlayProperties* outProperties) override;
+ binder::Status getHdrConversionCapabilities(
+ std::vector<gui::HdrConversionCapability>*) override;
+ binder::Status setHdrConversionStrategy(
+ const gui::HdrConversionStrategy& hdrConversionStrategy) override;
+ binder::Status getHdrOutputConversionSupport(bool* outSupport) override;
binder::Status setAutoLowLatencyMode(const sp<IBinder>& display, bool on) override;
binder::Status setGameContentType(const sp<IBinder>& display, bool on) override;
binder::Status captureDisplay(const DisplayCaptureArgs&,
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h
index c09bcce..61ff9bc 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.h
+++ b/services/surfaceflinger/TransactionCallbackInvoker.h
@@ -26,27 +26,14 @@
#include <unordered_set>
#include <android-base/thread_annotations.h>
-#include <android/gui/ITransactionCompletedListener.h>
-
#include <binder/IBinder.h>
-#include <gui/ListenerStats.h>
-#include <gui/ReleaseCallbackId.h>
-#include <renderengine/RenderEngine.h>
+#include <ftl/future.h>
+#include <gui/ITransactionCompletedListener.h>
#include <ui/Fence.h>
#include <ui/FenceResult.h>
namespace android {
-using gui::CallbackId;
-using gui::FrameEventHistoryStats;
-using gui::IListenerHash;
-using gui::ITransactionCompletedListener;
-using gui::JankData;
-using gui::ListenerCallbacks;
-using gui::ListenerStats;
-using gui::ReleaseCallbackId;
-using gui::TransactionStats;
-
class CallbackHandle : public RefBase {
public:
CallbackHandle(const sp<IBinder>& transactionListener, const std::vector<CallbackId>& ids,
diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h
index 380301f..366b09d 100644
--- a/services/surfaceflinger/TransactionState.h
+++ b/services/surfaceflinger/TransactionState.h
@@ -91,7 +91,9 @@
if (!displays.empty()) return true;
for (const auto& state : states) {
- if (state.state.frameRateCompatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE) {
+ const bool frameRateChanged = state.state.what & layer_state_t::eFrameRateChanged;
+ if (!frameRateChanged ||
+ state.state.frameRateCompatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE) {
return true;
}
}
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index 7959e52..0af3efa 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -21,13 +21,15 @@
#include <scheduler/PresentLatencyTracker.h>
-#include "Scheduler/DispSyncSource.h"
#include "Scheduler/OneShotTimer.h"
#include "Scheduler/RefreshRateSelector.h"
#include "Scheduler/VSyncDispatchTimerQueue.h"
#include "Scheduler/VSyncPredictor.h"
#include "Scheduler/VSyncReactor.h"
+#include "mock/MockVSyncDispatch.h"
+#include "mock/MockVSyncTracker.h"
+
#include "surfaceflinger_fuzzers_utils.h"
#include "surfaceflinger_scheduler_fuzzer.h"
@@ -53,7 +55,7 @@
component->dump(res);
}
-class SchedulerFuzzer : private VSyncSource::Callback {
+class SchedulerFuzzer {
public:
SchedulerFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
void process();
@@ -66,7 +68,6 @@
void fuzzVSyncPredictor();
void fuzzVSyncReactor();
void fuzzLayerHistory();
- void fuzzDispSyncSource();
void fuzzCallbackToken(scheduler::VSyncDispatchTimerQueue* dispatch);
void fuzzVSyncDispatchTimerQueue();
void fuzzOneShotTimer();
@@ -75,8 +76,7 @@
FuzzedDataProvider mFdp;
-protected:
- void onVSyncEvent(nsecs_t /* when */, VSyncSource::VSyncData) {}
+ std::optional<scheduler::VsyncSchedule> mVsyncSchedule;
};
PhysicalDisplayId SchedulerFuzzer::getPhysicalDisplayId() {
@@ -90,10 +90,14 @@
}
void SchedulerFuzzer::fuzzEventThread() {
+ mVsyncSchedule.emplace(scheduler::VsyncSchedule(std::make_unique<mock::VSyncTracker>(),
+ std::make_unique<mock::VSyncDispatch>(),
+ nullptr));
const auto getVsyncPeriod = [](uid_t /* uid */) { return kSyncPeriod.count(); };
std::unique_ptr<android::impl::EventThread> thread = std::make_unique<
- android::impl::EventThread>(std::move(std::make_unique<FuzzImplVSyncSource>()), nullptr,
- nullptr, getVsyncPeriod);
+ android::impl::EventThread>("fuzzer", *mVsyncSchedule, nullptr, nullptr, getVsyncPeriod,
+ (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(),
+ (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>());
thread->onHotplugReceived(getPhysicalDisplayId(), mFdp.ConsumeBool());
sp<EventThreadConnection> connection =
@@ -110,24 +114,6 @@
dump<android::impl::EventThread>(thread.get(), &mFdp);
}
-void SchedulerFuzzer::fuzzDispSyncSource() {
- std::unique_ptr<FuzzImplVSyncDispatch> vSyncDispatch =
- std::make_unique<FuzzImplVSyncDispatch>();
- std::unique_ptr<FuzzImplVSyncTracker> vSyncTracker = std::make_unique<FuzzImplVSyncTracker>();
- std::unique_ptr<scheduler::DispSyncSource> dispSyncSource = std::make_unique<
- scheduler::DispSyncSource>(*vSyncDispatch, *vSyncTracker,
- (std::chrono::nanoseconds)
- mFdp.ConsumeIntegral<uint64_t>() /*workDuration*/,
- (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>()
- /*readyDuration*/,
- mFdp.ConsumeBool(),
- mFdp.ConsumeRandomLengthString(kRandomStringLength).c_str());
- dispSyncSource->setVSyncEnabled(true);
- dispSyncSource->setCallback(this);
- dispSyncSource->setDuration((std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(), 0ns);
- dump<scheduler::DispSyncSource>(dispSyncSource.get(), &mFdp);
-}
-
void SchedulerFuzzer::fuzzCallbackToken(scheduler::VSyncDispatchTimerQueue* dispatch) {
scheduler::VSyncDispatch::CallbackToken tmp = dispatch->registerCallback(
[&](auto, auto, auto) {
@@ -417,7 +403,6 @@
fuzzVSyncPredictor();
fuzzVSyncReactor();
fuzzLayerHistory();
- fuzzDispSyncSource();
fuzzEventThread();
fuzzVSyncDispatchTimerQueue();
fuzzOneShotTimer();
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
index 2bc5b46..713b710 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
@@ -78,22 +78,6 @@
sp<Layer> createClone() override { return nullptr; }
};
-class FuzzImplVSyncSource : public VSyncSource {
-public:
- const char* getName() const override { return "fuzz"; }
-
- void setVSyncEnabled(bool /* enable */) override {}
-
- void setCallback(Callback* /* callback */) override {}
-
- void setDuration(std::chrono::nanoseconds /* workDuration */,
- std::chrono::nanoseconds /* readyDuration */) override {}
-
- VSyncData getLatestVSyncData() const override { return {}; }
-
- void dump(std::string& /* result */) const override {}
-};
-
class FuzzImplVSyncTracker : public scheduler::VSyncTracker {
public:
FuzzImplVSyncTracker(nsecs_t period) { mPeriod = period; }
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 6d1b3fe..87c3c65 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -36,6 +36,7 @@
"mock/MockNativeWindowSurface.cpp",
"mock/MockTimeStats.cpp",
"mock/MockVsyncController.cpp",
+ "mock/MockVSyncDispatch.cpp",
"mock/MockVSyncTracker.cpp",
"mock/system/window/MockNativeWindow.cpp",
],
@@ -71,7 +72,6 @@
"libsurfaceflinger_unittest_main.cpp",
"AidlPowerHalWrapperTest.cpp",
"CompositionTest.cpp",
- "DispSyncSourceTest.cpp",
"DisplayIdGeneratorTest.cpp",
"DisplayTransactionTest.cpp",
"DisplayDevice_GetBestColorModeTest.cpp",
@@ -94,6 +94,7 @@
"LayerMetadataTest.cpp",
"LayerHierarchyTest.cpp",
"LayerLifecycleManagerTest.cpp",
+ "LayerSnapshotTest.cpp",
"LayerTest.cpp",
"LayerTestUtils.cpp",
"MessageQueueTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp b/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp
deleted file mode 100644
index 67ace1a..0000000
--- a/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright 2019 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 "LibSurfaceFlingerUnittests"
-#define LOG_NDEBUG 0
-
-#include <inttypes.h>
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include <log/log.h>
-
-#include "AsyncCallRecorder.h"
-#include "Scheduler/DispSyncSource.h"
-#include "Scheduler/VSyncDispatch.h"
-#include "mock/MockVSyncTracker.h"
-
-namespace android {
-namespace {
-
-using namespace std::chrono_literals;
-using namespace testing;
-
-class MockVSyncDispatch : public scheduler::VSyncDispatch {
-public:
- MOCK_METHOD(CallbackToken, registerCallback, (Callback, std::string), (override));
- MOCK_METHOD(void, unregisterCallback, (CallbackToken), (override));
- MOCK_METHOD(scheduler::ScheduleResult, schedule, (CallbackToken, ScheduleTiming), (override));
- MOCK_METHOD(scheduler::CancelResult, cancel, (CallbackToken), (override));
- MOCK_METHOD(void, dump, (std::string&), (const, override));
-
- MockVSyncDispatch() {
- ON_CALL(*this, registerCallback)
- .WillByDefault(
- [this](std::function<void(nsecs_t, nsecs_t, nsecs_t)> const& callback,
- std::string) {
- CallbackToken token(mNextToken);
- mNextToken++;
-
- mCallbacks.emplace(token, CallbackData(callback));
- ALOGD("registerCallback: %zu", token.value());
- return token;
- });
-
- ON_CALL(*this, unregisterCallback).WillByDefault([this](CallbackToken token) {
- ALOGD("unregisterCallback: %zu", token.value());
- mCallbacks.erase(token);
- });
-
- ON_CALL(*this, schedule).WillByDefault([this](CallbackToken token, ScheduleTiming timing) {
- ALOGD("schedule: %zu", token.value());
- if (mCallbacks.count(token) == 0) {
- ALOGD("schedule: callback %zu not registered", token.value());
- return scheduler::ScheduleResult{};
- }
-
- auto& callback = mCallbacks.at(token);
- callback.scheduled = true;
- callback.vsyncTime = timing.earliestVsync;
- callback.targetWakeupTime =
- timing.earliestVsync - timing.workDuration - timing.readyDuration;
- ALOGD("schedule: callback %zu scheduled", token.value());
- return scheduler::ScheduleResult{callback.targetWakeupTime};
- });
-
- ON_CALL(*this, cancel).WillByDefault([this](CallbackToken token) {
- ALOGD("cancel: %zu", token.value());
- if (mCallbacks.count(token) == 0) {
- ALOGD("cancel: callback %zu is not registered", token.value());
- return scheduler::CancelResult::Error;
- }
-
- auto& callback = mCallbacks.at(token);
- callback.scheduled = false;
- ALOGD("cancel: callback %zu cancelled", token.value());
- return scheduler::CancelResult::Cancelled;
- });
- }
-
- void triggerCallbacks() {
- ALOGD("triggerCallbacks");
- for (auto& [token, callback] : mCallbacks) {
- if (callback.scheduled) {
- ALOGD("triggerCallbacks: callback %zu", token.value());
- callback.scheduled = false;
- callback.func(callback.vsyncTime, callback.targetWakeupTime, callback.readyTime);
- } else {
- ALOGD("triggerCallbacks: callback %zu is not scheduled", token.value());
- }
- }
- }
-
-private:
- struct CallbackData {
- explicit CallbackData(std::function<void(nsecs_t, nsecs_t, nsecs_t)> func)
- : func(std::move(func)) {}
-
- std::function<void(nsecs_t, nsecs_t, nsecs_t)> func;
- bool scheduled = false;
- nsecs_t vsyncTime = 0;
- nsecs_t targetWakeupTime = 0;
- nsecs_t readyTime = 0;
- };
-
- std::unordered_map<CallbackToken, CallbackData> mCallbacks;
- size_t mNextToken;
-};
-
-class DispSyncSourceTest : public testing::Test, private VSyncSource::Callback {
-protected:
- DispSyncSourceTest();
- ~DispSyncSourceTest() override;
-
- void SetUp() override;
- void createDispSyncSource();
-
- void onVSyncEvent(nsecs_t when, VSyncSource::VSyncData) override;
-
- std::unique_ptr<MockVSyncDispatch> mVSyncDispatch;
- std::unique_ptr<mock::VSyncTracker> mVSyncTracker;
- std::unique_ptr<scheduler::DispSyncSource> mDispSyncSource;
-
- AsyncCallRecorder<void (*)(nsecs_t, VSyncSource::VSyncData)> mVSyncEventCallRecorder;
-
- static constexpr std::chrono::nanoseconds mWorkDuration = 20ms;
- static constexpr std::chrono::nanoseconds mReadyDuration = 10ms;
- static constexpr int mIterations = 100;
- const scheduler::VSyncDispatch::CallbackToken mFakeToken{2398};
- const std::string mName = "DispSyncSourceTest";
-};
-
-DispSyncSourceTest::DispSyncSourceTest() {
- const ::testing::TestInfo* const test_info =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
-}
-
-DispSyncSourceTest::~DispSyncSourceTest() {
- const ::testing::TestInfo* const test_info =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
-}
-
-void DispSyncSourceTest::SetUp() {
- mVSyncDispatch = std::make_unique<MockVSyncDispatch>();
- mVSyncTracker = std::make_unique<mock::VSyncTracker>();
-}
-
-void DispSyncSourceTest::onVSyncEvent(nsecs_t when, VSyncSource::VSyncData vsyncData) {
- ALOGD("onVSyncEvent: %" PRId64, when);
-
- mVSyncEventCallRecorder.recordCall(when, vsyncData);
-}
-
-void DispSyncSourceTest::createDispSyncSource() {
- mDispSyncSource = std::make_unique<scheduler::DispSyncSource>(*mVSyncDispatch, *mVSyncTracker,
- mWorkDuration, mReadyDuration,
- true, mName.c_str());
- mDispSyncSource->setCallback(this);
-}
-
-/* ------------------------------------------------------------------------
- * Test cases
- */
-
-TEST_F(DispSyncSourceTest, createDispSync) {
- EXPECT_TRUE(mVSyncDispatch);
-}
-
-TEST_F(DispSyncSourceTest, createDispSyncSource) {
- InSequence seq;
- EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).WillOnce(Return(mFakeToken));
- EXPECT_CALL(*mVSyncDispatch, cancel(mFakeToken))
- .WillOnce(Return(scheduler::CancelResult::Cancelled));
- EXPECT_CALL(*mVSyncDispatch, unregisterCallback(mFakeToken)).WillOnce(Return());
- createDispSyncSource();
-
- EXPECT_TRUE(mDispSyncSource);
-}
-
-TEST_F(DispSyncSourceTest, noCallbackAfterInit) {
- InSequence seq;
- EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).Times(1);
- EXPECT_CALL(*mVSyncDispatch, cancel(_)).Times(1);
- EXPECT_CALL(*mVSyncDispatch, unregisterCallback(_)).Times(1);
- createDispSyncSource();
-
- EXPECT_TRUE(mDispSyncSource);
-
- // DispSyncSource starts with Vsync disabled
- mVSyncDispatch->triggerCallbacks();
- EXPECT_FALSE(mVSyncEventCallRecorder.waitForUnexpectedCall().has_value());
-}
-
-TEST_F(DispSyncSourceTest, waitForCallbacks) {
- InSequence seq;
- EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).Times(1);
- EXPECT_CALL(*mVSyncDispatch,
- schedule(_, Truly([&](auto timings) {
- return timings.workDuration == mWorkDuration.count() &&
- timings.readyDuration == mReadyDuration.count();
- })))
- .Times(mIterations + 1);
- EXPECT_CALL(*mVSyncDispatch, cancel(_)).Times(1);
- EXPECT_CALL(*mVSyncDispatch, unregisterCallback(_)).Times(1);
- createDispSyncSource();
-
- EXPECT_TRUE(mDispSyncSource);
-
- mDispSyncSource->setVSyncEnabled(true);
- for (int i = 0; i < mIterations; i++) {
- mVSyncDispatch->triggerCallbacks();
- const auto callbackData = mVSyncEventCallRecorder.waitForCall();
- ASSERT_TRUE(callbackData.has_value());
- const auto [when, vsyncData] = callbackData.value();
- EXPECT_EQ(when,
- vsyncData.expectedPresentationTime - mWorkDuration.count() -
- mReadyDuration.count());
- }
-}
-
-TEST_F(DispSyncSourceTest, waitForCallbacksWithDurationChange) {
- InSequence seq;
- EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).Times(1);
- EXPECT_CALL(*mVSyncDispatch,
- schedule(_, Truly([&](auto timings) {
- return timings.workDuration == mWorkDuration.count() &&
- timings.readyDuration == mReadyDuration.count();
- })))
- .Times(1);
-
- createDispSyncSource();
-
- EXPECT_TRUE(mDispSyncSource);
-
- mDispSyncSource->setVSyncEnabled(true);
- EXPECT_CALL(*mVSyncDispatch,
- schedule(_, Truly([&](auto timings) {
- return timings.workDuration == mWorkDuration.count() &&
- timings.readyDuration == mReadyDuration.count();
- })))
- .Times(mIterations);
- for (int i = 0; i < mIterations; i++) {
- mVSyncDispatch->triggerCallbacks();
- const auto callbackData = mVSyncEventCallRecorder.waitForCall();
- ASSERT_TRUE(callbackData.has_value());
- const auto [when, vsyncData] = callbackData.value();
- EXPECT_EQ(when,
- vsyncData.expectedPresentationTime - mWorkDuration.count() -
- mReadyDuration.count());
- }
-
- const auto newDuration = mWorkDuration / 2;
- EXPECT_CALL(*mVSyncDispatch, schedule(_, Truly([&](auto timings) {
- return timings.workDuration == newDuration.count() &&
- timings.readyDuration == 0;
- })))
- .Times(1);
- mDispSyncSource->setDuration(newDuration, 0ns);
-
- EXPECT_CALL(*mVSyncDispatch, schedule(_, Truly([&](auto timings) {
- return timings.workDuration == newDuration.count() &&
- timings.readyDuration == 0;
- })))
- .Times(mIterations);
- for (int i = 0; i < mIterations; i++) {
- mVSyncDispatch->triggerCallbacks();
- const auto callbackData = mVSyncEventCallRecorder.waitForCall();
- ASSERT_TRUE(callbackData.has_value());
- const auto [when, vsyncData] = callbackData.value();
- EXPECT_EQ(when, vsyncData.expectedPresentationTime - newDuration.count());
- }
-
- EXPECT_CALL(*mVSyncDispatch, cancel(_)).Times(1);
- EXPECT_CALL(*mVSyncDispatch, unregisterCallback(_)).Times(1);
-}
-
-TEST_F(DispSyncSourceTest, getLatestVsyncData) {
- const nsecs_t now = systemTime();
- const nsecs_t expectedPresentationTime =
- now + mWorkDuration.count() + mReadyDuration.count() + 1;
- EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_))
- .WillOnce(Return(expectedPresentationTime));
- {
- InSequence seq;
- EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).Times(1);
- EXPECT_CALL(*mVSyncDispatch, cancel(_)).Times(1);
- EXPECT_CALL(*mVSyncDispatch, unregisterCallback(_)).Times(1);
- }
-
- createDispSyncSource();
- EXPECT_TRUE(mDispSyncSource);
-
- const auto vsyncData = mDispSyncSource->getLatestVSyncData();
- ASSERT_EQ(vsyncData.expectedPresentationTime, expectedPresentationTime);
- EXPECT_EQ(vsyncData.deadlineTimestamp, expectedPresentationTime - mReadyDuration.count());
-}
-
-} // namespace
-} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp b/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp
index 225ad16..ac5e927 100644
--- a/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp
@@ -96,5 +96,23 @@
EXPECT_EQ(std::nullopt, displayDevice->getCompositionDisplay()->getState().displayBrightness);
}
+TEST_F(SetDisplayBrightnessTest, firstDisplayBrightnessWithComposite) {
+ ftl::FakeGuard guard(kMainThreadContext);
+ sp<DisplayDevice> displayDevice = getDisplayDevice();
+
+ EXPECT_EQ(std::nullopt, displayDevice->getStagedBrightness());
+
+ constexpr float kDisplayBrightness = -1.0f;
+ displayDevice->stageBrightness(kDisplayBrightness);
+
+ EXPECT_EQ(-1.0f, displayDevice->getStagedBrightness());
+
+ displayDevice->persistBrightness(true);
+
+ EXPECT_EQ(std::nullopt, displayDevice->getStagedBrightness());
+ EXPECT_EQ(kDisplayBrightness,
+ displayDevice->getCompositionDisplay()->getState().displayBrightness);
+}
+
} // namespace
} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index dd87f9d..b3aba37 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -30,12 +30,16 @@
#include "DisplayHardware/DisplayMode.h"
#include "FrameTimeline.h"
#include "Scheduler/EventThread.h"
+#include "mock/MockVSyncDispatch.h"
+#include "mock/MockVSyncTracker.h"
+#include "mock/MockVsyncController.h"
using namespace std::chrono_literals;
using namespace std::placeholders;
using testing::_;
using testing::Invoke;
+using testing::Return;
namespace android {
@@ -50,24 +54,13 @@
constexpr std::chrono::duration VSYNC_PERIOD(16ms);
-class MockVSyncSource : public VSyncSource {
-public:
- const char* getName() const override { return "test"; }
-
- MOCK_METHOD1(setVSyncEnabled, void(bool));
- MOCK_METHOD1(setCallback, void(VSyncSource::Callback*));
- MOCK_METHOD2(setDuration,
- void(std::chrono::nanoseconds workDuration,
- std::chrono::nanoseconds readyDuration));
- MOCK_METHOD1(pauseVsyncCallback, void(bool));
- MOCK_METHOD(VSyncSource::VSyncData, getLatestVSyncData, (), (const, override));
- MOCK_CONST_METHOD1(dump, void(std::string&));
-};
-
} // namespace
class EventThreadTest : public testing::Test {
protected:
+ static constexpr std::chrono::nanoseconds kWorkDuration = 0ms;
+ static constexpr std::chrono::nanoseconds kReadyDuration = 3ms;
+
class MockEventThreadConnection : public EventThreadConnection {
public:
MockEventThreadConnection(impl::EventThread* eventThread, uid_t callingUid,
@@ -84,21 +77,21 @@
EventThreadTest();
~EventThreadTest() override;
- void createThread(std::unique_ptr<VSyncSource>);
+ void createThread();
sp<MockEventThreadConnection> createConnection(ConnectionEventRecorder& recorder,
EventRegistrationFlags eventRegistration = {},
uid_t ownerUid = mConnectionUid);
- void expectVSyncSetEnabledCallReceived(bool expectedState);
+ void expectVSyncCallbackScheduleReceived(bool expectState);
void expectVSyncSetDurationCallReceived(std::chrono::nanoseconds expectedDuration,
std::chrono::nanoseconds expectedReadyDuration);
- VSyncSource::Callback* expectVSyncSetCallbackCallReceived();
void expectVsyncEventReceivedByConnection(const char* name,
ConnectionEventRecorder& connectionEventRecorder,
nsecs_t expectedTimestamp, unsigned expectedCount);
void expectVsyncEventReceivedByConnection(nsecs_t expectedTimestamp, unsigned expectedCount);
- void expectVsyncEventFrameTimelinesCorrect(nsecs_t expectedTimestamp,
- VSyncSource::VSyncData preferredVsyncData);
+ void expectVsyncEventFrameTimelinesCorrect(
+ nsecs_t expectedTimestamp,
+ /*VSyncSource::VSyncData*/ gui::VsyncEventData::FrameTimeline preferredVsyncData);
void expectHotplugEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
bool expectedConnected);
void expectConfigChangedEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
@@ -108,17 +101,31 @@
void expectUidFrameRateMappingEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
std::vector<FrameRateOverride>);
- AsyncCallRecorder<void (*)(bool)> mVSyncSetEnabledCallRecorder;
- AsyncCallRecorder<void (*)(VSyncSource::Callback*)> mVSyncSetCallbackCallRecorder;
- AsyncCallRecorder<void (*)(std::chrono::nanoseconds, std::chrono::nanoseconds)>
- mVSyncSetDurationCallRecorder;
+ void onVSyncEvent(nsecs_t timestamp, nsecs_t expectedPresentationTime,
+ nsecs_t deadlineTimestamp) {
+ mThread->onVsync(expectedPresentationTime, timestamp, deadlineTimestamp);
+ }
+
+ AsyncCallRecorderWithCannedReturn<
+ scheduler::ScheduleResult (*)(scheduler::VSyncDispatch::CallbackToken,
+ scheduler::VSyncDispatch::ScheduleTiming)>
+ mVSyncCallbackScheduleRecorder{0};
+ AsyncCallRecorderWithCannedReturn<
+ scheduler::ScheduleResult (*)(scheduler::VSyncDispatch::CallbackToken,
+ scheduler::VSyncDispatch::ScheduleTiming)>
+ mVSyncCallbackUpdateRecorder{0};
+ AsyncCallRecorderWithCannedReturn<
+ scheduler::VSyncDispatch::CallbackToken (*)(scheduler::VSyncDispatch::Callback,
+ std::string)>
+ mVSyncCallbackRegisterRecorder{scheduler::VSyncDispatch::CallbackToken(0)};
+ AsyncCallRecorder<void (*)(scheduler::VSyncDispatch::CallbackToken)>
+ mVSyncCallbackUnregisterRecorder;
AsyncCallRecorder<void (*)()> mResyncCallRecorder;
AsyncCallRecorder<void (*)(nsecs_t, uid_t)> mThrottleVsyncCallRecorder;
ConnectionEventRecorder mConnectionEventCallRecorder{0};
ConnectionEventRecorder mThrottledConnectionEventCallRecorder{0};
- MockVSyncSource* mVSyncSource;
- VSyncSource::Callback* mCallback = nullptr;
+ std::optional<scheduler::VsyncSchedule> mVsyncSchedule;
std::unique_ptr<impl::EventThread> mThread;
sp<MockEventThreadConnection> mConnection;
sp<MockEventThreadConnection> mThrottledConnection;
@@ -133,19 +140,22 @@
::testing::UnitTest::GetInstance()->current_test_info();
ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
- auto vsyncSource = std::make_unique<MockVSyncSource>();
- mVSyncSource = vsyncSource.get();
+ mVsyncSchedule.emplace(scheduler::VsyncSchedule(std::make_unique<mock::VSyncTracker>(),
+ std::make_unique<mock::VSyncDispatch>(),
+ nullptr));
- EXPECT_CALL(*mVSyncSource, setVSyncEnabled(_))
- .WillRepeatedly(Invoke(mVSyncSetEnabledCallRecorder.getInvocable()));
+ mock::VSyncDispatch& mockDispatch =
+ *static_cast<mock::VSyncDispatch*>(&mVsyncSchedule->getDispatch());
+ EXPECT_CALL(mockDispatch, registerCallback(_, _))
+ .WillRepeatedly(Invoke(mVSyncCallbackRegisterRecorder.getInvocable()));
+ EXPECT_CALL(mockDispatch, schedule(_, _))
+ .WillRepeatedly(Invoke(mVSyncCallbackScheduleRecorder.getInvocable()));
+ EXPECT_CALL(mockDispatch, update(_, _))
+ .WillRepeatedly(Invoke(mVSyncCallbackUpdateRecorder.getInvocable()));
+ EXPECT_CALL(mockDispatch, unregisterCallback(_))
+ .WillRepeatedly(Invoke(mVSyncCallbackUnregisterRecorder.getInvocable()));
- EXPECT_CALL(*mVSyncSource, setCallback(_))
- .WillRepeatedly(Invoke(mVSyncSetCallbackCallRecorder.getInvocable()));
-
- EXPECT_CALL(*mVSyncSource, setDuration(_, _))
- .WillRepeatedly(Invoke(mVSyncSetDurationCallRecorder.getInvocable()));
-
- createThread(std::move(vsyncSource));
+ createThread();
mConnection =
createConnection(mConnectionEventCallRecorder,
gui::ISurfaceComposer::EventRegistration::modeChanged |
@@ -164,11 +174,12 @@
::testing::UnitTest::GetInstance()->current_test_info();
ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+ mThread.reset();
// EventThread should unregister itself as VSyncSource callback.
- EXPECT_TRUE(!mVSyncSetCallbackCallRecorder.waitForUnexpectedCall().has_value());
+ EXPECT_TRUE(mVSyncCallbackUnregisterRecorder.waitForCall().has_value());
}
-void EventThreadTest::createThread(std::unique_ptr<VSyncSource> source) {
+void EventThreadTest::createThread() {
const auto throttleVsync = [&](nsecs_t expectedVsyncTimestamp, uid_t uid) {
mThrottleVsyncCallRecorder.getInvocable()(expectedVsyncTimestamp, uid);
return (uid == mThrottledConnectionUid);
@@ -178,12 +189,13 @@
};
mTokenManager = std::make_unique<frametimeline::impl::TokenManager>();
- mThread = std::make_unique<impl::EventThread>(std::move(source), mTokenManager.get(),
- throttleVsync, getVsyncPeriod);
+ mThread =
+ std::make_unique<impl::EventThread>(/*std::move(source), */ "EventThreadTest",
+ *mVsyncSchedule, mTokenManager.get(), throttleVsync,
+ getVsyncPeriod, kWorkDuration, kReadyDuration);
// EventThread should register itself as VSyncSource callback.
- mCallback = expectVSyncSetCallbackCallReceived();
- ASSERT_TRUE(mCallback);
+ EXPECT_TRUE(mVSyncCallbackRegisterRecorder.waitForCall().has_value());
}
sp<EventThreadTest::MockEventThreadConnection> EventThreadTest::createConnection(
@@ -197,23 +209,20 @@
return connection;
}
-void EventThreadTest::expectVSyncSetEnabledCallReceived(bool expectedState) {
- auto args = mVSyncSetEnabledCallRecorder.waitForCall();
- ASSERT_TRUE(args.has_value());
- EXPECT_EQ(expectedState, std::get<0>(args.value()));
+void EventThreadTest::expectVSyncCallbackScheduleReceived(bool expectState) {
+ if (expectState) {
+ ASSERT_TRUE(mVSyncCallbackScheduleRecorder.waitForCall().has_value());
+ } else {
+ ASSERT_FALSE(mVSyncCallbackScheduleRecorder.waitForUnexpectedCall().has_value());
+ }
}
void EventThreadTest::expectVSyncSetDurationCallReceived(
std::chrono::nanoseconds expectedDuration, std::chrono::nanoseconds expectedReadyDuration) {
- auto args = mVSyncSetDurationCallRecorder.waitForCall();
+ auto args = mVSyncCallbackUpdateRecorder.waitForCall();
ASSERT_TRUE(args.has_value());
- EXPECT_EQ(expectedDuration, std::get<0>(args.value()));
- EXPECT_EQ(expectedReadyDuration, std::get<1>(args.value()));
-}
-
-VSyncSource::Callback* EventThreadTest::expectVSyncSetCallbackCallReceived() {
- auto callbackSet = mVSyncSetCallbackCallRecorder.waitForCall();
- return callbackSet.has_value() ? std::get<0>(callbackSet.value()) : nullptr;
+ EXPECT_EQ(expectedDuration.count(), std::get<1>(args.value()).workDuration);
+ EXPECT_EQ(expectedReadyDuration.count(), std::get<1>(args.value()).readyDuration);
}
void EventThreadTest::expectThrottleVsyncReceived(nsecs_t expectedTimestamp, uid_t uid) {
@@ -246,7 +255,7 @@
}
void EventThreadTest::expectVsyncEventFrameTimelinesCorrect(
- nsecs_t expectedTimestamp, VSyncSource::VSyncData preferredVsyncData) {
+ nsecs_t expectedTimestamp, VsyncEventData::FrameTimeline preferredVsyncData) {
auto args = mConnectionEventCallRecorder.waitForCall();
ASSERT_TRUE(args.has_value()) << " did not receive an event for timestamp "
<< expectedTimestamp;
@@ -335,9 +344,10 @@
*/
TEST_F(EventThreadTest, canCreateAndDestroyThreadWithNoEventsSent) {
- EXPECT_FALSE(mVSyncSetEnabledCallRecorder.waitForUnexpectedCall().has_value());
- EXPECT_FALSE(mVSyncSetCallbackCallRecorder.waitForCall(0us).has_value());
- EXPECT_FALSE(mVSyncSetDurationCallRecorder.waitForCall(0us).has_value());
+ EXPECT_FALSE(mVSyncCallbackRegisterRecorder.waitForCall(0us).has_value());
+ EXPECT_FALSE(mVSyncCallbackScheduleRecorder.waitForCall(0us).has_value());
+ EXPECT_FALSE(mVSyncCallbackUpdateRecorder.waitForCall(0us).has_value());
+ EXPECT_FALSE(mVSyncCallbackUnregisterRecorder.waitForCall(0us).has_value());
EXPECT_FALSE(mResyncCallRecorder.waitForCall(0us).has_value());
EXPECT_FALSE(mConnectionEventCallRecorder.waitForCall(0us).has_value());
}
@@ -350,7 +360,7 @@
mThread->requestNextVsync(mConnection);
// EventThread should not enable vsync callbacks.
- EXPECT_FALSE(mVSyncSetEnabledCallRecorder.waitForUnexpectedCall().has_value());
+ expectVSyncCallbackScheduleReceived(false);
}
TEST_F(EventThreadTest, requestNextVsyncPostsASingleVSyncEventToTheConnection) {
@@ -360,47 +370,51 @@
// EventThread should immediately request a resync.
EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value());
- // EventThread should enable vsync callbacks.
- expectVSyncSetEnabledCallReceived(true);
+ // EventThread should enable schedule a vsync callback
+ expectVSyncCallbackScheduleReceived(true);
// Use the received callback to signal a first vsync event.
// The throttler should receive the event, as well as the connection.
- mCallback->onVSyncEvent(123, {456, 789});
+ onVSyncEvent(123, 456, 789);
expectThrottleVsyncReceived(456, mConnectionUid);
expectVsyncEventReceivedByConnection(123, 1u);
+ // EventThread is requesting one more callback due to VsyncRequest::SingleSuppressCallback
+ expectVSyncCallbackScheduleReceived(true);
+
// Use the received callback to signal a second vsync event.
// The throttler should receive the event, but the connection should
// not as it was only interested in the first.
- mCallback->onVSyncEvent(456, {123, 0});
+ onVSyncEvent(456, 123, 0);
EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
// EventThread should also detect that at this point that it does not need
// any more vsync events, and should disable their generation.
- expectVSyncSetEnabledCallReceived(false);
+ expectVSyncCallbackScheduleReceived(false);
}
TEST_F(EventThreadTest, requestNextVsyncEventFrameTimelinesCorrect) {
// Signal that we want the next vsync event to be posted to the connection
mThread->requestNextVsync(mConnection);
- expectVSyncSetEnabledCallReceived(true);
+ expectVSyncCallbackScheduleReceived(true);
// Use the received callback to signal a vsync event.
// The throttler should receive the event, as well as the connection.
- VSyncSource::VSyncData vsyncData = {456, 789};
- mCallback->onVSyncEvent(123, vsyncData);
- expectVsyncEventFrameTimelinesCorrect(123, vsyncData);
+ onVSyncEvent(123, 456, 789);
+ expectVsyncEventFrameTimelinesCorrect(123, {-1, 789, 456});
}
TEST_F(EventThreadTest, getLatestVsyncEventData) {
const nsecs_t now = systemTime();
- const nsecs_t preferredDeadline = now + 10000000;
const nsecs_t preferredExpectedPresentationTime = now + 20000000;
- const VSyncSource::VSyncData preferredData = {preferredExpectedPresentationTime,
- preferredDeadline};
- EXPECT_CALL(*mVSyncSource, getLatestVSyncData()).WillOnce(Return(preferredData));
+ const nsecs_t preferredDeadline = preferredExpectedPresentationTime - kReadyDuration.count();
+
+ mock::VSyncTracker& mockTracker =
+ *static_cast<mock::VSyncTracker*>(&mVsyncSchedule->getTracker());
+ EXPECT_CALL(mockTracker, nextAnticipatedVSyncTimeFrom(_))
+ .WillOnce(Return(preferredExpectedPresentationTime));
VsyncEventData vsyncEventData = mThread->getLatestVsyncEventData(mConnection);
@@ -451,8 +465,7 @@
mThread->setVsyncRate(0, firstConnection);
// By itself, this should not enable vsync events
- EXPECT_FALSE(mVSyncSetEnabledCallRecorder.waitForUnexpectedCall().has_value());
- EXPECT_FALSE(mVSyncSetCallbackCallRecorder.waitForCall(0us).has_value());
+ expectVSyncCallbackScheduleReceived(false);
// However if there is another connection which wants events at a nonzero rate.....
ConnectionEventRecorder secondConnectionEventRecorder{0};
@@ -461,12 +474,12 @@
mThread->setVsyncRate(1, secondConnection);
// EventThread should enable vsync callbacks.
- expectVSyncSetEnabledCallReceived(true);
+ expectVSyncCallbackScheduleReceived(true);
// Send a vsync event. EventThread should then make a call to the
// the second connection. The first connection should not
// get the event.
- mCallback->onVSyncEvent(123, {456, 0});
+ onVSyncEvent(123, 0456, 0);
EXPECT_FALSE(firstConnectionEventRecorder.waitForUnexpectedCall().has_value());
expectVsyncEventReceivedByConnection("secondConnection", secondConnectionEventRecorder, 123,
1u);
@@ -476,21 +489,21 @@
mThread->setVsyncRate(1, mConnection);
// EventThread should enable vsync callbacks.
- expectVSyncSetEnabledCallReceived(true);
+ expectVSyncCallbackScheduleReceived(true);
// Send a vsync event. EventThread should then make a call to the
// throttler, and the connection.
- mCallback->onVSyncEvent(123, {456, 789});
+ onVSyncEvent(123, 456, 789);
expectThrottleVsyncReceived(456, mConnectionUid);
expectVsyncEventReceivedByConnection(123, 1u);
// A second event should go to the same places.
- mCallback->onVSyncEvent(456, {123, 0});
+ onVSyncEvent(456, 123, 0);
expectThrottleVsyncReceived(123, mConnectionUid);
expectVsyncEventReceivedByConnection(456, 2u);
// A third event should go to the same places.
- mCallback->onVSyncEvent(789, {777, 111});
+ onVSyncEvent(789, 777, 111);
expectThrottleVsyncReceived(777, mConnectionUid);
expectVsyncEventReceivedByConnection(789, 3u);
}
@@ -499,25 +512,25 @@
mThread->setVsyncRate(2, mConnection);
// EventThread should enable vsync callbacks.
- expectVSyncSetEnabledCallReceived(true);
+ expectVSyncCallbackScheduleReceived(true);
// The first event will not be seen by the connection.
- mCallback->onVSyncEvent(123, {456, 789});
+ onVSyncEvent(123, 456, 789);
EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
// The second event will be seen by the connection.
- mCallback->onVSyncEvent(456, {123, 0});
+ onVSyncEvent(456, 123, 0);
expectVsyncEventReceivedByConnection(456, 2u);
EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
// The third event will not be seen by the connection.
- mCallback->onVSyncEvent(789, {777, 744});
+ onVSyncEvent(789, 777, 744);
EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
// The fourth event will be seen by the connection.
- mCallback->onVSyncEvent(101112, {7847, 86});
+ onVSyncEvent(101112, 7847, 86);
expectVsyncEventReceivedByConnection(101112, 4u);
}
@@ -525,17 +538,17 @@
mThread->setVsyncRate(1, mConnection);
// EventThread should enable vsync callbacks.
- expectVSyncSetEnabledCallReceived(true);
+ expectVSyncCallbackScheduleReceived(true);
// Destroy the only (strong) reference to the connection.
mConnection = nullptr;
// The first event will not be seen by the connection.
- mCallback->onVSyncEvent(123, {456, 789});
+ onVSyncEvent(123, 56, 789);
EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
// EventThread should disable vsync callbacks
- expectVSyncSetEnabledCallReceived(false);
+ expectVSyncCallbackScheduleReceived(false);
}
TEST_F(EventThreadTest, connectionsRemovedIfEventDeliveryError) {
@@ -544,18 +557,22 @@
mThread->setVsyncRate(1, errorConnection);
// EventThread should enable vsync callbacks.
- expectVSyncSetEnabledCallReceived(true);
+ expectVSyncCallbackScheduleReceived(true);
// The first event will be seen by the connection, which then returns an error.
- mCallback->onVSyncEvent(123, {456, 789});
+ onVSyncEvent(123, 456, 789);
expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 123, 1u);
+ // Another schedule is expected, since the connection is removed only after
+ // the next vsync is requested.
+ expectVSyncCallbackScheduleReceived(true);
+
// A subsequent event will not be seen by the connection.
- mCallback->onVSyncEvent(456, {123, 0});
+ onVSyncEvent(456, 123, 0);
EXPECT_FALSE(errorConnectionEventRecorder.waitForUnexpectedCall().has_value());
// EventThread should disable vsync callbacks with the second event
- expectVSyncSetEnabledCallReceived(false);
+ expectVSyncCallbackScheduleReceived(false);
}
TEST_F(EventThreadTest, tracksEventConnections) {
@@ -571,10 +588,10 @@
EXPECT_EQ(4, mThread->getEventThreadConnectionCount());
// EventThread should enable vsync callbacks.
- expectVSyncSetEnabledCallReceived(true);
+ expectVSyncCallbackScheduleReceived(true);
// The first event will be seen by the connection, which then returns an error.
- mCallback->onVSyncEvent(123, {456, 789});
+ onVSyncEvent(123, 456, 789);
expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 123, 1u);
expectVsyncEventReceivedByConnection("successConnection", secondConnectionEventRecorder, 123,
1u);
@@ -587,19 +604,22 @@
mThread->setVsyncRate(1, errorConnection);
// EventThread should enable vsync callbacks.
- expectVSyncSetEnabledCallReceived(true);
+ expectVSyncCallbackScheduleReceived(true);
// The first event will be seen by the connection, which then returns a non-fatal error.
- mCallback->onVSyncEvent(123, {456, 789});
+ onVSyncEvent(123, 456, 789);
expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 123, 1u);
+ expectVSyncCallbackScheduleReceived(true);
// A subsequent event will be seen by the connection, which still then returns a non-fatal
// error.
- mCallback->onVSyncEvent(456, {123, 0});
+ onVSyncEvent(456, 123, 0);
expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 456, 2u);
+ expectVSyncCallbackScheduleReceived(true);
// EventThread will not disable vsync callbacks as the errors are non-fatal.
- EXPECT_FALSE(mVSyncSetEnabledCallRecorder.waitForUnexpectedCall().has_value());
+ onVSyncEvent(456, 123, 0);
+ expectVSyncCallbackScheduleReceived(true);
}
TEST_F(EventThreadTest, setPhaseOffsetForwardsToVSyncSource) {
@@ -718,24 +738,27 @@
EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value());
// EventThread should enable vsync callbacks.
- expectVSyncSetEnabledCallReceived(true);
+ expectVSyncCallbackScheduleReceived(true);
// Use the received callback to signal a first vsync event.
// The throttler should receive the event, but not the connection.
- mCallback->onVSyncEvent(123, {456, 789});
+ onVSyncEvent(123, 456, 789);
expectThrottleVsyncReceived(456, mThrottledConnectionUid);
mThrottledConnectionEventCallRecorder.waitForUnexpectedCall();
+ expectVSyncCallbackScheduleReceived(true);
// Use the received callback to signal a second vsync event.
// The throttler should receive the event, but the connection should
// not as it was only interested in the first.
- mCallback->onVSyncEvent(456, {123, 0});
+ onVSyncEvent(456, 123, 0);
expectThrottleVsyncReceived(123, mThrottledConnectionUid);
EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
+ expectVSyncCallbackScheduleReceived(true);
// EventThread should not change the vsync state as it didn't send the event
// yet
- EXPECT_FALSE(mVSyncSetEnabledCallRecorder.waitForUnexpectedCall().has_value());
+ onVSyncEvent(456, 123, 0);
+ expectVSyncCallbackScheduleReceived(true);
}
} // namespace
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index 8f89a8c..afbc57a 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -147,6 +147,7 @@
}),
Return(hardware::graphics::composer::V2_4::Error::NONE)));
EXPECT_CALL(*mHal, getOverlaySupport(_)).WillOnce(Return(HalError::NONE));
+ EXPECT_CALL(*mHal, getHdrConversionCapabilities(_)).WillOnce(Return(HalError::NONE));
EXPECT_CALL(*mHal, registerCallback(_));
@@ -165,6 +166,7 @@
EXPECT_CALL(*mHal, getLayerGenericMetadataKeys(_))
.WillOnce(Return(hardware::graphics::composer::V2_4::Error::UNSUPPORTED));
EXPECT_CALL(*mHal, getOverlaySupport(_)).WillOnce(Return(HalError::UNSUPPORTED));
+ EXPECT_CALL(*mHal, getHdrConversionCapabilities(_)).WillOnce(Return(HalError::UNSUPPORTED));
EXPECT_CALL(*mHal, registerCallback(_));
mHwc.setCallback(mCallback);
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
index 8560902..33c9440 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
@@ -21,6 +21,7 @@
#include "FrontEnd/LayerHierarchy.h"
#include "FrontEnd/LayerLifecycleManager.h"
#include "Layer.h"
+#include "LayerHierarchyTest.h"
#include "gui/SurfaceComposerClient.h"
#define UPDATE_AND_VERIFY(HIERARCHY) \
@@ -31,16 +32,6 @@
namespace android::surfaceflinger::frontend {
-namespace {
-LayerCreationArgs createArgs(uint32_t id, bool canBeRoot, wp<IBinder> parent, wp<IBinder> mirror) {
- LayerCreationArgs args(nullptr, nullptr, "testlayer", 0, {}, std::make_optional(id));
- args.addToRoot = canBeRoot;
- args.parentHandle = parent;
- args.mirrorLayerHandle = mirror;
- return args;
-}
-} // namespace
-
// To run test:
/**
mp :libsurfaceflinger_unittest && adb sync; adb shell \
@@ -50,163 +41,9 @@
--gtest_brief=1
*/
-class LayerHierarchyTest : public testing::Test {
+class LayerHierarchyTest : public LayerHierarchyTestBase {
protected:
- LayerHierarchyTest() {
- // tree with 3 levels of children
- // ROOT
- // ├── 1
- // │ ├── 11
- // │ │ └── 111
- // │ ├── 12
- // │ │ ├── 121
- // │ │ └── 122
- // │ │ └── 1221
- // │ └── 13
- // └── 2
-
- createRootLayer(1);
- createRootLayer(2);
- createLayer(11, 1);
- createLayer(12, 1);
- createLayer(13, 1);
- createLayer(111, 11);
- createLayer(121, 12);
- createLayer(122, 12);
- createLayer(1221, 122);
- mLifecycleManager.commitChanges();
- }
- std::vector<uint32_t> getTraversalPath(const LayerHierarchy& hierarchy) const {
- std::vector<uint32_t> layerIds;
- hierarchy.traverse([&layerIds = layerIds](const LayerHierarchy& hierarchy,
- const LayerHierarchy::TraversalPath&) -> bool {
- layerIds.emplace_back(hierarchy.getLayer()->id);
- return true;
- });
- return layerIds;
- }
-
- std::vector<uint32_t> getTraversalPathInZOrder(const LayerHierarchy& hierarchy) const {
- std::vector<uint32_t> layerIds;
- hierarchy.traverseInZOrder(
- [&layerIds = layerIds](const LayerHierarchy& hierarchy,
- const LayerHierarchy::TraversalPath&) -> bool {
- layerIds.emplace_back(hierarchy.getLayer()->id);
- return true;
- });
- return layerIds;
- }
-
- void createRootLayer(uint32_t id) {
- sp<LayerHandle> handle = sp<LayerHandle>::make(id);
- mHandles[id] = handle;
- std::vector<std::unique_ptr<RequestedLayerState>> layers;
- layers.emplace_back(std::make_unique<RequestedLayerState>(
- createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/nullptr, /*mirror=*/nullptr)));
- mLifecycleManager.addLayers(std::move(layers));
- }
-
- void createLayer(uint32_t id, uint32_t parentId) {
- sp<LayerHandle> handle = sp<LayerHandle>::make(id);
- mHandles[id] = handle;
- std::vector<std::unique_ptr<RequestedLayerState>> layers;
- layers.emplace_back(std::make_unique<RequestedLayerState>(
- createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/mHandles[parentId],
- /*mirror=*/nullptr)));
- mLifecycleManager.addLayers(std::move(layers));
- }
-
- void reparentLayer(uint32_t id, uint32_t newParentId) {
- std::vector<TransactionState> transactions;
- transactions.emplace_back();
- transactions.back().states.push_back({});
-
- if (newParentId == UNASSIGNED_LAYER_ID) {
- transactions.back().states.front().state.parentSurfaceControlForChild = nullptr;
- } else {
- auto parentHandle = mHandles[newParentId];
- transactions.back().states.front().state.parentSurfaceControlForChild =
- sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(), parentHandle,
- static_cast<int32_t>(newParentId), "Test");
- }
- transactions.back().states.front().state.what = layer_state_t::eReparent;
- transactions.back().states.front().state.surface = mHandles[id];
- mLifecycleManager.applyTransactions(transactions);
- }
-
- void reparentRelativeLayer(uint32_t id, uint32_t relativeParentId) {
- std::vector<TransactionState> transactions;
- transactions.emplace_back();
- transactions.back().states.push_back({});
-
- if (relativeParentId == UNASSIGNED_LAYER_ID) {
- transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
- } else {
- auto parentHandle = mHandles[relativeParentId];
- transactions.back().states.front().state.relativeLayerSurfaceControl =
- sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(), parentHandle,
- static_cast<int32_t>(relativeParentId), "test");
- transactions.back().states.front().state.what = layer_state_t::eRelativeLayerChanged;
- }
- transactions.back().states.front().state.surface = mHandles[id];
- mLifecycleManager.applyTransactions(transactions);
- }
-
- void mirrorLayer(uint32_t id, uint32_t parent, uint32_t layerToMirror) {
- auto parentHandle = (parent == UNASSIGNED_LAYER_ID) ? nullptr : mHandles[parent];
- auto mirrorHandle =
- (layerToMirror == UNASSIGNED_LAYER_ID) ? nullptr : mHandles[layerToMirror];
-
- sp<LayerHandle> handle = sp<LayerHandle>::make(id);
- mHandles[id] = handle;
- std::vector<std::unique_ptr<RequestedLayerState>> layers;
- layers.emplace_back(std::make_unique<RequestedLayerState>(
- createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentHandle,
- /*mirror=*/mHandles[layerToMirror])));
- mLifecycleManager.addLayers(std::move(layers));
- }
-
- void updateBackgroundColor(uint32_t id, half alpha) {
- std::vector<TransactionState> transactions;
- transactions.emplace_back();
- transactions.back().states.push_back({});
- transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
- transactions.back().states.front().state.bgColorAlpha = alpha;
- transactions.back().states.front().state.surface = mHandles[id];
- mLifecycleManager.applyTransactions(transactions);
- }
-
- void destroyLayerHandle(uint32_t id) { mLifecycleManager.onHandlesDestroyed({id}); }
-
- void updateAndVerify(LayerHierarchyBuilder& hierarchyBuilder) {
- if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
- hierarchyBuilder.update(mLifecycleManager.getLayers(),
- mLifecycleManager.getDestroyedLayers());
- }
- mLifecycleManager.commitChanges();
-
- // rebuild layer hierarchy from scratch and verify that it matches the updated state.
- LayerHierarchyBuilder newBuilder(mLifecycleManager.getLayers());
- EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()),
- getTraversalPath(newBuilder.getHierarchy()));
- EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()),
- getTraversalPathInZOrder(newBuilder.getHierarchy()));
- EXPECT_FALSE(
- mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
- }
-
- void setZ(uint32_t id, int32_t z) {
- std::vector<TransactionState> transactions;
- transactions.emplace_back();
- transactions.back().states.push_back({});
-
- transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
- transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
- transactions.back().states.front().state.z = z;
- mLifecycleManager.applyTransactions(transactions);
- }
- LayerLifecycleManager mLifecycleManager;
- std::unordered_map<uint32_t, sp<LayerHandle>> mHandles;
+ LayerHierarchyTest() : LayerHierarchyTestBase() { mLifecycleManager.commitChanges(); }
};
// reparenting tests
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
new file mode 100644
index 0000000..08727f2
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2022 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "FrontEnd/LayerHandle.h"
+#include "FrontEnd/LayerHierarchy.h"
+#include "FrontEnd/LayerLifecycleManager.h"
+#include "Layer.h"
+#include "gui/SurfaceComposerClient.h"
+
+namespace android::surfaceflinger::frontend {
+
+class LayerHierarchyTestBase : public testing::Test {
+protected:
+ LayerHierarchyTestBase() {
+ // tree with 3 levels of children
+ // ROOT
+ // ├── 1
+ // │ ├── 11
+ // │ │ └── 111
+ // │ ├── 12
+ // │ │ ├── 121
+ // │ │ └── 122
+ // │ │ └── 1221
+ // │ └── 13
+ // └── 2
+
+ createRootLayer(1);
+ createRootLayer(2);
+ createLayer(11, 1);
+ createLayer(12, 1);
+ createLayer(13, 1);
+ createLayer(111, 11);
+ createLayer(121, 12);
+ createLayer(122, 12);
+ createLayer(1221, 122);
+ }
+
+ LayerCreationArgs createArgs(uint32_t id, bool canBeRoot, wp<IBinder> parent,
+ wp<IBinder> mirror) {
+ LayerCreationArgs args(nullptr, nullptr, "testlayer", 0, {}, std::make_optional(id));
+ args.addToRoot = canBeRoot;
+ args.parentHandle = parent;
+ args.mirrorLayerHandle = mirror;
+ return args;
+ }
+
+ std::vector<uint32_t> getTraversalPath(const LayerHierarchy& hierarchy) const {
+ std::vector<uint32_t> layerIds;
+ hierarchy.traverse([&layerIds = layerIds](const LayerHierarchy& hierarchy,
+ const LayerHierarchy::TraversalPath&) -> bool {
+ layerIds.emplace_back(hierarchy.getLayer()->id);
+ return true;
+ });
+ return layerIds;
+ }
+
+ std::vector<uint32_t> getTraversalPathInZOrder(const LayerHierarchy& hierarchy) const {
+ std::vector<uint32_t> layerIds;
+ hierarchy.traverseInZOrder(
+ [&layerIds = layerIds](const LayerHierarchy& hierarchy,
+ const LayerHierarchy::TraversalPath&) -> bool {
+ layerIds.emplace_back(hierarchy.getLayer()->id);
+ return true;
+ });
+ return layerIds;
+ }
+
+ virtual void createRootLayer(uint32_t id) {
+ sp<LayerHandle> handle = sp<LayerHandle>::make(id);
+ mHandles[id] = handle;
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(std::make_unique<RequestedLayerState>(
+ createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/nullptr, /*mirror=*/nullptr)));
+ mLifecycleManager.addLayers(std::move(layers));
+ }
+
+ virtual void createLayer(uint32_t id, uint32_t parentId) {
+ sp<LayerHandle> handle = sp<LayerHandle>::make(id);
+ mHandles[id] = handle;
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(std::make_unique<RequestedLayerState>(
+ createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/mHandles[parentId],
+ /*mirror=*/nullptr)));
+ mLifecycleManager.addLayers(std::move(layers));
+ }
+
+ void reparentLayer(uint32_t id, uint32_t newParentId) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ if (newParentId == UNASSIGNED_LAYER_ID) {
+ transactions.back().states.front().state.parentSurfaceControlForChild = nullptr;
+ } else {
+ auto parentHandle = mHandles[newParentId];
+ transactions.back().states.front().state.parentSurfaceControlForChild =
+ sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(), parentHandle,
+ static_cast<int32_t>(newParentId), "Test");
+ }
+ transactions.back().states.front().state.what = layer_state_t::eReparent;
+ transactions.back().states.front().state.surface = mHandles[id];
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void reparentRelativeLayer(uint32_t id, uint32_t relativeParentId) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ if (relativeParentId == UNASSIGNED_LAYER_ID) {
+ transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
+ } else {
+ auto parentHandle = mHandles[relativeParentId];
+ transactions.back().states.front().state.relativeLayerSurfaceControl =
+ sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(), parentHandle,
+ static_cast<int32_t>(relativeParentId), "test");
+ transactions.back().states.front().state.what = layer_state_t::eRelativeLayerChanged;
+ }
+ transactions.back().states.front().state.surface = mHandles[id];
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ virtual void mirrorLayer(uint32_t id, uint32_t parent, uint32_t layerToMirror) {
+ auto parentHandle = (parent == UNASSIGNED_LAYER_ID) ? nullptr : mHandles[parent];
+ auto mirrorHandle =
+ (layerToMirror == UNASSIGNED_LAYER_ID) ? nullptr : mHandles[layerToMirror];
+
+ sp<LayerHandle> handle = sp<LayerHandle>::make(id);
+ mHandles[id] = handle;
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(std::make_unique<RequestedLayerState>(
+ createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentHandle,
+ /*mirror=*/mHandles[layerToMirror])));
+ mLifecycleManager.addLayers(std::move(layers));
+ }
+
+ void updateBackgroundColor(uint32_t id, half alpha) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+ transactions.back().states.front().state.bgColorAlpha = alpha;
+ transactions.back().states.front().state.surface = mHandles[id];
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void destroyLayerHandle(uint32_t id) { mLifecycleManager.onHandlesDestroyed({id}); }
+
+ void updateAndVerify(LayerHierarchyBuilder& hierarchyBuilder) {
+ if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
+ hierarchyBuilder.update(mLifecycleManager.getLayers(),
+ mLifecycleManager.getDestroyedLayers());
+ }
+ mLifecycleManager.commitChanges();
+
+ // rebuild layer hierarchy from scratch and verify that it matches the updated state.
+ LayerHierarchyBuilder newBuilder(mLifecycleManager.getLayers());
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()),
+ getTraversalPath(newBuilder.getHierarchy()));
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()),
+ getTraversalPathInZOrder(newBuilder.getHierarchy()));
+ EXPECT_FALSE(
+ mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ }
+
+ void setZ(uint32_t id, int32_t z) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
+ transactions.back().states.front().state.surface = mHandles[id];
+ transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
+ transactions.back().states.front().state.z = z;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setCrop(uint32_t id, const Rect& crop) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eCropChanged;
+ transactions.back().states.front().state.surface = mHandles[id];
+ transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
+ transactions.back().states.front().state.crop = crop;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setFlags(uint32_t id, uint32_t mask, uint32_t flags) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eFlagsChanged;
+ transactions.back().states.front().state.flags = flags;
+ transactions.back().states.front().state.mask = mask;
+ transactions.back().states.front().state.surface = mHandles[id];
+ transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setAlpha(uint32_t id, float alpha) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eAlphaChanged;
+ transactions.back().states.front().state.surface = mHandles[id];
+ transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
+ transactions.back().states.front().state.color.a = static_cast<half>(alpha);
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void hideLayer(uint32_t id) {
+ setFlags(id, layer_state_t::eLayerHidden, layer_state_t::eLayerHidden);
+ }
+
+ void showLayer(uint32_t id) { setFlags(id, layer_state_t::eLayerHidden, 0); }
+
+ void setColor(uint32_t id, half3 rgb = half3(1._hf, 1._hf, 1._hf)) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.what = layer_state_t::eColorChanged;
+ transactions.back().states.front().state.color.rgb = rgb;
+ transactions.back().states.front().state.surface = mHandles[id];
+ transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ LayerLifecycleManager mLifecycleManager;
+ std::unordered_map<uint32_t, sp<LayerHandle>> mHandles;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
new file mode 100644
index 0000000..2441c06
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2022 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "FrontEnd/LayerHandle.h"
+#include "FrontEnd/LayerHierarchy.h"
+#include "FrontEnd/LayerLifecycleManager.h"
+#include "FrontEnd/LayerSnapshotBuilder.h"
+#include "Layer.h"
+#include "LayerHierarchyTest.h"
+
+#define UPDATE_AND_VERIFY(BUILDER, ...) \
+ ({ \
+ SCOPED_TRACE(""); \
+ updateAndVerify((BUILDER), /*displayChanges=*/false, __VA_ARGS__); \
+ })
+
+#define UPDATE_AND_VERIFY_WITH_DISPLAY_CHANGES(BUILDER, ...) \
+ ({ \
+ SCOPED_TRACE(""); \
+ updateAndVerify((BUILDER), /*displayChanges=*/true, __VA_ARGS__); \
+ })
+
+namespace android::surfaceflinger::frontend {
+
+// To run test:
+/**
+ mp :libsurfaceflinger_unittest && adb sync; adb shell \
+ /data/nativetest/libsurfaceflinger_unittest/libsurfaceflinger_unittest \
+ --gtest_filter="LayerSnapshotTest.*" --gtest_brief=1
+*/
+
+class LayerSnapshotTest : public LayerHierarchyTestBase {
+protected:
+ LayerSnapshotTest() : LayerHierarchyTestBase() {
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ }
+
+ void createRootLayer(uint32_t id) override {
+ LayerHierarchyTestBase::createRootLayer(id);
+ setColor(id);
+ }
+
+ void createLayer(uint32_t id, uint32_t parentId) override {
+ LayerHierarchyTestBase::createLayer(id, parentId);
+ setColor(parentId);
+ }
+
+ void mirrorLayer(uint32_t id, uint32_t parent, uint32_t layerToMirror) override {
+ LayerHierarchyTestBase::mirrorLayer(id, parent, layerToMirror);
+ setColor(id);
+ }
+
+ void updateAndVerify(LayerSnapshotBuilder& actualBuilder, bool hasDisplayChanges,
+ const std::vector<uint32_t> expectedVisibleLayerIdsInZOrder) {
+ if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
+ mHierarchyBuilder.update(mLifecycleManager.getLayers(),
+ mLifecycleManager.getDestroyedLayers());
+ }
+ LayerSnapshotBuilder::Args args{
+ .root = mHierarchyBuilder.getHierarchy(),
+ .layerLifecycleManager = mLifecycleManager,
+ .includeMetadata = false,
+ .displays = mFrontEndDisplayInfos,
+ .displayChanges = hasDisplayChanges,
+ .globalShadowSettings = globalShadowSettings,
+ };
+ actualBuilder.update(args);
+
+ // rebuild layer snapshots from scratch and verify that it matches the updated state.
+ LayerSnapshotBuilder expectedBuilder(args);
+ mLifecycleManager.commitChanges();
+ ASSERT_TRUE(expectedBuilder.getSnapshots().size() > 0);
+ ASSERT_TRUE(actualBuilder.getSnapshots().size() > 0);
+
+ std::vector<std::unique_ptr<LayerSnapshot>>& snapshots = actualBuilder.getSnapshots();
+ std::vector<uint32_t> actualVisibleLayerIdsInZOrder;
+ for (auto& snapshot : snapshots) {
+ if (!snapshot->isVisible) {
+ break;
+ }
+ actualVisibleLayerIdsInZOrder.push_back(snapshot->path.id);
+ }
+ EXPECT_EQ(expectedVisibleLayerIdsInZOrder, actualVisibleLayerIdsInZOrder);
+ }
+
+ LayerSnapshot* getSnapshot(uint32_t layerId) { return mSnapshotBuilder.getSnapshot(layerId); }
+
+ LayerHierarchyBuilder mHierarchyBuilder{{}};
+ LayerSnapshotBuilder mSnapshotBuilder;
+ std::unordered_map<uint32_t, sp<LayerHandle>> mHandles;
+ display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos;
+ renderengine::ShadowSettings globalShadowSettings;
+ static const std::vector<uint32_t> STARTING_ZORDER;
+};
+const std::vector<uint32_t> LayerSnapshotTest::STARTING_ZORDER = {1, 11, 111, 12, 121,
+ 122, 1221, 13, 2};
+
+TEST_F(LayerSnapshotTest, buildSnapshot) {
+ LayerSnapshotBuilder::Args args{
+ .root = mHierarchyBuilder.getHierarchy(),
+ .layerLifecycleManager = mLifecycleManager,
+ .includeMetadata = false,
+ .displays = mFrontEndDisplayInfos,
+ .globalShadowSettings = globalShadowSettings,
+ };
+ LayerSnapshotBuilder builder(args);
+}
+
+TEST_F(LayerSnapshotTest, updateSnapshot) {
+ LayerSnapshotBuilder::Args args{
+ .root = mHierarchyBuilder.getHierarchy(),
+ .layerLifecycleManager = mLifecycleManager,
+ .includeMetadata = false,
+ .displays = mFrontEndDisplayInfos,
+ .globalShadowSettings = globalShadowSettings,
+ };
+
+ LayerSnapshotBuilder builder;
+ builder.update(args);
+}
+
+// update using parent snapshot data
+TEST_F(LayerSnapshotTest, croppedByParent) {
+ /// MAKE ALL LAYERS VISIBLE BY DEFAULT
+ DisplayInfo info;
+ info.info.logicalHeight = 100;
+ info.info.logicalWidth = 200;
+ mFrontEndDisplayInfos.emplace_or_replace(ui::LayerStack::fromValue(1), info);
+ Rect layerCrop(0, 0, 10, 20);
+ setCrop(11, layerCrop);
+ EXPECT_TRUE(mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Geometry));
+ UPDATE_AND_VERIFY_WITH_DISPLAY_CHANGES(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(11)->geomCrop, layerCrop);
+ EXPECT_EQ(getSnapshot(111)->geomLayerBounds, layerCrop.toFloatRect());
+ float maxHeight = static_cast<float>(info.info.logicalHeight * 10);
+ float maxWidth = static_cast<float>(info.info.logicalWidth * 10);
+
+ FloatRect maxDisplaySize(-maxWidth, -maxHeight, maxWidth, maxHeight);
+ EXPECT_EQ(getSnapshot(1)->geomLayerBounds, maxDisplaySize);
+}
+
+// visibility tests
+TEST_F(LayerSnapshotTest, newLayerHiddenByPolicy) {
+ createLayer(112, 11);
+ hideLayer(112);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+ showLayer(112);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 11, 111, 112, 12, 121, 122, 1221, 13, 2});
+}
+
+TEST_F(LayerSnapshotTest, hiddenByParent) {
+ hideLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+}
+
+TEST_F(LayerSnapshotTest, reparentShowsChild) {
+ hideLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+
+ showLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+}
+
+TEST_F(LayerSnapshotTest, reparentHidesChild) {
+ hideLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+
+ reparentLayer(121, 11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 122, 1221, 13, 2});
+}
+
+TEST_F(LayerSnapshotTest, unHidingUpdatesSnapshot) {
+ hideLayer(11);
+ Rect crop(1, 2, 3, 4);
+ setCrop(111, crop);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+
+ showLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(111)->geomLayerBounds, crop.toFloatRect());
+}
+
+TEST_F(LayerSnapshotTest, childBehindParentCanBeHiddenByParent) {
+ setZ(111, -1);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 111, 11, 12, 121, 122, 1221, 13, 2});
+
+ hideLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+}
+
+// relative tests
+TEST_F(LayerSnapshotTest, RelativeParentCanHideChild) {
+ reparentRelativeLayer(13, 11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 11, 13, 111, 12, 121, 122, 1221, 2});
+
+ hideLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 2});
+}
+
+TEST_F(LayerSnapshotTest, ReparentingToHiddenRelativeParentHidesChild) {
+ hideLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+ reparentRelativeLayer(13, 11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 2});
+}
+
+TEST_F(LayerSnapshotTest, AlphaInheritedByChildren) {
+ setAlpha(1, 0.5);
+ setAlpha(122, 0.5);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(12)->alpha, 0.5f);
+ EXPECT_EQ(getSnapshot(1221)->alpha, 0.25f);
+}
+
+// Change states
+TEST_F(LayerSnapshotTest, UpdateClearsPreviousChangeStates) {
+ setCrop(1, Rect(1, 2, 3, 4));
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_TRUE(getSnapshot(1)->changes.get() != 0);
+ EXPECT_TRUE(getSnapshot(11)->changes.get() != 0);
+ setCrop(2, Rect(1, 2, 3, 4));
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_TRUE(getSnapshot(2)->changes.get() != 0);
+ EXPECT_TRUE(getSnapshot(1)->changes.get() == 0);
+ EXPECT_TRUE(getSnapshot(11)->changes.get() == 0);
+}
+
+TEST_F(LayerSnapshotTest, FastPathClearsPreviousChangeStates) {
+ setColor(11, {1._hf, 0._hf, 0._hf});
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_TRUE(getSnapshot(11)->changes.get() != 0);
+ EXPECT_TRUE(getSnapshot(1)->changes.get() == 0);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_TRUE(getSnapshot(11)->changes.get() == 0);
+}
+
+TEST_F(LayerSnapshotTest, FastPathSetsChangeFlagToContent) {
+ setColor(1, {1._hf, 0._hf, 0._hf});
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(1)->changes, RequestedLayerState::Changes::Content);
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
index 5e1042e..7aa5201 100644
--- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
@@ -23,6 +23,7 @@
#include "FrameTimeline.h"
#include "Scheduler/MessageQueue.h"
#include "SurfaceFlinger.h"
+#include "mock/MockVSyncDispatch.h"
namespace android {
@@ -59,14 +60,6 @@
const sp<MockHandler> mHandler;
};
-struct MockVSyncDispatch : scheduler::VSyncDispatch {
- MOCK_METHOD(CallbackToken, registerCallback, (Callback, std::string), (override));
- MOCK_METHOD(void, unregisterCallback, (CallbackToken), (override));
- MOCK_METHOD(scheduler::ScheduleResult, schedule, (CallbackToken, ScheduleTiming), (override));
- MOCK_METHOD(scheduler::CancelResult, cancel, (CallbackToken token), (override));
- MOCK_METHOD(void, dump, (std::string&), (const, override));
-};
-
struct MockTokenManager : frametimeline::TokenManager {
MOCK_METHOD1(generateTokenForPredictions, int64_t(frametimeline::TimelineItem&& prediction));
MOCK_CONST_METHOD1(getPredictionsForToken, std::optional<frametimeline::TimelineItem>(int64_t));
@@ -79,7 +72,7 @@
EXPECT_CALL(mVSyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
}
- MockVSyncDispatch mVSyncDispatch;
+ mock::VSyncDispatch mVSyncDispatch;
MockTokenManager mTokenManager;
TestableMessageQueue mEventQueue;
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index b8a6063..0f53eb6 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -26,6 +26,7 @@
#include "Scheduler/Scheduler.h"
#include "Scheduler/VSyncTracker.h"
#include "Scheduler/VsyncController.h"
+#include "mock/MockVSyncDispatch.h"
#include "mock/MockVSyncTracker.h"
#include "mock/MockVsyncController.h"
@@ -42,7 +43,9 @@
std::unique_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr,
ISchedulerCallback& callback)
: Scheduler(*this, callback, Feature::kContentDetection) {
- mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller)));
+ mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker),
+ std::make_unique<mock::VSyncDispatch>(),
+ std::move(controller)));
const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
registerDisplay(displayId, std::move(selectorPtr));
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 113c518..584d52c 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -231,6 +231,8 @@
std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mTimeStats, fps,
hal::PowerMode::OFF);
+ mTokenManager = std::make_unique<frametimeline::impl::TokenManager>();
+
using Callback = scheduler::ISchedulerCallback;
Callback& callback = callbackImpl == SchedulerCallbackImpl::kNoOp
? static_cast<Callback&>(mNoOpSchedulerCallback)
@@ -248,6 +250,8 @@
std::move(selectorPtr), callback);
}
+ mScheduler->initVsync(mScheduler->getVsyncSchedule().getDispatch(), *mTokenManager, 0ms);
+
mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
resetScheduler(mScheduler);
@@ -911,6 +915,7 @@
sp<SurfaceFlinger> mFlinger;
scheduler::mock::SchedulerCallback mSchedulerCallback;
scheduler::mock::NoOpSchedulerCallback mNoOpSchedulerCallback;
+ std::unique_ptr<frametimeline::impl::TokenManager> mTokenManager;
scheduler::TestableScheduler* mScheduler = nullptr;
};
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index 2b86e94..14a2860 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -270,6 +270,43 @@
EXPECT_THAT(cb.mCalls[0], Eq(mPeriod));
}
+TEST_F(VSyncDispatchTimerQueueTest, updateAlarmSettingFuture) {
+ auto intended = mPeriod - 230;
+ Sequence seq;
+ EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq);
+ EXPECT_CALL(mMockClock, alarmAt(_, 700)).InSequence(seq);
+
+ CountingCallback cb(mDispatch);
+ auto result = mDispatch.schedule(cb,
+ {.workDuration = 100,
+ .readyDuration = 0,
+ .earliestVsync = intended});
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(900, *result);
+
+ result = mDispatch.update(cb,
+ {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended});
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(700, *result);
+
+ advanceToNextCallback();
+
+ ASSERT_THAT(cb.mCalls.size(), Eq(1));
+ EXPECT_THAT(cb.mCalls[0], Eq(mPeriod));
+ EXPECT_THAT(cb.mWakeupTime[0], Eq(700));
+}
+
+TEST_F(VSyncDispatchTimerQueueTest, updateDoesntSchedule) {
+ auto intended = mPeriod - 230;
+ EXPECT_CALL(mMockClock, alarmAt(_, _)).Times(0);
+
+ CountingCallback cb(mDispatch);
+ const auto result =
+ mDispatch.update(cb,
+ {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended});
+ EXPECT_FALSE(result.has_value());
+}
+
TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFutureWithAdjustmentToTrueVsync) {
EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000)).WillOnce(Return(1150));
EXPECT_CALL(mMockClock, alarmAt(_, 1050));
diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
index 8bd689a..1fb2709 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -69,14 +69,6 @@
std::shared_ptr<Clock> const mClock;
};
-struct MockVSyncDispatch : VSyncDispatch {
- MOCK_METHOD(CallbackToken, registerCallback, (Callback, std::string), (override));
- MOCK_METHOD(void, unregisterCallback, (CallbackToken), (override));
- MOCK_METHOD(ScheduleResult, schedule, (CallbackToken, ScheduleTiming), (override));
- MOCK_METHOD(CancelResult, cancel, (CallbackToken), (override));
- MOCK_METHOD(void, dump, (std::string&), (const, override));
-};
-
std::shared_ptr<android::FenceTime> generateInvalidFence() {
sp<Fence> fence = sp<Fence>::make();
return std::make_shared<android::FenceTime>(fence);
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 2f16b7b..5e29fe7 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -145,6 +145,11 @@
MOCK_METHOD2(setBootDisplayConfig, Error(Display, Config));
MOCK_METHOD1(clearBootDisplayConfig, Error(Display));
MOCK_METHOD2(getPreferredBootDisplayConfig, Error(Display, Config*));
+ MOCK_METHOD1(getHdrConversionCapabilities,
+ Error(std::vector<
+ aidl::android::hardware::graphics::common::HdrConversionCapability>*));
+ MOCK_METHOD1(setHdrConversionStrategy,
+ Error(aidl::android::hardware::graphics::common::HdrConversionStrategy));
MOCK_METHOD2(getSupportedContentTypes,
V2_4::Error(Display, std::vector<IComposerClient::ContentType>*));
MOCK_METHOD2(setContentType, V2_4::Error(Display, IComposerClient::ContentType));
diff --git a/libs/gui/aidl/android/gui/ReleaseCallbackId.aidl b/services/surfaceflinger/tests/unittests/mock/MockVSyncDispatch.cpp
similarity index 66%
copy from libs/gui/aidl/android/gui/ReleaseCallbackId.aidl
copy to services/surfaceflinger/tests/unittests/mock/MockVSyncDispatch.cpp
index c86de34..2817514 100644
--- a/libs/gui/aidl/android/gui/ReleaseCallbackId.aidl
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncDispatch.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 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,6 +14,12 @@
* limitations under the License.
*/
-package android.gui;
+#include "mock/MockVSyncDispatch.h"
-parcelable ReleaseCallbackId cpp_header "gui/ReleaseCallbackId.h";
+namespace android::mock {
+
+// Explicit default instantiation is recommended.
+VSyncDispatch::VSyncDispatch() = default;
+VSyncDispatch::~VSyncDispatch() = default;
+
+} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncDispatch.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncDispatch.h
new file mode 100644
index 0000000..dc32ff9
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncDispatch.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "Scheduler/VSyncDispatch.h"
+
+namespace android::mock {
+
+class VSyncDispatch : public android::scheduler::VSyncDispatch {
+public:
+ VSyncDispatch();
+ ~VSyncDispatch() override;
+
+ MOCK_METHOD(CallbackToken, registerCallback, (Callback, std::string), (override));
+ MOCK_METHOD(void, unregisterCallback, (CallbackToken), (override));
+ MOCK_METHOD(scheduler::ScheduleResult, schedule, (CallbackToken, ScheduleTiming), (override));
+ MOCK_METHOD(scheduler::ScheduleResult, update, (CallbackToken, ScheduleTiming), (override));
+ MOCK_METHOD(scheduler::CancelResult, cancel, (CallbackToken token), (override));
+ MOCK_METHOD(void, dump, (std::string&), (const, override));
+};
+
+} // namespace android::mock