Merge changes I15e0f5a2,I0db8970b
* changes:
SF: Obey active display's RefreshRateSelector
SF: Do not deref empty std::optional<PowerMode>
diff --git a/cmds/servicemanager/Android.bp b/cmds/servicemanager/Android.bp
index 61f931e..1386660 100644
--- a/cmds/servicemanager/Android.bp
+++ b/cmds/servicemanager/Android.bp
@@ -48,17 +48,6 @@
}
cc_binary {
- name: "servicemanager.microdroid",
- defaults: ["servicemanager_defaults"],
- init_rc: ["servicemanager.microdroid.rc"],
- srcs: ["main.cpp"],
- bootstrap: true,
- // Prevent this from being installed when running tests in this directory.
- // This is okay because microdorid build rule can bundle this anyway.
- installable: false,
-}
-
-cc_binary {
name: "servicemanager.recovery",
stem: "servicemanager",
recovery: true,
diff --git a/cmds/servicemanager/servicemanager.microdroid.rc b/cmds/servicemanager/servicemanager.microdroid.rc
deleted file mode 100644
index 8819e1e..0000000
--- a/cmds/servicemanager/servicemanager.microdroid.rc
+++ /dev/null
@@ -1,8 +0,0 @@
-service servicemanager /system/bin/servicemanager.microdroid
- class core
- user system
- group system readproc
- critical
- onrestart setprop servicemanager.ready false
- onrestart restart apexd
- shutdown critical
diff --git a/libs/gui/aidl/android/gui/SupportedBufferCombinations.aidl b/include/ftl/details/mixins.h
similarity index 62%
rename from libs/gui/aidl/android/gui/SupportedBufferCombinations.aidl
rename to include/ftl/details/mixins.h
index a8bc994..9ab9e08 100644
--- a/libs/gui/aidl/android/gui/SupportedBufferCombinations.aidl
+++ b/include/ftl/details/mixins.h
@@ -14,10 +14,17 @@
* limitations under the License.
*/
-package android.gui;
+#pragma once
-/** @hide */
-parcelable SupportedBufferCombinations {
- int[] pixelFormats;
- int[] dataspaces;
-}
+namespace android::ftl::details {
+
+template <typename Self, template <typename> class>
+class Mixin {
+ protected:
+ constexpr Self& self() { return *static_cast<Self*>(this); }
+ constexpr const Self& self() const { return *static_cast<const Self*>(this); }
+
+ constexpr auto& mut() { return self().value_; }
+};
+
+} // namespace android::ftl::details
diff --git a/include/ftl/enum.h b/include/ftl/enum.h
index 82af1d6..075d12b 100644
--- a/include/ftl/enum.h
+++ b/include/ftl/enum.h
@@ -92,7 +92,7 @@
// enum class E { A, B, C };
// static_assert(ftl::to_underlying(E::B) == 1);
//
-template <typename E>
+template <typename E, typename = std::enable_if_t<std::is_enum_v<E>>>
constexpr auto to_underlying(E v) {
return static_cast<std::underlying_type_t<E>>(v);
}
diff --git a/include/ftl/mixins.h b/include/ftl/mixins.h
new file mode 100644
index 0000000..0e1d200
--- /dev/null
+++ b/include/ftl/mixins.h
@@ -0,0 +1,148 @@
+/*
+ * 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 <ftl/details/mixins.h>
+
+namespace android::ftl {
+
+// CRTP mixins for defining type-safe wrappers that are distinct from their underlying type. Common
+// uses are IDs, opaque handles, and physical quantities. The constructor is provided by (and must
+// be inherited from) the `Constructible` mixin, whereas operators (equality, ordering, arithmetic,
+// etc.) are enabled through inheritance:
+//
+// struct Id : ftl::Constructible<Id, std::int32_t>, ftl::Equatable<Id> {
+// using Constructible::Constructible;
+// };
+//
+// static_assert(!std::is_default_constructible_v<Id>);
+//
+// Unlike `Constructible`, `DefaultConstructible` allows default construction. The default value is
+// zero-initialized unless specified:
+//
+// struct Color : ftl::DefaultConstructible<Color, std::uint8_t>,
+// ftl::Equatable<Color>,
+// ftl::Orderable<Color> {
+// using DefaultConstructible::DefaultConstructible;
+// };
+//
+// static_assert(Color() == Color(0u));
+// static_assert(ftl::to_underlying(Color(-1)) == 255u);
+// static_assert(Color(1u) < Color(2u));
+//
+// struct Sequence : ftl::DefaultConstructible<Sequence, std::int8_t, -1>,
+// ftl::Equatable<Sequence>,
+// ftl::Orderable<Sequence>,
+// ftl::Incrementable<Sequence> {
+// using DefaultConstructible::DefaultConstructible;
+// };
+//
+// static_assert(Sequence() == Sequence(-1));
+//
+// The underlying type need not be a fundamental type:
+//
+// struct Timeout : ftl::DefaultConstructible<Timeout, std::chrono::seconds, 10>,
+// ftl::Equatable<Timeout>,
+// ftl::Addable<Timeout> {
+// using DefaultConstructible::DefaultConstructible;
+// };
+//
+// using namespace std::chrono_literals;
+// static_assert(Timeout() + Timeout(5s) == Timeout(15s));
+//
+template <typename Self, typename T>
+struct Constructible {
+ explicit constexpr Constructible(T value) : value_(value) {}
+
+ explicit constexpr operator const T&() const { return value_; }
+
+ private:
+ template <typename, template <typename> class>
+ friend class details::Mixin;
+
+ T value_;
+};
+
+template <typename Self, typename T, auto kDefault = T{}>
+struct DefaultConstructible : Constructible<Self, T> {
+ using Constructible<Self, T>::Constructible;
+ constexpr DefaultConstructible() : DefaultConstructible(T{kDefault}) {}
+};
+
+// Shorthand for casting a type-safe wrapper to its underlying value.
+template <typename Self, typename T>
+constexpr const T& to_underlying(const Constructible<Self, T>& c) {
+ return static_cast<const T&>(c);
+}
+
+// Comparison operators for equality.
+template <typename Self>
+struct Equatable : details::Mixin<Self, Equatable> {
+ constexpr bool operator==(const Self& other) const {
+ return to_underlying(this->self()) == to_underlying(other);
+ }
+
+ constexpr bool operator!=(const Self& other) const { return !(*this == other); }
+};
+
+// Comparison operators for ordering.
+template <typename Self>
+struct Orderable : details::Mixin<Self, Orderable> {
+ constexpr bool operator<(const Self& other) const {
+ return to_underlying(this->self()) < to_underlying(other);
+ }
+
+ constexpr bool operator>(const Self& other) const { return other < this->self(); }
+ constexpr bool operator>=(const Self& other) const { return !(*this < other); }
+ constexpr bool operator<=(const Self& other) const { return !(*this > other); }
+};
+
+// Pre-increment and post-increment operators.
+template <typename Self>
+struct Incrementable : details::Mixin<Self, Incrementable> {
+ constexpr Self& operator++() {
+ ++this->mut();
+ return this->self();
+ }
+
+ constexpr Self operator++(int) {
+ const Self tmp = this->self();
+ operator++();
+ return tmp;
+ }
+};
+
+// Additive operators, including incrementing.
+template <typename Self>
+struct Addable : details::Mixin<Self, Addable>, Incrementable<Self> {
+ constexpr Self& operator+=(const Self& other) {
+ this->mut() += to_underlying(other);
+ return this->self();
+ }
+
+ constexpr Self operator+(const Self& other) const {
+ Self tmp = this->self();
+ return tmp += other;
+ }
+
+ private:
+ using Base = details::Mixin<Self, Addable>;
+ using Base::mut;
+ using Base::self;
+};
+
+} // namespace android::ftl
diff --git a/include/ftl/optional.h b/include/ftl/optional.h
index 626507f..7b02bac 100644
--- a/include/ftl/optional.h
+++ b/include/ftl/optional.h
@@ -97,6 +97,16 @@
}
};
+template <typename T, typename U>
+constexpr bool operator==(const Optional<T>& lhs, const Optional<U>& rhs) {
+ return static_cast<std::optional<T>>(lhs) == static_cast<std::optional<U>>(rhs);
+}
+
+template <typename T, typename U>
+constexpr bool operator!=(const Optional<T>& lhs, const Optional<U>& rhs) {
+ return !(lhs == rhs);
+}
+
// Deduction guides.
template <typename T>
Optional(T) -> Optional<T>;
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 11c8e5d..7770374 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -45,11 +45,11 @@
#define IF_LOG_TRANSACTIONS() if (false)
#define IF_LOG_COMMANDS() if (false)
-#define LOG_REMOTEREFS(...)
+#define LOG_REMOTEREFS(...)
#define IF_LOG_REMOTEREFS() if (false)
-#define LOG_THREADPOOL(...)
-#define LOG_ONEWAY(...)
+#define LOG_THREADPOOL(...)
+#define LOG_ONEWAY(...)
#else
@@ -394,14 +394,92 @@
// context, so we don't abort
}
+constexpr uint32_t encodeExplicitIdentity(bool hasExplicitIdentity, pid_t callingPid) {
+ uint32_t as_unsigned = static_cast<uint32_t>(callingPid);
+ if (hasExplicitIdentity) {
+ return as_unsigned | (1 << 30);
+ } else {
+ return as_unsigned & ~(1 << 30);
+ }
+}
+
+constexpr int64_t packCallingIdentity(bool hasExplicitIdentity, uid_t callingUid,
+ pid_t callingPid) {
+ // Calling PID is a 32-bit signed integer, but doesn't consume the entire 32 bit space.
+ // To future-proof this and because we have extra capacity, we decided to also support -1,
+ // since this constant is used to represent invalid UID in other places of the system.
+ // Thus, we pack hasExplicitIdentity into the 2nd bit from the left. This allows us to
+ // preserve the (left-most) bit for the sign while also encoding the value of
+ // hasExplicitIdentity.
+ // 32b | 1b | 1b | 30b
+ // token = [ calling uid | calling pid(sign) | has explicit identity | calling pid(rest) ]
+ uint64_t token = (static_cast<uint64_t>(callingUid) << 32) |
+ encodeExplicitIdentity(hasExplicitIdentity, callingPid);
+ return static_cast<int64_t>(token);
+}
+
+constexpr bool unpackHasExplicitIdentity(int64_t token) {
+ return static_cast<int32_t>(token) & (1 << 30);
+}
+
+constexpr uid_t unpackCallingUid(int64_t token) {
+ return static_cast<uid_t>(token >> 32);
+}
+
+constexpr pid_t unpackCallingPid(int64_t token) {
+ int32_t encodedPid = static_cast<int32_t>(token);
+ if (encodedPid & (1 << 31)) {
+ return encodedPid | (1 << 30);
+ } else {
+ return encodedPid & ~(1 << 30);
+ }
+}
+
+static_assert(unpackHasExplicitIdentity(packCallingIdentity(true, 1000, 9999)) == true,
+ "pack true hasExplicit");
+
+static_assert(unpackCallingUid(packCallingIdentity(true, 1000, 9999)) == 1000, "pack true uid");
+
+static_assert(unpackCallingPid(packCallingIdentity(true, 1000, 9999)) == 9999, "pack true pid");
+
+static_assert(unpackHasExplicitIdentity(packCallingIdentity(false, 1000, 9999)) == false,
+ "pack false hasExplicit");
+
+static_assert(unpackCallingUid(packCallingIdentity(false, 1000, 9999)) == 1000, "pack false uid");
+
+static_assert(unpackCallingPid(packCallingIdentity(false, 1000, 9999)) == 9999, "pack false pid");
+
+static_assert(unpackHasExplicitIdentity(packCallingIdentity(true, 1000, -1)) == true,
+ "pack true (negative) hasExplicit");
+
+static_assert(unpackCallingUid(packCallingIdentity(true, 1000, -1)) == 1000,
+ "pack true (negative) uid");
+
+static_assert(unpackCallingPid(packCallingIdentity(true, 1000, -1)) == -1,
+ "pack true (negative) pid");
+
+static_assert(unpackHasExplicitIdentity(packCallingIdentity(false, 1000, -1)) == false,
+ "pack false (negative) hasExplicit");
+
+static_assert(unpackCallingUid(packCallingIdentity(false, 1000, -1)) == 1000,
+ "pack false (negative) uid");
+
+static_assert(unpackCallingPid(packCallingIdentity(false, 1000, -1)) == -1,
+ "pack false (negative) pid");
+
int64_t IPCThreadState::clearCallingIdentity()
{
// ignore mCallingSid for legacy reasons
- int64_t token = ((int64_t)mCallingUid<<32) | mCallingPid;
+ int64_t token = packCallingIdentity(mHasExplicitIdentity, mCallingUid, mCallingPid);
clearCaller();
+ mHasExplicitIdentity = true;
return token;
}
+bool IPCThreadState::hasExplicitIdentity() {
+ return mHasExplicitIdentity;
+}
+
void IPCThreadState::setStrictModePolicy(int32_t policy)
{
mStrictModePolicy = policy;
@@ -474,9 +552,10 @@
void IPCThreadState::restoreCallingIdentity(int64_t token)
{
- mCallingUid = (int)(token>>32);
+ mCallingUid = unpackCallingUid(token);
mCallingSid = nullptr; // not enough data to restore
- mCallingPid = (int)token;
+ mCallingPid = unpackCallingPid(token);
+ mHasExplicitIdentity = unpackHasExplicitIdentity(token);
}
void IPCThreadState::clearCaller()
@@ -889,6 +968,7 @@
mCallRestriction(mProcess->mCallRestriction) {
pthread_setspecific(gTLS, this);
clearCaller();
+ mHasExplicitIdentity = false;
mIn.setDataCapacity(256);
mOut.setDataCapacity(256);
}
@@ -1279,6 +1359,7 @@
const pid_t origPid = mCallingPid;
const char* origSid = mCallingSid;
const uid_t origUid = mCallingUid;
+ const bool origHasExplicitIdentity = mHasExplicitIdentity;
const int32_t origStrictModePolicy = mStrictModePolicy;
const int32_t origTransactionBinderFlags = mLastTransactionBinderFlags;
const int32_t origWorkSource = mWorkSource;
@@ -1292,6 +1373,7 @@
mCallingPid = tr.sender_pid;
mCallingSid = reinterpret_cast<const char*>(tr_secctx.secctx);
mCallingUid = tr.sender_euid;
+ mHasExplicitIdentity = false;
mLastTransactionBinderFlags = tr.flags;
// ALOGI(">>>> TRANSACT from pid %d sid %s uid %d\n", mCallingPid,
@@ -1367,6 +1449,7 @@
mCallingPid = origPid;
mCallingSid = origSid;
mCallingUid = origUid;
+ mHasExplicitIdentity = origHasExplicitIdentity;
mStrictModePolicy = origStrictModePolicy;
mLastTransactionBinderFlags = origTransactionBinderFlags;
mWorkSource = origWorkSource;
diff --git a/libs/binder/include/binder/IPCThreadState.h b/libs/binder/include/binder/IPCThreadState.h
index c01e92f..65b77c6 100644
--- a/libs/binder/include/binder/IPCThreadState.h
+++ b/libs/binder/include/binder/IPCThreadState.h
@@ -139,6 +139,7 @@
int64_t clearCallingIdentity();
// Restores PID/UID (not SID)
void restoreCallingIdentity(int64_t token);
+ bool hasExplicitIdentity();
status_t setupPolling(int* fd);
status_t handlePolledCommands();
@@ -241,6 +242,7 @@
bool mPropagateWorkSource;
bool mIsLooper;
bool mIsFlushing;
+ bool mHasExplicitIdentity;
int32_t mStrictModePolicy;
int32_t mLastTransactionBinderFlags;
CallRestriction mCallRestriction;
diff --git a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
index dd177af..f08bde8 100644
--- a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
+++ b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
@@ -17,20 +17,41 @@
#pragma once
#include <sys/socket.h>
+#include <stdint.h>
extern "C" {
struct AIBinder;
+struct ARpcServer;
// Starts an RPC server on a given port and a given root IBinder object.
-// This function sets up the server and joins before returning.
-bool RunVsockRpcServer(AIBinder* service, unsigned int port);
+// Returns an opaque handle to the running server instance, or null if the server
+// could not be started.
+[[nodiscard]] ARpcServer* ARpcServer_newVsock(AIBinder* service, unsigned int port);
-// Starts an RPC server on a given port and a given root IBinder object.
-// This function sets up the server, calls readyCallback with a given param, and
-// then joins before returning.
-bool RunVsockRpcServerCallback(AIBinder* service, unsigned int port,
- void (*readyCallback)(void* param), void* param);
+// Starts a Unix domain RPC server with a given init-managed Unix domain `name`
+// and a given root IBinder object.
+// The socket should be created in init.rc with the same `name`.
+// Returns an opaque handle to the running server instance, or null if the server
+// could not be started.
+[[nodiscard]] ARpcServer* ARpcServer_newInitUnixDomain(AIBinder* service, const char* name);
+
+// Runs ARpcServer_join() in a background thread. Immediately returns.
+void ARpcServer_start(ARpcServer* server);
+
+// Joins the thread of a running RpcServer instance. At any given point, there
+// can only be one thread calling ARpcServer_join().
+// If a client needs to actively terminate join, call ARpcServer_shutdown() in
+// a separate thread.
+void ARpcServer_join(ARpcServer* server);
+
+// Shuts down any running ARpcServer_join().
+void ARpcServer_shutdown(ARpcServer* server);
+
+// Frees the ARpcServer handle and drops the reference count on the underlying
+// RpcServer instance. The handle must not be reused afterwards.
+// This automatically calls ARpcServer_shutdown().
+void ARpcServer_free(ARpcServer* server);
// Starts an RPC server on a given port and a given root IBinder factory.
// RunVsockRpcServerWithFactory acts like RunVsockRpcServerCallback, but instead of
@@ -42,15 +63,6 @@
AIBinder* VsockRpcClient(unsigned int cid, unsigned int port);
-// Starts a Unix domain RPC server with a given init-managed Unix domain `name` and
-// a given root IBinder object.
-// The socket should be created in init.rc with the same `name`.
-//
-// This function sets up the server, calls readyCallback with a given param, and
-// then joins before returning.
-bool RunInitUnixDomainRpcServer(AIBinder* service, const char* name,
- void (*readyCallback)(void* param), void* param);
-
// Gets the service via the RPC binder with Unix domain socket with the given
// Unix socket `name`.
// The final Unix domain socket path name is /dev/socket/`name`.
diff --git a/libs/binder/libbinder_rpc_unstable.cpp b/libs/binder/libbinder_rpc_unstable.cpp
index 9edb3b6..f55c779 100644
--- a/libs/binder/libbinder_rpc_unstable.cpp
+++ b/libs/binder/libbinder_rpc_unstable.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include <binder_rpc_unstable.hpp>
+
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <android/binder_libbinder.h>
@@ -25,23 +27,32 @@
using android::OK;
using android::RpcServer;
using android::RpcSession;
+using android::sp;
using android::status_t;
using android::statusToString;
using android::base::unique_fd;
-extern "C" {
+// Opaque handle for RpcServer.
+struct ARpcServer {};
-void RunRpcServer(android::sp<RpcServer>& server, AIBinder* service,
- void (*readyCallback)(void* param), void* param) {
- server->setRootObject(AIBinder_toPlatformBinder(service));
-
- if (readyCallback) readyCallback(param);
- server->join();
-
- // Shutdown any open sessions since server failed.
- (void)server->shutdown();
+static sp<RpcServer> toRpcServer(ARpcServer* handle) {
+ auto ref = reinterpret_cast<RpcServer*>(handle);
+ return sp<RpcServer>::fromExisting(ref);
}
+static ARpcServer* createRpcServerHandle(sp<RpcServer>& server) {
+ auto ref = server.get();
+ ref->incStrong(ref);
+ return reinterpret_cast<ARpcServer*>(ref);
+}
+
+static void freeRpcServerHandle(ARpcServer* handle) {
+ auto ref = reinterpret_cast<RpcServer*>(handle);
+ ref->decStrong(ref);
+}
+
+extern "C" {
+
bool RunVsockRpcServerWithFactory(AIBinder* (*factory)(unsigned int cid, void* context),
void* factoryContext, unsigned int port) {
auto server = RpcServer::make();
@@ -64,20 +75,47 @@
return true;
}
-bool RunVsockRpcServerCallback(AIBinder* service, unsigned int port,
- void (*readyCallback)(void* param), void* param) {
+ARpcServer* ARpcServer_newVsock(AIBinder* service, unsigned int port) {
auto server = RpcServer::make();
if (status_t status = server->setupVsockServer(port); status != OK) {
LOG(ERROR) << "Failed to set up vsock server with port " << port
<< " error: " << statusToString(status).c_str();
- return false;
+ return nullptr;
}
- RunRpcServer(server, service, readyCallback, param);
- return true;
+ server->setRootObject(AIBinder_toPlatformBinder(service));
+ return createRpcServerHandle(server);
}
-bool RunVsockRpcServer(AIBinder* service, unsigned int port) {
- return RunVsockRpcServerCallback(service, port, nullptr, nullptr);
+ARpcServer* ARpcServer_newInitUnixDomain(AIBinder* service, const char* name) {
+ auto server = RpcServer::make();
+ auto fd = unique_fd(android_get_control_socket(name));
+ if (!fd.ok()) {
+ LOG(ERROR) << "Failed to get fd for the socket:" << name;
+ return nullptr;
+ }
+ if (status_t status = server->setupRawSocketServer(std::move(fd)); status != OK) {
+ LOG(ERROR) << "Failed to set up Unix Domain RPC server with name " << name
+ << " error: " << statusToString(status).c_str();
+ return nullptr;
+ }
+ server->setRootObject(AIBinder_toPlatformBinder(service));
+ return createRpcServerHandle(server);
+}
+
+void ARpcServer_start(ARpcServer* handle) {
+ toRpcServer(handle)->start();
+}
+
+void ARpcServer_join(ARpcServer* handle) {
+ toRpcServer(handle)->join();
+}
+
+void ARpcServer_shutdown(ARpcServer* handle) {
+ toRpcServer(handle)->shutdown();
+}
+
+void ARpcServer_free(ARpcServer* handle) {
+ freeRpcServerHandle(handle);
}
AIBinder* VsockRpcClient(unsigned int cid, unsigned int port) {
@@ -90,23 +128,6 @@
return AIBinder_fromPlatformBinder(session->getRootObject());
}
-bool RunInitUnixDomainRpcServer(AIBinder* service, const char* name,
- void (*readyCallback)(void* param), void* param) {
- auto server = RpcServer::make();
- auto fd = unique_fd(android_get_control_socket(name));
- if (!fd.ok()) {
- LOG(ERROR) << "Failed to get fd for the socket:" << name;
- return false;
- }
- if (status_t status = server->setupRawSocketServer(std::move(fd)); status != OK) {
- LOG(ERROR) << "Failed to set up Unix Domain RPC server with name " << name
- << " error: " << statusToString(status).c_str();
- return false;
- }
- RunRpcServer(server, service, readyCallback, param);
- return true;
-}
-
AIBinder* UnixDomainRpcClient(const char* name) {
std::string pathname(name);
pathname = ANDROID_SOCKET_DIR "/" + pathname;
diff --git a/libs/binder/libbinder_rpc_unstable.map.txt b/libs/binder/libbinder_rpc_unstable.map.txt
index f9c7bcf..1bc2416 100644
--- a/libs/binder/libbinder_rpc_unstable.map.txt
+++ b/libs/binder/libbinder_rpc_unstable.map.txt
@@ -1,9 +1,12 @@
LIBBINDER_RPC_UNSTABLE_SHIM { # platform-only
global:
- RunVsockRpcServer;
- RunVsockRpcServerCallback;
+ ARpcServer_free;
+ ARpcServer_join;
+ ARpcServer_newInitUnixDomain;
+ ARpcServer_newVsock;
+ ARpcServer_shutdown;
+ ARpcServer_start;
VsockRpcClient;
- RunInitUnixDomainRpcServer;
UnixDomainRpcClient;
RpcPreconnectedClient;
local:
diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h
index c234270..ad4188f 100644
--- a/libs/binder/ndk/include_platform/android/binder_manager.h
+++ b/libs/binder/ndk/include_platform/android/binder_manager.h
@@ -68,6 +68,7 @@
*
* \param instance identifier of the service used to lookup the service.
*/
+[[deprecated("this polls 5s, use AServiceManager_waitForService or AServiceManager_checkService")]]
__attribute__((warn_unused_result)) AIBinder* AServiceManager_getService(const char* instance)
__INTRODUCED_IN(29);
@@ -108,6 +109,67 @@
__INTRODUCED_IN(31);
/**
+ * Function to call when a service is registered. The instance is passed as well as
+ * ownership of the binder named 'registered'.
+ *
+ * WARNING: a lock is held when this method is called in order to prevent races with
+ * AServiceManager_NotificationRegistration_delete. Do not make synchronous binder calls when
+ * implementing this method to avoid deadlocks.
+ *
+ * \param instance instance name of service registered
+ * \param registered ownership-passed instance of service registered
+ * \param cookie data passed during registration for notifications
+ */
+typedef void (*AServiceManager_onRegister)(const char* instance, AIBinder* registered,
+ void* cookie);
+
+/**
+ * Represents a registration to servicemanager which can be cleared anytime.
+ */
+struct AServiceManager_NotificationRegistration;
+
+/**
+ * Get notifications when a service is registered. If the service is already registered,
+ * you will immediately get a notification.
+ *
+ * WARNING: it is strongly recommended to use AServiceManager_waitForService API instead.
+ * That API will wait synchronously, which is what you usually want in cases, including
+ * using some feature or during boot up. There is a history of bugs where waiting for
+ * notifications like this races with service startup. Also, when this API is used, a service
+ * bug will result in silent failure (rather than a debuggable deadlock). Furthermore, there
+ * is a history of this API being used to know when a service is up as a proxy for whethre
+ * that service should be started. This should only be used if you are intending to get
+ * ahold of the service as a client. For lazy services, whether a service is registered
+ * should not be used as a proxy for when it should be registered, which is only known
+ * by the real client.
+ *
+ * WARNING: if you use this API, you must also ensure that you check missing services are
+ * started and crash otherwise. If service failures are ignored, the system rots.
+ *
+ * \param instance name of service to wait for notifications about
+ * \param onRegister callback for when service is registered
+ * \param cookie data associated with this callback
+ *
+ * \return the token for this registration. Deleting this token will unregister.
+ */
+__attribute__((warn_unused_result)) AServiceManager_NotificationRegistration*
+AServiceManager_registerForServiceNotifications(const char* instance,
+ AServiceManager_onRegister onRegister, void* cookie)
+ __INTRODUCED_IN(34);
+
+/**
+ * Unregister for notifications and delete the object.
+ *
+ * After this method is called, the callback is guaranteed to no longer be invoked. This will block
+ * until any in-progress onRegister callbacks have completed. It is therefore safe to immediately
+ * destroy the void* cookie that was registered when this method returns.
+ *
+ * \param notification object to dismiss
+ */
+void AServiceManager_NotificationRegistration_delete(
+ AServiceManager_NotificationRegistration* notification) __INTRODUCED_IN(34);
+
+/**
* Check if a service is declared (e.g. VINTF manifest).
*
* \param instance identifier of the service.
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 32ca564..5c7005c 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -155,6 +155,8 @@
LIBBINDER_NDK34 { # introduced=UpsideDownCake
global:
AServiceManager_getUpdatableApexName; # systemapi
+ AServiceManager_registerForServiceNotifications; # systemapi llndk
+ AServiceManager_NotificationRegistration_delete; # systemapi llndk
};
LIBBINDER_NDK_PLATFORM {
diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp
index a12d0e9..e107c83 100644
--- a/libs/binder/ndk/service_manager.cpp
+++ b/libs/binder/ndk/service_manager.cpp
@@ -28,6 +28,7 @@
using ::android::IServiceManager;
using ::android::sp;
using ::android::status_t;
+using ::android::statusToString;
using ::android::String16;
using ::android::String8;
@@ -86,6 +87,67 @@
AIBinder_incStrong(ret.get());
return ret.get();
}
+typedef void (*AServiceManager_onRegister)(const char* instance, AIBinder* registered,
+ void* cookie);
+
+struct AServiceManager_NotificationRegistration
+ : public IServiceManager::LocalRegistrationCallback {
+ std::mutex m;
+ const char* instance = nullptr;
+ void* cookie = nullptr;
+ AServiceManager_onRegister onRegister = nullptr;
+
+ virtual void onServiceRegistration(const String16& smInstance, const sp<IBinder>& binder) {
+ std::lock_guard<std::mutex> l(m);
+ if (onRegister == nullptr) return;
+
+ CHECK_EQ(String8(smInstance), instance);
+
+ sp<AIBinder> ret = ABpBinder::lookupOrCreateFromBinder(binder);
+ AIBinder_incStrong(ret.get());
+
+ onRegister(instance, ret.get(), cookie);
+ }
+
+ void clear() {
+ std::lock_guard<std::mutex> l(m);
+ instance = nullptr;
+ cookie = nullptr;
+ onRegister = nullptr;
+ }
+};
+
+__attribute__((warn_unused_result)) AServiceManager_NotificationRegistration*
+AServiceManager_registerForServiceNotifications(const char* instance,
+ AServiceManager_onRegister onRegister,
+ void* cookie) {
+ CHECK_NE(instance, nullptr);
+ CHECK_NE(onRegister, nullptr) << instance;
+ // cookie can be nullptr
+
+ auto cb = sp<AServiceManager_NotificationRegistration>::make();
+ cb->instance = instance;
+ cb->onRegister = onRegister;
+ cb->cookie = cookie;
+
+ sp<IServiceManager> sm = defaultServiceManager();
+ if (status_t res = sm->registerForNotifications(String16(instance), cb); res != STATUS_OK) {
+ LOG(ERROR) << "Failed to register for service notifications for " << instance << ": "
+ << statusToString(res);
+ return nullptr;
+ }
+
+ cb->incStrong(nullptr);
+ return cb.get();
+}
+
+void AServiceManager_NotificationRegistration_delete(
+ AServiceManager_NotificationRegistration* notification) {
+ CHECK_NE(notification, nullptr);
+ notification->clear();
+ notification->decStrong(nullptr);
+}
+
bool AServiceManager_isDeclared(const char* instance) {
if (instance == nullptr) {
return false;
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index e221e4c..9d5ef68 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -254,6 +254,47 @@
AIBinder_decStrong(binder);
}
+struct ServiceData {
+ std::string instance;
+ ndk::SpAIBinder binder;
+
+ static void fillOnRegister(const char* instance, AIBinder* binder, void* cookie) {
+ ServiceData* d = reinterpret_cast<ServiceData*>(cookie);
+ d->instance = instance;
+ d->binder = ndk::SpAIBinder(binder);
+ }
+};
+
+TEST(NdkBinder, RegisterForServiceNotificationsNonExisting) {
+ ServiceData data;
+ auto* notif = AServiceManager_registerForServiceNotifications(
+ "DOES_NOT_EXIST", ServiceData::fillOnRegister, (void*)&data);
+ ASSERT_NE(notif, nullptr);
+
+ sleep(1); // give us a chance to fail
+ AServiceManager_NotificationRegistration_delete(notif);
+
+ // checking after deleting to avoid needing a mutex over the data - otherwise
+ // in an environment w/ multiple threads, you would need to guard access
+ EXPECT_EQ(data.instance, "");
+ EXPECT_EQ(data.binder, nullptr);
+}
+
+TEST(NdkBinder, RegisterForServiceNotificationsExisting) {
+ ServiceData data;
+ auto* notif = AServiceManager_registerForServiceNotifications(
+ kExistingNonNdkService, ServiceData::fillOnRegister, (void*)&data);
+ ASSERT_NE(notif, nullptr);
+
+ sleep(1); // give us a chance to fail
+ AServiceManager_NotificationRegistration_delete(notif);
+
+ // checking after deleting to avoid needing a mutex over the data - otherwise
+ // in an environment w/ multiple threads, you would need to guard access
+ EXPECT_EQ(data.instance, kExistingNonNdkService);
+ EXPECT_EQ(data.binder, ndk::SpAIBinder(AServiceManager_checkService(kExistingNonNdkService)));
+}
+
TEST(NdkBinder, UnimplementedDump) {
sp<IFoo> foo = IFoo::getService(IFoo::kSomeInstanceName);
ASSERT_NE(foo, nullptr);
diff --git a/libs/binder/rust/rpcbinder/Android.bp b/libs/binder/rust/rpcbinder/Android.bp
index 9771cc9..f70ebfc 100644
--- a/libs/binder/rust/rpcbinder/Android.bp
+++ b/libs/binder/rust/rpcbinder/Android.bp
@@ -19,6 +19,7 @@
"libbinder_rpc_unstable_bindgen_sys",
"libbinder_rs",
"libdowncast_rs",
+ "libforeign_types",
"liblibc",
"liblog_rust",
],
diff --git a/libs/binder/rust/rpcbinder/src/lib.rs b/libs/binder/rust/rpcbinder/src/lib.rs
index 89a49a4..1b719aa 100644
--- a/libs/binder/rust/rpcbinder/src/lib.rs
+++ b/libs/binder/rust/rpcbinder/src/lib.rs
@@ -23,6 +23,4 @@
get_preconnected_rpc_interface, get_preconnected_rpc_service, get_unix_domain_rpc_interface,
get_unix_domain_rpc_service, get_vsock_rpc_interface, get_vsock_rpc_service,
};
-pub use server::{
- run_init_unix_domain_rpc_server, run_vsock_rpc_server, run_vsock_rpc_server_with_factory,
-};
+pub use server::{run_vsock_rpc_server_with_factory, RpcServer, RpcServerRef};
diff --git a/libs/binder/rust/rpcbinder/src/server.rs b/libs/binder/rust/rpcbinder/src/server.rs
index b350a13..42f5567 100644
--- a/libs/binder/rust/rpcbinder/src/server.rs
+++ b/libs/binder/rust/rpcbinder/src/server.rs
@@ -18,114 +18,89 @@
unstable_api::{AIBinder, AsNative},
SpIBinder,
};
+use binder_rpc_unstable_bindgen::ARpcServer;
+use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
+use std::io::{Error, ErrorKind};
use std::{ffi::CString, os::raw, ptr::null_mut};
-/// Runs a binder RPC server, serving the supplied binder service implementation on the given vsock
-/// port.
-///
-/// If and when the server is ready for connections (it is listening on the port), `on_ready` is
-/// called to allow appropriate action to be taken - e.g. to notify clients that they may now
-/// attempt to connect.
-///
-/// The current thread is joined to the binder thread pool to handle incoming messages.
-///
-/// Returns true if the server has shutdown normally, false if it failed in some way.
-pub fn run_vsock_rpc_server<F>(service: SpIBinder, port: u32, on_ready: F) -> bool
-where
- F: FnOnce(),
-{
- let mut ready_notifier = ReadyNotifier(Some(on_ready));
- ready_notifier.run_vsock_server(service, port)
+foreign_type! {
+ type CType = binder_rpc_unstable_bindgen::ARpcServer;
+ fn drop = binder_rpc_unstable_bindgen::ARpcServer_free;
+
+ /// A type that represents a foreign instance of RpcServer.
+ #[derive(Debug)]
+ pub struct RpcServer;
+ /// A borrowed RpcServer.
+ pub struct RpcServerRef;
}
-/// Runs a binder RPC server, serving the supplied binder service implementation on the given
-/// socket file name. The socket should be initialized in init.rc with the same name.
-///
-/// If and when the server is ready for connections, `on_ready` is called to allow appropriate
-/// action to be taken - e.g. to notify clients that they may now attempt to connect.
-///
-/// The current thread is joined to the binder thread pool to handle incoming messages.
-///
-/// Returns true if the server has shutdown normally, false if it failed in some way.
-pub fn run_init_unix_domain_rpc_server<F>(
- service: SpIBinder,
- socket_name: &str,
- on_ready: F,
-) -> bool
-where
- F: FnOnce(),
-{
- let mut ready_notifier = ReadyNotifier(Some(on_ready));
- ready_notifier.run_init_unix_domain_server(service, socket_name)
-}
+/// SAFETY - The opaque handle can be cloned freely.
+unsafe impl Send for RpcServer {}
+/// SAFETY - The underlying C++ RpcServer class is thread-safe.
+unsafe impl Sync for RpcServer {}
-struct ReadyNotifier<F>(Option<F>)
-where
- F: FnOnce();
-
-impl<F> ReadyNotifier<F>
-where
- F: FnOnce(),
-{
- fn run_vsock_server(&mut self, mut service: SpIBinder, port: u32) -> bool {
+impl RpcServer {
+ /// Creates a binder RPC server, serving the supplied binder service implementation on the given
+ /// vsock port.
+ pub fn new_vsock(mut service: SpIBinder, port: u32) -> Result<RpcServer, Error> {
let service = service.as_native_mut();
- let param = self.as_void_ptr();
// SAFETY: Service ownership is transferring to the server and won't be valid afterward.
// Plus the binder objects are threadsafe.
- // RunVsockRpcServerCallback does not retain a reference to `ready_callback` or `param`; it only
- // uses them before it returns, which is during the lifetime of `self`.
unsafe {
- binder_rpc_unstable_bindgen::RunVsockRpcServerCallback(
- service,
- port,
- Some(Self::ready_callback),
- param,
- )
+ Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newVsock(service, port))
}
}
- fn run_init_unix_domain_server(&mut self, mut service: SpIBinder, socket_name: &str) -> bool {
+ /// Creates a binder RPC server, serving the supplied binder service implementation on the given
+ /// socket file name. The socket should be initialized in init.rc with the same name.
+ pub fn new_init_unix_domain(
+ mut service: SpIBinder,
+ socket_name: &str,
+ ) -> Result<RpcServer, Error> {
let socket_name = match CString::new(socket_name) {
Ok(s) => s,
Err(e) => {
log::error!("Cannot convert {} to CString. Error: {:?}", socket_name, e);
- return false;
+ return Err(Error::from(ErrorKind::InvalidInput));
}
};
let service = service.as_native_mut();
- let param = self.as_void_ptr();
// SAFETY: Service ownership is transferring to the server and won't be valid afterward.
// Plus the binder objects are threadsafe.
- // RunInitUnixDomainRpcServer does not retain a reference to `ready_callback` or `param`;
- // it only uses them before it returns, which is during the lifetime of `self`.
unsafe {
- binder_rpc_unstable_bindgen::RunInitUnixDomainRpcServer(
+ Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newInitUnixDomain(
service,
socket_name.as_ptr(),
- Some(Self::ready_callback),
- param,
- )
+ ))
}
}
- fn as_void_ptr(&mut self) -> *mut raw::c_void {
- self as *mut _ as *mut raw::c_void
- }
-
- unsafe extern "C" fn ready_callback(param: *mut raw::c_void) {
- // SAFETY: This is only ever called by `RunVsockRpcServerCallback`, within the lifetime of the
- // `ReadyNotifier`, with `param` taking the value returned by `as_void_ptr` (so a properly
- // aligned non-null pointer to an initialized instance).
- let ready_notifier = param as *mut Self;
- ready_notifier.as_mut().unwrap().notify()
- }
-
- fn notify(&mut self) {
- if let Some(on_ready) = self.0.take() {
- on_ready();
+ unsafe fn checked_from_ptr(ptr: *mut ARpcServer) -> Result<RpcServer, Error> {
+ if ptr.is_null() {
+ return Err(Error::new(ErrorKind::Other, "Failed to start server"));
}
+ Ok(RpcServer::from_ptr(ptr))
+ }
+}
+
+impl RpcServerRef {
+ /// Starts a new background thread and calls join(). Returns immediately.
+ pub fn start(&self) {
+ unsafe { binder_rpc_unstable_bindgen::ARpcServer_start(self.as_ptr()) };
+ }
+
+ /// Joins the RpcServer thread. The call blocks until the server terminates.
+ /// This must be called from exactly one thread.
+ pub fn join(&self) {
+ unsafe { binder_rpc_unstable_bindgen::ARpcServer_join(self.as_ptr()) };
+ }
+
+ /// Shuts down the running RpcServer. Can be called multiple times and from
+ /// multiple threads. Called automatically during drop().
+ pub fn shutdown(&self) {
+ unsafe { binder_rpc_unstable_bindgen::ARpcServer_shutdown(self.as_ptr()) };
}
}
diff --git a/libs/fakeservicemanager/ServiceManager.cpp b/libs/fakeservicemanager/ServiceManager.cpp
index 480ec79..1109ad8 100644
--- a/libs/fakeservicemanager/ServiceManager.cpp
+++ b/libs/fakeservicemanager/ServiceManager.cpp
@@ -36,6 +36,9 @@
status_t ServiceManager::addService(const String16& name, const sp<IBinder>& service,
bool /*allowIsolated*/,
int /*dumpsysFlags*/) {
+ if (service == nullptr) {
+ return UNEXPECTED_NULL;
+ }
mNameToService[name] = service;
return NO_ERROR;
}
@@ -103,4 +106,8 @@
std::vector<IServiceManager::ServiceDebugInfo> ret;
return ret;
}
+
+void ServiceManager::clear() {
+ mNameToService.clear();
+}
} // namespace android
diff --git a/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h b/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h
index ee0637e..ba6bb7d 100644
--- a/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h
+++ b/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h
@@ -64,6 +64,9 @@
std::vector<IServiceManager::ServiceDebugInfo> getServiceDebugInfo() override;
+ // Clear all of the registered services
+ void clear();
+
private:
std::map<String16, sp<IBinder>> mNameToService;
};
diff --git a/libs/fakeservicemanager/test_sm.cpp b/libs/fakeservicemanager/test_sm.cpp
index 71e5abe..8682c1c 100644
--- a/libs/fakeservicemanager/test_sm.cpp
+++ b/libs/fakeservicemanager/test_sm.cpp
@@ -50,6 +50,12 @@
IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
}
+TEST(AddService, SadNullBinder) {
+ auto sm = new ServiceManager();
+ EXPECT_EQ(sm->addService(String16("foo"), nullptr, false /*allowIsolated*/,
+ IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), android::UNEXPECTED_NULL);
+}
+
TEST(AddService, HappyOverExistingService) {
auto sm = new ServiceManager();
EXPECT_EQ(sm->addService(String16("foo"), getBinder(), false /*allowIsolated*/,
@@ -58,6 +64,15 @@
IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
}
+TEST(AddService, HappyClearAddedService) {
+ auto sm = new ServiceManager();
+ EXPECT_EQ(sm->addService(String16("foo"), getBinder(), false /*allowIsolated*/,
+ IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
+ EXPECT_NE(sm->getService(String16("foo")), nullptr);
+ sm->clear();
+ EXPECT_EQ(sm->getService(String16("foo")), nullptr);
+}
+
TEST(GetService, HappyHappy) {
auto sm = new ServiceManager();
sp<IBinder> service = getBinder();
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index df0b271..8e57152 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -22,6 +22,7 @@
"flags_test.cpp",
"future_test.cpp",
"match_test.cpp",
+ "mixins_test.cpp",
"non_null_test.cpp",
"optional_test.cpp",
"shared_mutex_test.cpp",
diff --git a/libs/ftl/mixins_test.cpp b/libs/ftl/mixins_test.cpp
new file mode 100644
index 0000000..2c9f9df
--- /dev/null
+++ b/libs/ftl/mixins_test.cpp
@@ -0,0 +1,185 @@
+/*
+ * 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 <ftl/mixins.h>
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+namespace android::test {
+namespace {
+
+// Keep in sync with example usage in header file.
+
+struct Id : ftl::Constructible<Id, std::int32_t>, ftl::Equatable<Id> {
+ using Constructible::Constructible;
+};
+
+static_assert(!std::is_default_constructible_v<Id>);
+
+struct Color : ftl::DefaultConstructible<Color, std::uint8_t>,
+ ftl::Equatable<Color>,
+ ftl::Orderable<Color> {
+ using DefaultConstructible::DefaultConstructible;
+};
+
+static_assert(Color() == Color(0u));
+static_assert(ftl::to_underlying(Color(-1)) == 255u);
+static_assert(Color(1u) < Color(2u));
+
+struct Sequence : ftl::DefaultConstructible<Sequence, std::int8_t, -1>,
+ ftl::Equatable<Sequence>,
+ ftl::Orderable<Sequence>,
+ ftl::Incrementable<Sequence> {
+ using DefaultConstructible::DefaultConstructible;
+};
+
+static_assert(Sequence() == Sequence(-1));
+
+struct Timeout : ftl::DefaultConstructible<Timeout, std::chrono::seconds, 10>,
+ ftl::Equatable<Timeout>,
+ ftl::Addable<Timeout> {
+ using DefaultConstructible::DefaultConstructible;
+};
+
+using namespace std::chrono_literals;
+static_assert(Timeout() + Timeout(5s) == Timeout(15s));
+
+// Construction.
+constexpr Id kId{1234};
+constexpr Sequence kSequence;
+
+// Underlying value.
+static_assert(ftl::to_underlying(Id(-42)) == -42);
+static_assert(ftl::to_underlying(kSequence) == -1);
+
+// Casting.
+static_assert(static_cast<std::int32_t>(Id(-1)) == -1);
+static_assert(static_cast<std::int8_t>(kSequence) == -1);
+
+static_assert(!std::is_convertible_v<std::int32_t, Id>);
+static_assert(!std::is_convertible_v<Id, std::int32_t>);
+
+// Equality.
+static_assert(kId == Id(1234));
+static_assert(kId != Id(123));
+static_assert(kSequence == Sequence(-1));
+
+// Ordering.
+static_assert(Sequence(1) < Sequence(2));
+static_assert(Sequence(2) > Sequence(1));
+static_assert(Sequence(3) <= Sequence(4));
+static_assert(Sequence(4) >= Sequence(3));
+static_assert(Sequence(5) <= Sequence(5));
+static_assert(Sequence(6) >= Sequence(6));
+
+// Incrementing.
+template <typename Op, typename T, typename... Ts>
+constexpr auto mutable_op(Op op, T lhs, Ts... rhs) {
+ const T result = op(lhs, rhs...);
+ return std::make_pair(lhs, result);
+}
+
+static_assert(mutable_op([](auto& lhs) { return ++lhs; }, Sequence()) ==
+ std::make_pair(Sequence(0), Sequence(0)));
+
+static_assert(mutable_op([](auto& lhs) { return lhs++; }, Sequence()) ==
+ std::make_pair(Sequence(0), Sequence(-1)));
+
+// Addition.
+
+// `Addable` implies `Incrementable`.
+static_assert(mutable_op([](auto& lhs) { return ++lhs; }, Timeout()) ==
+ std::make_pair(Timeout(11s), Timeout(11s)));
+
+static_assert(mutable_op([](auto& lhs) { return lhs++; }, Timeout()) ==
+ std::make_pair(Timeout(11s), Timeout(10s)));
+
+static_assert(Timeout(5s) + Timeout(6s) == Timeout(11s));
+
+static_assert(mutable_op([](auto& lhs, const auto& rhs) { return lhs += rhs; }, Timeout(7s),
+ Timeout(8s)) == std::make_pair(Timeout(15s), Timeout(15s)));
+
+// Type safety.
+
+namespace traits {
+
+template <typename, typename = void>
+struct is_incrementable : std::false_type {};
+
+template <typename T>
+struct is_incrementable<T, std::void_t<decltype(++std::declval<T&>())>> : std::true_type {};
+
+template <typename T>
+constexpr bool is_incrementable_v = is_incrementable<T>{};
+
+template <typename, typename, typename, typename = void>
+struct has_binary_op : std::false_type {};
+
+template <typename Op, typename T, typename U>
+struct has_binary_op<Op, T, U, std::void_t<decltype(Op{}(std::declval<T&>(), std::declval<U&>()))>>
+ : std::true_type {};
+
+template <typename T, typename U>
+constexpr bool is_equatable_v =
+ has_binary_op<std::equal_to<void>, T, U>{} && has_binary_op<std::not_equal_to<void>, T, U>{};
+
+template <typename T, typename U>
+constexpr bool is_orderable_v =
+ has_binary_op<std::less<void>, T, U>{} && has_binary_op<std::less_equal<void>, T, U>{} &&
+ has_binary_op<std::greater<void>, T, U>{} && has_binary_op<std::greater_equal<void>, T, U>{};
+
+template <typename T, typename U>
+constexpr bool is_addable_v = has_binary_op<std::plus<void>, T, U>{};
+
+} // namespace traits
+
+struct Real : ftl::Constructible<Real, float> {
+ using Constructible::Constructible;
+};
+
+static_assert(traits::is_equatable_v<Id, Id>);
+static_assert(!traits::is_equatable_v<Real, Real>);
+static_assert(!traits::is_equatable_v<Id, Color>);
+static_assert(!traits::is_equatable_v<Sequence, Id>);
+static_assert(!traits::is_equatable_v<Id, std::int32_t>);
+static_assert(!traits::is_equatable_v<std::chrono::seconds, Timeout>);
+
+static_assert(traits::is_orderable_v<Color, Color>);
+static_assert(!traits::is_orderable_v<Id, Id>);
+static_assert(!traits::is_orderable_v<Real, Real>);
+static_assert(!traits::is_orderable_v<Color, Sequence>);
+static_assert(!traits::is_orderable_v<Color, std::uint8_t>);
+static_assert(!traits::is_orderable_v<std::chrono::seconds, Timeout>);
+
+static_assert(traits::is_incrementable_v<Sequence>);
+static_assert(traits::is_incrementable_v<Timeout>);
+static_assert(!traits::is_incrementable_v<Id>);
+static_assert(!traits::is_incrementable_v<Color>);
+static_assert(!traits::is_incrementable_v<Real>);
+
+static_assert(traits::is_addable_v<Timeout, Timeout>);
+static_assert(!traits::is_addable_v<Id, Id>);
+static_assert(!traits::is_addable_v<Real, Real>);
+static_assert(!traits::is_addable_v<Sequence, Sequence>);
+static_assert(!traits::is_addable_v<Timeout, Sequence>);
+static_assert(!traits::is_addable_v<Color, Timeout>);
+
+} // namespace
+} // namespace android::test
diff --git a/libs/ftl/optional_test.cpp b/libs/ftl/optional_test.cpp
index f7410c2..6b3b6c4 100644
--- a/libs/ftl/optional_test.cpp
+++ b/libs/ftl/optional_test.cpp
@@ -164,4 +164,35 @@
}));
}
+// Comparison.
+namespace {
+
+constexpr Optional<int> kOptional1 = 1;
+constexpr Optional<int> kAnotherOptional1 = 1;
+constexpr Optional<int> kOptional2 = 2;
+constexpr Optional<int> kOptionalEmpty, kAnotherOptionalEmpty;
+
+constexpr std::optional<int> kStdOptional1 = 1;
+
+static_assert(kOptional1 == kAnotherOptional1);
+
+static_assert(kOptional1 != kOptional2);
+static_assert(kOptional2 != kOptional1);
+
+static_assert(kOptional1 != kOptionalEmpty);
+static_assert(kOptionalEmpty != kOptional1);
+
+static_assert(kOptionalEmpty == kAnotherOptionalEmpty);
+
+static_assert(kOptional1 == kStdOptional1);
+static_assert(kStdOptional1 == kOptional1);
+
+static_assert(kOptional2 != kStdOptional1);
+static_assert(kStdOptional1 != kOptional2);
+
+static_assert(kOptional2 != kOptionalEmpty);
+static_assert(kOptionalEmpty != kOptional2);
+
+} // namespace
+
} // namespace android::test
diff --git a/libs/gui/aidl/android/gui/OverlayProperties.aidl b/libs/gui/aidl/android/gui/OverlayProperties.aidl
index 80d5ced..75cea15 100644
--- a/libs/gui/aidl/android/gui/OverlayProperties.aidl
+++ b/libs/gui/aidl/android/gui/OverlayProperties.aidl
@@ -16,9 +16,11 @@
package android.gui;
-import android.gui.SupportedBufferCombinations;
-
/** @hide */
parcelable OverlayProperties {
+ parcelable SupportedBufferCombinations {
+ int[] pixelFormats;
+ int[] dataspaces;
+ }
SupportedBufferCombinations[] combinations;
}
diff --git a/libs/gui/include/gui/LayerMetadata.h b/libs/gui/include/gui/LayerMetadata.h
index e16f89c..9cf62bc 100644
--- a/libs/gui/include/gui/LayerMetadata.h
+++ b/libs/gui/include/gui/LayerMetadata.h
@@ -66,8 +66,9 @@
Standard = 1,
Performance = 2,
Battery = 3,
+ Custom = 4,
- ftl_last = Battery
+ ftl_last = Custom
};
} // namespace android::gui
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index ac74c8a..b01a3db 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -272,6 +272,7 @@
WindowInfoHandle(const WindowInfo& other);
inline const WindowInfo* getInfo() const { return &mInfo; }
+ inline WindowInfo* editInfo() { return &mInfo; }
sp<IBinder> getToken() const;
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp
index 0375915..54af7c9 100644
--- a/libs/jpegrecoverymap/Android.bp
+++ b/libs/jpegrecoverymap/Android.bp
@@ -31,6 +31,7 @@
srcs: [
"recoverymap.cpp",
"recoverymapmath.cpp",
+ "recoverymaputils.cpp",
],
shared_libs: [
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
index df24b10..5c9c8b6 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
@@ -61,6 +61,21 @@
* decompressImage().
*/
size_t getDecompressedImageHeight();
+ /*
+ * Returns the XMP data from the image.
+ */
+ void* getXMPPtr();
+ /*
+ * Returns the decompressed XMP buffer size. This method must be called only after
+ * calling decompressImage().
+ */
+ size_t getXMPSize();
+
+ bool getCompressedImageParameters(const void* image, int length,
+ size_t* pWidth, size_t* pHeight,
+ std::vector<uint8_t>* &iccData,
+ std::vector<uint8_t>* &exifData);
+
private:
bool decode(const void* image, int length);
// Returns false if errors occur.
@@ -72,6 +87,9 @@
static const int kCompressBatchSize = 16;
// The buffer that holds the decompressed result.
std::vector<JOCTET> mResultBuffer;
+ // The buffer that holds XMP Data.
+ std::vector<JOCTET> mXMPBuffer;
+
// Resolution of the decompressed image.
size_t mWidth;
size_t mHeight;
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
index 194cd2f..9f53a57 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
@@ -37,6 +37,7 @@
ERROR_JPEGR_INVALID_NULL_PTR = JPEGR_IO_ERROR_BASE - 2,
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,
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 b2ca481..5597303 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
@@ -22,11 +22,24 @@
namespace android::recoverymap {
typedef enum {
- JPEGR_COLORSPACE_UNSPECIFIED,
- JPEGR_COLORSPACE_BT709,
- JPEGR_COLORSPACE_P3,
- JPEGR_COLORSPACE_BT2100,
-} jpegr_color_space;
+ JPEGR_COLORGAMUT_UNSPECIFIED,
+ JPEGR_COLORGAMUT_BT709,
+ JPEGR_COLORGAMUT_P3,
+ JPEGR_COLORGAMUT_BT2100,
+} jpegr_color_gamut;
+
+// Transfer functions as defined for XMP metadata
+typedef enum {
+ JPEGR_TF_HLG = 0,
+ JPEGR_TF_PQ = 1,
+} jpegr_transfer_function;
+
+struct jpegr_info_struct {
+ size_t width;
+ size_t height;
+ std::vector<uint8_t>* iccData;
+ std::vector<uint8_t>* exifData;
+};
/*
* Holds information for uncompressed image or recovery map.
@@ -38,8 +51,8 @@
int width;
// Height of the recovery map or image in pixels.
int height;
- // Color space.
- jpegr_color_space colorSpace;
+ // Color gamut.
+ jpegr_color_gamut colorGamut;
};
/*
@@ -48,10 +61,12 @@
struct jpegr_compressed_struct {
// Pointer to the data location.
void* data;
- // Data length.
+ // Used data length in bytes.
int length;
- // Color space.
- jpegr_color_space colorSpace;
+ // Maximum available data length in bytes.
+ int maxLength;
+ // Color gamut.
+ jpegr_color_gamut colorGamut;
};
/*
@@ -64,9 +79,52 @@
int length;
};
+struct chromaticity_coord {
+ float x;
+ float y;
+};
+
+
+struct st2086_metadata {
+ // xy chromaticity coordinate of the red primary of the mastering display
+ chromaticity_coord redPrimary;
+ // xy chromaticity coordinate of the green primary of the mastering display
+ chromaticity_coord greenPrimary;
+ // xy chromaticity coordinate of the blue primary of the mastering display
+ chromaticity_coord bluePrimary;
+ // xy chromaticity coordinate of the white point of the mastering display
+ chromaticity_coord whitePoint;
+ // Maximum luminance in nits of the mastering display
+ uint32_t maxLuminance;
+ // Minimum luminance in nits of the mastering display
+ float minLuminance;
+};
+
+struct hdr10_metadata {
+ // Mastering display color volume
+ st2086_metadata st2086Metadata;
+ // Max frame average light level in nits
+ float maxFALL;
+ // Max content light level in nits
+ float maxCLL;
+};
+
+struct jpegr_metadata {
+ // JPEG/R version
+ uint32_t version;
+ // Range scaling factor for the map
+ float rangeScalingFactor;
+ // The transfer function for decoding the HDR representation of the image
+ jpegr_transfer_function transferFunction;
+ // HDR10 metadata, only applicable for transferFunction of JPEGR_TF_PQ
+ hdr10_metadata hdr10Metadata;
+};
+
typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr;
typedef struct jpegr_compressed_struct* jr_compressed_ptr;
typedef struct jpegr_exif_struct* jr_exif_ptr;
+typedef struct jpegr_metadata* jr_metadata_ptr;
+typedef struct jpegr_info_struct* jr_info_ptr;
class RecoveryMap {
public:
@@ -75,9 +133,10 @@
*
* Generate recovery map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append
* the recovery map to the end of the compressed JPEG. HDR and SDR inputs must be the same
- * resolution and color space.
+ * resolution.
* @param uncompressed_p010_image uncompressed HDR image in P010 color format
* @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+ * @param hdr_tf transfer function of the HDR image
* @param dest destination of the compressed JPEGR image
* @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
* the highest quality
@@ -86,6 +145,7 @@
*/
status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
jr_uncompressed_ptr uncompressed_yuv_420_image,
+ jpegr_transfer_function hdr_tf,
jr_compressed_ptr dest,
int quality,
jr_exif_ptr exif);
@@ -100,12 +160,14 @@
* @param uncompressed_p010_image uncompressed HDR image in P010 color format
* @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
* @param compressed_jpeg_image compressed 8-bit JPEG image
+ * @param hdr_tf transfer function of the HDR image
* @param dest destination of the compressed JPEGR image
* @return NO_ERROR if encoding succeeds, error code if error occurs.
*/
status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
jr_uncompressed_ptr uncompressed_yuv_420_image,
jr_compressed_ptr compressed_jpeg_image,
+ jpegr_transfer_function hdr_tf,
jr_compressed_ptr dest);
/*
@@ -115,27 +177,28 @@
*
* Decode the compressed 8-bit JPEG image to YUV SDR, generate recovery map from the HDR input
* and the decoded SDR result, append the recovery map to the end of the compressed JPEG. HDR
- * and SDR inputs must be the same resolution and color space.
+ * and SDR inputs must be the same resolution.
* @param uncompressed_p010_image uncompressed HDR image in P010 color format
* @param compressed_jpeg_image compressed 8-bit JPEG image
+ * @param hdr_tf transfer function of the HDR image
* @param dest destination of the compressed JPEGR image
* @return NO_ERROR if encoding succeeds, error code if error occurs.
*/
status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
jr_compressed_ptr compressed_jpeg_image,
+ jpegr_transfer_function hdr_tf,
jr_compressed_ptr dest);
/*
* Decompress JPEGR image.
*
+ * The output JPEGR image is in RGBA_1010102 data format if decoding to HDR.
* @param compressed_jpegr_image compressed JPEGR image
* @param dest destination of the uncompressed JPEGR image
- * @param exif destination of the decoded EXIF metadata. Default value is nullptr where EXIF
- * metadata will not be decoded.
- * @param request_sdr flag that request SDR output, default to false (request HDR output). If
- * set to true, decoder will only decode the primary image which is SDR.
- * Setting of request_sdr and input source (HDR or SDR) can be found in
- * the table below:
+ * @param exif destination of the decoded EXIF metadata.
+ * @param request_sdr flag that request SDR output. If set to true, decoder will only decode
+ * the primary image which is SDR. Setting of request_sdr and input source
+ * (HDR or SDR) can be found in the table below:
* | input source | request_sdr | output of decoding |
* | HDR | true | SDR |
* | HDR | false | HDR |
@@ -147,6 +210,17 @@
jr_uncompressed_ptr dest,
jr_exif_ptr exif = nullptr,
bool request_sdr = false);
+
+ /*
+ * Gets Info from JPEGR file without decoding it.
+ *
+ * The output is filled jpegr_info structure
+ * @param compressed_jpegr_image compressed JPEGR image
+ * @param jpegr_info pointer to output JPEGR info
+ * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise
+ */
+ status_t getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
+ jr_info_ptr jpegr_info);
private:
/*
* This method is called in the decoding pipeline. It will decode the recovery map.
@@ -176,29 +250,46 @@
* @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
* @param uncompressed_p010_image uncompressed HDR image in P010 color format
* @param dest recovery map; caller responsible for memory of data
- * @param hdr_ratio HDR ratio will be updated in this method
+ * @param metadata metadata provides the transfer function for the HDR
+ * image; range_scaling_factor and hdr10 FALL and CLL will
+ * be updated.
* @return NO_ERROR if calculation succeeds, error code if error occurs.
*/
status_t generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
jr_uncompressed_ptr uncompressed_p010_image,
- jr_uncompressed_ptr dest,
- float &hdr_ratio);
+ jr_metadata_ptr metadata,
+ jr_uncompressed_ptr dest);
/*
* This method is called in the decoding pipeline. It will take the uncompressed (decoded)
- * 8-bit yuv image and the uncompressed (decoded) recovery map as input, and calculate the
- * 10-bit recovered image (in p010 color format).
+ * 8-bit yuv image, the uncompressed (decoded) recovery map, and extracted JPEG/R metadata as
+ * input, and calculate the 10-bit recovered image. The recovered output image is the same
+ * color gamut as the SDR image, with the transfer function specified in the JPEG/R metadata,
+ * and is in RGBA1010102 data format.
*
* @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
* @param uncompressed_recovery_map uncompressed recovery map
+ * @param metadata JPEG/R metadata extracted from XMP.
* @param dest reconstructed HDR image
* @return NO_ERROR if calculation succeeds, error code if error occurs.
*/
status_t applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
jr_uncompressed_ptr uncompressed_recovery_map,
+ jr_metadata_ptr metadata,
jr_uncompressed_ptr dest);
/*
+ * This methoud is called to separate primary image and recovery map image from JPEGR
+ *
+ * @param compressed_jpegr_image compressed JPEGR image
+ * @param primary_image destination of primary image
+ * @param recovery_map destination of compressed recovery map
+ * @return NO_ERROR if calculation succeeds, error code if error occurs.
+ */
+ status_t extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
+ jr_compressed_ptr primary_image,
+ jr_compressed_ptr recovery_map);
+ /*
* This method is called in the decoding pipeline. It will read XMP metadata to find the start
* position of the compressed recovery map, and will extract the compressed recovery map.
*
@@ -206,7 +297,8 @@
* @param dest destination of compressed recovery map
* @return NO_ERROR if calculation succeeds, error code if error occurs.
*/
- status_t extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image, jr_compressed_ptr dest);
+ status_t extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
+ jr_compressed_ptr dest);
/*
* This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image
@@ -215,13 +307,13 @@
*
* @param compressed_jpeg_image compressed 8-bit JPEG image
* @param compress_recovery_map compressed recover map
- * @param hdr_ratio HDR ratio
+ * @param metadata JPEG/R metadata to encode in XMP of the jpeg
* @param dest compressed JPEGR image
* @return NO_ERROR if calculation succeeds, error code if error occurs.
*/
status_t appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
jr_compressed_ptr compressed_recovery_map,
- float hdr_ratio,
+ jr_metadata_ptr metadata,
jr_compressed_ptr dest);
/*
@@ -229,7 +321,7 @@
*
* below is an example of the XMP metadata that this function generates where
* secondary_image_length = 1000
- * hdr_ratio = 1.25
+ * range_scaling_factor = 1.25
*
* <x:xmpmeta
* xmlns:x="adobe:ns:meta/"
@@ -239,7 +331,7 @@
* <rdf:Description
* xmlns:GContainer="http://ns.google.com/photos/1.0/container/">
* <GContainer:Version>1</GContainer:Version>
- * <GContainer:HdrRatio>1.25</GContainer:HdrRatio>
+ * <GContainer:RangeScalingFactor>1.25</GContainer:RangeScalingFactor>
* <GContainer:Directory>
* <rdf:Seq>
* <rdf:li>
@@ -260,10 +352,10 @@
* </x:xmpmeta>
*
* @param secondary_image_length length of secondary image
- * @param hdr_ratio hdr ratio
+ * @param metadata JPEG/R metadata to encode as XMP
* @return XMP metadata in type of string
*/
- std::string generateXmp(int secondary_image_length, float hdr_ratio);
+ std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata);
};
} // namespace android::recoverymap
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
index e76bc20..fe7a651 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
@@ -43,6 +43,9 @@
};
};
+typedef Color (*ColorTransformFn)(Color);
+typedef float (*ColorCalculationFn)(Color);
+
inline Color operator+=(Color& lhs, const Color& rhs) {
lhs.r += rhs.r;
lhs.g += rhs.g;
@@ -138,7 +141,10 @@
////////////////////////////////////////////////////////////////////////////////
// Display-P3 transformations
-
+/*
+ * Calculated the luminance of a linear RGB P3 pixel, according to EG 432-1.
+ */
+float p3Luminance(Color e);
////////////////////////////////////////////////////////////////////////////////
@@ -169,10 +175,43 @@
*/
Color hlgInvOetf(Color e_gamma);
+/*
+ * Convert from scene luminance in nits to PQ.
+ */
+Color pqOetf(Color e);
+
+/*
+ * Convert from PQ to scene luminance in nits.
+ */
+Color pqInvOetf(Color e_gamma);
+
+
////////////////////////////////////////////////////////////////////////////////
// Color space conversions
+/*
+ * Convert between color spaces with linear RGB data, according to ITU-R BT.2407 and EG 432-1.
+ *
+ * All conversions are derived from multiplying the matrix for XYZ to output RGB color gamut by the
+ * matrix for input RGB color gamut to XYZ. The matrix for converting from XYZ to an RGB gamut is
+ * always the inverse of the RGB gamut to XYZ matrix.
+ */
+Color bt709ToP3(Color e);
+Color bt709ToBt2100(Color e);
+Color p3ToBt709(Color e);
+Color p3ToBt2100(Color e);
+Color bt2100ToBt709(Color e);
+Color bt2100ToP3(Color e);
+/*
+ * Identity conversion.
+ */
+inline Color identityConversion(Color e) { return e; }
+
+/*
+ * Get the conversion to apply to the HDR image for recovery map generation
+ */
+ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gamut hdr_gamut);
////////////////////////////////////////////////////////////////////////////////
@@ -220,6 +259,13 @@
*/
Color sampleP010(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
+/*
+ * Convert from Color to RGBA1010102.
+ *
+ * Alpha always set to 1.0.
+ */
+uint32_t colorToRgba1010102(Color e_gamma);
+
} // namespace android::recoverymap
#endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
new file mode 100644
index 0000000..e35f2d7
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
+#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
+
+#include <stdint.h>
+#include <cstdio>
+
+
+namespace android::recoverymap {
+
+struct jpegr_metadata;
+
+/*
+ * Parses XMP packet and fills metadata with data from XMP
+ *
+ * @param xmp_data pointer to XMP packet
+ * @param xmp_size size of XMP packet
+ * @param metadata place to store HDR metadata values
+ * @return true if metadata is successfully retrieved, false otherwise
+*/
+bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata);
+
+}
+
+#endif //ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoder.cpp
index c1fb6c3..0185e55 100644
--- a/libs/jpegrecoverymap/jpegdecoder.cpp
+++ b/libs/jpegrecoverymap/jpegdecoder.cpp
@@ -20,8 +20,15 @@
#include <errno.h>
#include <setjmp.h>
+#include <string>
+
+using namespace std;
namespace android::recoverymap {
+
+const uint32_t kExifMarker = JPEG_APP0 + 1;
+const uint32_t kICCMarker = JPEG_APP0 + 2;
+
struct jpegr_source_mgr : jpeg_source_mgr {
jpegr_source_mgr(const uint8_t* ptr, int len);
~jpegr_source_mgr();
@@ -88,6 +95,7 @@
}
mResultBuffer.clear();
+ mXMPBuffer.clear();
if (!decode(image, length)) {
return false;
}
@@ -103,6 +111,15 @@
return mResultBuffer.size();
}
+void* JpegDecoder::getXMPPtr() {
+ return mXMPBuffer.data();
+}
+
+size_t JpegDecoder::getXMPSize() {
+ return mXMPBuffer.size();
+}
+
+
size_t JpegDecoder::getDecompressedImageWidth() {
return mWidth;
}
@@ -115,6 +132,8 @@
jpeg_decompress_struct cinfo;
jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
jpegrerror_mgr myerr;
+ string nameSpace = "http://ns.adobe.com/xap/1.0/";
+
cinfo.err = jpeg_std_error(&myerr.pub);
myerr.pub.error_exit = jpegrerror_exit;
@@ -124,9 +143,26 @@
}
jpeg_create_decompress(&cinfo);
+ jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF);
+
cinfo.src = &mgr;
jpeg_read_header(&cinfo, TRUE);
+ // Save XMP Data
+ for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) {
+ if (marker->marker == kExifMarker) {
+ const unsigned int len = marker->data_length;
+ if (len > nameSpace.size() &&
+ !strncmp(reinterpret_cast<const char*>(marker->data),
+ nameSpace.c_str(), nameSpace.size())) {
+ mXMPBuffer.resize(len+1, 0);
+ memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len);
+ break;
+ }
+ }
+ }
+
+
mWidth = cinfo.image_width;
mHeight = cinfo.image_height;
@@ -161,6 +197,43 @@
return decompressYUV(cinfo, dest);
}
+bool JpegDecoder::getCompressedImageParameters(const void* image, int length,
+ size_t *pWidth, size_t *pHeight,
+ std::vector<uint8_t> *&iccData , std::vector<uint8_t> *&exifData) {
+ jpeg_decompress_struct cinfo;
+ jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
+ jpegrerror_mgr myerr;
+ cinfo.err = jpeg_std_error(&myerr.pub);
+ myerr.pub.error_exit = jpegrerror_exit;
+
+ if (setjmp(myerr.setjmp_buffer)) {
+ jpeg_destroy_decompress(&cinfo);
+ return false;
+ }
+ jpeg_create_decompress(&cinfo);
+
+ jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF);
+ jpeg_save_markers(&cinfo, kICCMarker, 0xFFFF);
+
+ cinfo.src = &mgr;
+ if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
+ jpeg_destroy_decompress(&cinfo);
+ return false;
+ }
+
+ *pWidth = cinfo.image_width;
+ *pHeight = cinfo.image_height;
+
+ //TODO: Parse iccProfile and exifData
+ (void)iccData;
+ (void)exifData;
+
+
+ jpeg_destroy_decompress(&cinfo);
+ return true;
+}
+
+
bool JpegDecoder::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
JSAMPROW y[kCompressBatchSize];
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index 86eb8a8..4a209ec 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -14,24 +14,27 @@
* limitations under the License.
*/
-// TODO: need to clean up handling around hdr_ratio and passing it around
-// TODO: need to handle color space information; currently we assume everything
-// is srgb in.
-// TODO: handle PQ encode/decode (currently only HLG)
-
#include <jpegrecoverymap/recoverymap.h>
#include <jpegrecoverymap/jpegencoder.h>
#include <jpegrecoverymap/jpegdecoder.h>
#include <jpegrecoverymap/recoverymapmath.h>
+#include <jpegrecoverymap/recoverymaputils.h>
#include <image_io/jpeg/jpeg_marker.h>
#include <image_io/xml/xml_writer.h>
+#include <image_io/jpeg/jpeg_info.h>
+#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>
using namespace std;
+using namespace photos_editing_formats::image_io;
namespace android::recoverymap {
@@ -43,9 +46,23 @@
} \
}
+// The current JPEGR version that we encode to
+static const uint32_t kJpegrVersion = 1;
+
// Map is quarter res / sixteenth size
static const size_t kMapDimensionScaleFactor = 4;
+// JPEG compress quality (0 ~ 100) for recovery map
+static const int kMapCompressQuality = 85;
+// TODO: fill in st2086 metadata
+static const st2086_metadata kSt2086Metadata = {
+ {0.0f, 0.0f},
+ {0.0f, 0.0f},
+ {0.0f, 0.0f},
+ {0.0f, 0.0f},
+ 0,
+ 1.0f,
+};
/*
* Helper function used for generating XMP metadata.
@@ -70,7 +87,7 @@
* @return status of succeed or error code.
*/
status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
- if (position + length > destination->length) {
+ if (position + length > destination->maxLength) {
return ERROR_JPEGR_BUFFER_TOO_SMALL;
}
@@ -81,6 +98,7 @@
status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
jr_uncompressed_ptr uncompressed_yuv_420_image,
+ jpegr_transfer_function hdr_tf,
jr_compressed_ptr dest,
int quality,
jr_exif_ptr /* exif */) {
@@ -99,21 +117,27 @@
return ERROR_JPEGR_RESOLUTION_MISMATCH;
}
+ jpegr_metadata metadata;
+ metadata.version = kJpegrVersion;
+ metadata.transferFunction = hdr_tf;
+ if (hdr_tf == JPEGR_TF_PQ) {
+ metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
+ }
+
jpegr_uncompressed_struct map;
- float hdr_ratio = 0.0f;
JPEGR_CHECK(generateRecoveryMap(
- uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio));
+ uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
std::unique_ptr<uint8_t[]> map_data;
map_data.reset(reinterpret_cast<uint8_t*>(map.data));
jpegr_compressed_struct compressed_map;
- std::unique_ptr<uint8_t[]> compressed_map_data =
- std::make_unique<uint8_t[]>(map.width * map.height);
+ compressed_map.maxLength = map.width * map.height;
+ unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
compressed_map.data = compressed_map_data.get();
JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
JpegEncoder jpeg_encoder;
- // TODO: ICC data - need color space information
+ // TODO: determine ICC data based on color gamut information
if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
uncompressed_yuv_420_image->width,
uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
@@ -123,7 +147,7 @@
jpeg.data = jpeg_encoder.getCompressedImagePtr();
jpeg.length = jpeg_encoder.getCompressedImageSize();
- JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, hdr_ratio, dest));
+ JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest));
return NO_ERROR;
}
@@ -131,6 +155,7 @@
status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
jr_uncompressed_ptr uncompressed_yuv_420_image,
jr_compressed_ptr compressed_jpeg_image,
+ jpegr_transfer_function hdr_tf,
jr_compressed_ptr dest) {
if (uncompressed_p010_image == nullptr
|| uncompressed_yuv_420_image == nullptr
@@ -144,26 +169,33 @@
return ERROR_JPEGR_RESOLUTION_MISMATCH;
}
+ jpegr_metadata metadata;
+ metadata.version = kJpegrVersion;
+ metadata.transferFunction = hdr_tf;
+ if (hdr_tf == JPEGR_TF_PQ) {
+ metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
+ }
+
jpegr_uncompressed_struct map;
- float hdr_ratio = 0.0f;
JPEGR_CHECK(generateRecoveryMap(
- uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio));
+ uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
std::unique_ptr<uint8_t[]> map_data;
map_data.reset(reinterpret_cast<uint8_t*>(map.data));
jpegr_compressed_struct compressed_map;
- std::unique_ptr<uint8_t[]> compressed_map_data =
- std::make_unique<uint8_t[]>(map.width * map.height);
+ compressed_map.maxLength = map.width * map.height;
+ unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
compressed_map.data = compressed_map_data.get();
JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
- JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, hdr_ratio, dest));
+ JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
return NO_ERROR;
}
status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
jr_compressed_ptr compressed_jpeg_image,
+ jpegr_transfer_function hdr_tf,
jr_compressed_ptr dest) {
if (uncompressed_p010_image == nullptr
|| compressed_jpeg_image == nullptr
@@ -179,39 +211,71 @@
uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
+ uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
|| uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
return ERROR_JPEGR_RESOLUTION_MISMATCH;
}
+ jpegr_metadata metadata;
+ metadata.version = kJpegrVersion;
+ metadata.transferFunction = hdr_tf;
+ if (hdr_tf == JPEGR_TF_PQ) {
+ metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
+ }
+
jpegr_uncompressed_struct map;
- float hdr_ratio = 0.0f;
JPEGR_CHECK(generateRecoveryMap(
- &uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio));
+ &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
std::unique_ptr<uint8_t[]> map_data;
map_data.reset(reinterpret_cast<uint8_t*>(map.data));
jpegr_compressed_struct compressed_map;
- std::unique_ptr<uint8_t[]> compressed_map_data =
- std::make_unique<uint8_t[]>(map.width * map.height);
+ compressed_map.maxLength = map.width * map.height;
+ unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
compressed_map.data = compressed_map_data.get();
JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
- JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, hdr_ratio, dest));
+ JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
return NO_ERROR;
}
+status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
+ jr_info_ptr jpegr_info) {
+ if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) {
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
+ jpegr_compressed_struct primary_image, recovery_map;
+ JPEGR_CHECK(extractPrimaryImageAndRecoveryMap(compressed_jpegr_image,
+ &primary_image, &recovery_map));
+
+ JpegDecoder jpeg_decoder;
+ if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
+ &jpegr_info->width, &jpegr_info->height,
+ jpegr_info->iccData, jpegr_info->exifData)) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+
status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
jr_uncompressed_ptr dest,
- jr_exif_ptr /* exif */,
- bool /* request_sdr */) {
+ jr_exif_ptr exif,
+ bool request_sdr) {
if (compressed_jpegr_image == nullptr || dest == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
+ // TODO: fill EXIF data
+ (void) exif;
+
jpegr_compressed_struct compressed_map;
+ jpegr_metadata metadata;
JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
jpegr_uncompressed_struct map;
@@ -227,7 +291,19 @@
uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
- JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, dest));
+ if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()),
+ jpeg_decoder.getXMPSize(), &metadata)) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+
+ if (request_sdr) {
+ memcpy(dest->data, uncompressed_yuv_420_image.data,
+ uncompressed_yuv_420_image.width*uncompressed_yuv_420_image.height *3 / 2);
+ dest->width = uncompressed_yuv_420_image.width;
+ dest->height = uncompressed_yuv_420_image.height;
+ } else {
+ JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
+ }
return NO_ERROR;
}
@@ -257,30 +333,36 @@
return ERROR_JPEGR_INVALID_NULL_PTR;
}
- // TODO: should we have ICC data?
+ // TODO: should we have ICC data for the map?
JpegEncoder jpeg_encoder;
- if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data, uncompressed_recovery_map->width,
- uncompressed_recovery_map->height, 85, nullptr, 0,
+ if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data,
+ uncompressed_recovery_map->width,
+ uncompressed_recovery_map->height,
+ kMapCompressQuality,
+ nullptr,
+ 0,
true /* isSingleChannel */)) {
return ERROR_JPEGR_ENCODE_ERROR;
}
- if (dest->length < jpeg_encoder.getCompressedImageSize()) {
+ if (dest->maxLength < jpeg_encoder.getCompressedImageSize()) {
return ERROR_JPEGR_BUFFER_TOO_SMALL;
}
memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
dest->length = jpeg_encoder.getCompressedImageSize();
+ dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
return NO_ERROR;
}
status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
jr_uncompressed_ptr uncompressed_p010_image,
- jr_uncompressed_ptr dest,
- float &hdr_ratio) {
+ jr_metadata_ptr metadata,
+ jr_uncompressed_ptr dest) {
if (uncompressed_yuv_420_image == nullptr
|| uncompressed_p010_image == nullptr
+ || metadata == nullptr
|| dest == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
@@ -290,6 +372,11 @@
return ERROR_JPEGR_RESOLUTION_MISMATCH;
}
+ if (uncompressed_yuv_420_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED
+ || uncompressed_p010_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED) {
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+
size_t image_width = uncompressed_yuv_420_image->width;
size_t image_height = uncompressed_yuv_420_image->height;
size_t map_width = image_width / kMapDimensionScaleFactor;
@@ -297,25 +384,63 @@
dest->width = map_width;
dest->height = map_height;
+ dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
dest->data = new uint8_t[map_width * map_height];
std::unique_ptr<uint8_t[]> map_data;
map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
+ ColorTransformFn hdrInvOetf = nullptr;
+ switch (metadata->transferFunction) {
+ case JPEGR_TF_HLG:
+ hdrInvOetf = hlgInvOetf;
+ break;
+ case JPEGR_TF_PQ:
+ hdrInvOetf = pqInvOetf;
+ break;
+ }
+
+ ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
+ uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
+
+ ColorCalculationFn luminanceFn = nullptr;
+ switch (uncompressed_yuv_420_image->colorGamut) {
+ case JPEGR_COLORGAMUT_BT709:
+ luminanceFn = srgbLuminance;
+ break;
+ case JPEGR_COLORGAMUT_P3:
+ luminanceFn = p3Luminance;
+ break;
+ case JPEGR_COLORGAMUT_BT2100:
+ luminanceFn = bt2100Luminance;
+ break;
+ case JPEGR_COLORGAMUT_UNSPECIFIED:
+ // Should be impossible to hit after input validation.
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+
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 = hlgInvOetf(hdr_rgb_gamma);
- float hdr_y_nits = bt2100Luminance(hdr_rgb);
+ Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
+ hdr_rgb = hdrGamutConversionFn(hdr_rgb);
+ float hdr_y_nits = luminanceFn(hdr_rgb);
+ hdr_y_nits_avg += hdr_y_nits;
if (hdr_y_nits > hdr_y_nits_max) {
hdr_y_nits_max = hdr_y_nits;
}
}
}
+ hdr_y_nits_avg /= image_width * image_height;
- hdr_ratio = hdr_y_nits_max / kSdrWhiteNits;
+ metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits;
+ if (metadata->transferFunction == JPEGR_TF_PQ) {
+ metadata->hdr10Metadata.maxFALL = hdr_y_nits_avg;
+ 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) {
@@ -323,16 +448,17 @@
kMapDimensionScaleFactor, x, y);
Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
- float sdr_y_nits = srgbLuminance(sdr_rgb);
+ float sdr_y_nits = luminanceFn(sdr_rgb);
Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
- Color hdr_rgb = hlgInvOetf(hdr_rgb_gamma);
- float hdr_y_nits = bt2100Luminance(hdr_rgb);
+ Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
+ hdr_rgb = hdrGamutConversionFn(hdr_rgb);
+ float hdr_y_nits = luminanceFn(hdr_rgb);
size_t pixel_idx = x + y * map_width;
reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
- encodeRecovery(sdr_y_nits, hdr_y_nits, hdr_ratio);
+ encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
}
}
@@ -342,17 +468,15 @@
status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
jr_uncompressed_ptr uncompressed_recovery_map,
+ jr_metadata_ptr metadata,
jr_uncompressed_ptr dest) {
if (uncompressed_yuv_420_image == nullptr
|| uncompressed_recovery_map == nullptr
+ || metadata == nullptr
|| dest == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
- // TODO: need to get this from the XMP; should probably be a function
- // parameter
- float hdr_ratio = 4.0f;
-
size_t width = uncompressed_yuv_420_image->width;
size_t height = uncompressed_yuv_420_image->height;
@@ -360,61 +484,117 @@
dest->height = height;
size_t pixel_count = width * height;
+ ColorTransformFn hdrOetf = nullptr;
+ switch (metadata->transferFunction) {
+ 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) {
- size_t pixel_y_idx = x + y * width;
+ 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);
- size_t pixel_uv_idx = x / 2 + (y / 2) * (width / 2);
-
- Color ypuv_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
- Color rgbp_sdr = srgbYuvToRgb(ypuv_sdr);
- Color rgb_sdr = srgbInvOetf(rgbp_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, hdr_ratio);
+ Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata->rangeScalingFactor);
- Color rgbp_hdr = hlgOetf(rgb_hdr);
- // TODO: actually just leave in RGB and convert to RGBA1010102 instead.
- Color ypuv_hdr = srgbRgbToYuv(rgbp_hdr);
+ Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
+ uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
- reinterpret_cast<uint16_t*>(dest->data)[pixel_y_idx] = ypuv_hdr.r;
- reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx] = ypuv_hdr.g;
- reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx + 1] = ypuv_hdr.b;
+ size_t pixel_idx = x + y * width;
+ reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
}
}
+ return NO_ERROR;
+}
+
+status_t RecoveryMap::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
+ jr_compressed_ptr primary_image,
+ jr_compressed_ptr recovery_map) {
+ if (compressed_jpegr_image == nullptr) {
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
+ MessageHandler msg_handler;
+ std::shared_ptr<DataSegment> seg =
+ DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
+ static_cast<const uint8_t*>(compressed_jpegr_image->data),
+ DataSegment::BufferDispositionPolicy::kDontDelete);
+ DataSegmentDataSource data_source(seg);
+ JpegInfoBuilder jpeg_info_builder;
+ jpeg_info_builder.SetImageLimit(2);
+ JpegScanner jpeg_scanner(&msg_handler);
+ jpeg_scanner.Run(&data_source, &jpeg_info_builder);
+ data_source.Reset();
+
+ if (jpeg_scanner.HasError()) {
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ const auto& jpeg_info = jpeg_info_builder.GetInfo();
+ const auto& image_ranges = jpeg_info.GetImageRanges();
+ if (image_ranges.empty()) {
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ if (image_ranges.size() != 2) {
+ // Must be 2 JPEG Images
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ if (primary_image != nullptr) {
+ primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
+ image_ranges[0].GetBegin();
+ primary_image->length = image_ranges[0].GetLength();
+ }
+
+ if (recovery_map != nullptr) {
+ recovery_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
+ image_ranges[1].GetBegin();
+ recovery_map->length = image_ranges[1].GetLength();
+ }
return NO_ERROR;
}
+
status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
jr_compressed_ptr dest) {
if (compressed_jpegr_image == nullptr || dest == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
- // TBD
- return NO_ERROR;
+ return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest);
}
status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
jr_compressed_ptr compressed_recovery_map,
- float hdr_ratio,
+ jr_metadata_ptr metadata,
jr_compressed_ptr dest) {
if (compressed_jpeg_image == nullptr
|| compressed_recovery_map == nullptr
+ || metadata == nullptr
|| dest == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
- string xmp = generateXmp(compressed_recovery_map->length, hdr_ratio);
- string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
+ const string xmp = generateXmp(compressed_recovery_map->length, *metadata);
+ const string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
+ const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
// 2 bytes: APP1 sign (ff e1)
- // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0"
+ // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0",
// x bytes: length of xmp packet
- int length = 2 + nameSpace.size() + xmp.size();
- uint8_t lengthH = ((length >> 8) & 0xff);
- uint8_t lengthL = (length & 0xff);
+
+ const int length = 3 + nameSpaceLength + xmp.size();
+ const uint8_t lengthH = ((length >> 8) & 0xff);
+ const uint8_t lengthL = (length & 0xff);
int pos = 0;
@@ -432,7 +612,7 @@
JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
- JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpace.size(), pos));
+ JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
JPEGR_CHECK(Write(dest,
(uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
@@ -442,7 +622,7 @@
return NO_ERROR;
}
-string RecoveryMap::generateXmp(int secondary_image_length, float hdr_ratio) {
+string RecoveryMap::generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
const string kContainerPrefix = "GContainer";
const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
const string kItemPrefix = "Item";
@@ -455,7 +635,6 @@
const string kPrimary = "Primary";
const string kSemantic = "Semantic";
const string kVersion = "Version";
- const int kVersionValue = 1;
const string kConDir = Name(kContainerPrefix, kDirectory);
const string kContainerItem = Name(kContainerPrefix, kItem);
@@ -475,8 +654,11 @@
writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
writer.StartWritingElement("rdf:Description");
writer.WriteXmlns(kContainerPrefix, kContainerUri);
- writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), kVersionValue);
- writer.WriteElementAndContent(Name(kContainerPrefix, "HdrRatio"), hdr_ratio);
+ writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), metadata.version);
+ writer.WriteElementAndContent(Name(kContainerPrefix, "rangeScalingFactor"),
+ metadata.rangeScalingFactor);
+ // TODO: determine structure for hdr10 metadata
+ // TODO: write rest of metadata
writer.StartWritingElements(kConDirSeq);
size_t item_depth = writer.StartWritingElements(kLiItem);
writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);
diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp
index 0d3319f..6dcbca3 100644
--- a/libs/jpegrecoverymap/recoverymapmath.cpp
+++ b/libs/jpegrecoverymap/recoverymapmath.cpp
@@ -64,6 +64,11 @@
////////////////////////////////////////////////////////////////////////////////
// Display-P3 transformations
+static const float kP3R = 0.22897f, kP3G = 0.69174f, kP3B = 0.07929f;
+
+float p3Luminance(Color e) {
+ return kP3R * e.r + kP3G * e.g + kP3B * e.b;
+}
////////////////////////////////////////////////////////////////////////////////
@@ -128,7 +133,7 @@
return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}};
}
-float hlgInvOetf(float e_gamma) {
+static float hlgInvOetf(float e_gamma) {
if (e_gamma <= 0.5f) {
return pow(e_gamma, 2.0f) / 3.0f;
} else {
@@ -142,10 +147,118 @@
hlgInvOetf(e_gamma.b) }}};
}
+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,
+ kPqC3 = 2392.0f / 4096.0f * 32.0f;
+
+static float pqOetf(float e) {
+ if (e < 0.0f) e = 0.0f;
+ return pow((kPqC1 + kPqC2 * pow(e / 10000.0f, kPqM1)) / (1 + kPqC3 * pow(e / 10000.0f, kPqM1)),
+ kPqM2);
+}
+
+Color pqOetf(Color e) {
+ return {{{ pqOetf(e.r), pqOetf(e.g), pqOetf(e.b) }}};
+}
+
+static float pqInvOetf(float e_gamma) {
+ static const float kPqInvOetfCoef = log2(-(pow(kPqM1, 1.0f / kPqM2) - kPqC1)
+ / (kPqC3 * pow(kPqM1, 1.0f / kPqM2) - kPqC2));
+ return kPqInvOetfCoef / log2(e_gamma * 10000.0f);
+}
+
+Color pqInvOetf(Color e_gamma) {
+ return {{{ pqInvOetf(e_gamma.r),
+ pqInvOetf(e_gamma.g),
+ pqInvOetf(e_gamma.b) }}};
+}
+
////////////////////////////////////////////////////////////////////////////////
// Color conversions
+Color bt709ToP3(Color e) {
+ return {{{ 0.82254f * e.r + 0.17755f * e.g + 0.00006f * e.b,
+ 0.03312f * e.r + 0.96684f * e.g + -0.00001f * e.b,
+ 0.01706f * e.r + 0.07240f * e.g + 0.91049f * e.b }}};
+}
+
+Color bt709ToBt2100(Color e) {
+ return {{{ 0.62740f * e.r + 0.32930f * e.g + 0.04332f * e.b,
+ 0.06904f * e.r + 0.91958f * e.g + 0.01138f * e.b,
+ 0.01636f * e.r + 0.08799f * e.g + 0.89555f * e.b }}};
+}
+
+Color p3ToBt709(Color e) {
+ return {{{ 1.22482f * e.r + -0.22490f * e.g + -0.00007f * e.b,
+ -0.04196f * e.r + 1.04199f * e.g + 0.00001f * e.b,
+ -0.01961f * e.r + -0.07865f * e.g + 1.09831f * e.b }}};
+}
+
+Color p3ToBt2100(Color e) {
+ return {{{ 0.75378f * e.r + 0.19862f * e.g + 0.04754f * e.b,
+ 0.04576f * e.r + 0.94177f * e.g + 0.01250f * e.b,
+ -0.00121f * e.r + 0.01757f * e.g + 0.98359f * e.b }}};
+}
+
+Color bt2100ToBt709(Color e) {
+ return {{{ 1.66045f * e.r + -0.58764f * e.g + -0.07286f * e.b,
+ -0.12445f * e.r + 1.13282f * e.g + -0.00837f * e.b,
+ -0.01811f * e.r + -0.10057f * e.g + 1.11878f * e.b }}};
+}
+
+Color bt2100ToP3(Color e) {
+ return {{{ 1.34369f * e.r + -0.28223f * e.g + -0.06135f * e.b,
+ -0.06533f * e.r + 1.07580f * e.g + -0.01051f * e.b,
+ 0.00283f * e.r + -0.01957f * e.g + 1.01679f * e.b
+ }}};
+}
+
+// TODO: confirm we always want to convert like this before calculating
+// luminance.
+ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gamut hdr_gamut) {
+ switch (sdr_gamut) {
+ case JPEGR_COLORGAMUT_BT709:
+ switch (hdr_gamut) {
+ case JPEGR_COLORGAMUT_BT709:
+ return identityConversion;
+ case JPEGR_COLORGAMUT_P3:
+ return p3ToBt709;
+ case JPEGR_COLORGAMUT_BT2100:
+ return bt2100ToBt709;
+ case JPEGR_COLORGAMUT_UNSPECIFIED:
+ return nullptr;
+ }
+ break;
+ case JPEGR_COLORGAMUT_P3:
+ switch (hdr_gamut) {
+ case JPEGR_COLORGAMUT_BT709:
+ return bt709ToP3;
+ case JPEGR_COLORGAMUT_P3:
+ return identityConversion;
+ case JPEGR_COLORGAMUT_BT2100:
+ return bt2100ToP3;
+ case JPEGR_COLORGAMUT_UNSPECIFIED:
+ return nullptr;
+ }
+ break;
+ case JPEGR_COLORGAMUT_BT2100:
+ switch (hdr_gamut) {
+ case JPEGR_COLORGAMUT_BT709:
+ return bt709ToBt2100;
+ case JPEGR_COLORGAMUT_P3:
+ return p3ToBt2100;
+ case JPEGR_COLORGAMUT_BT2100:
+ return identityConversion;
+ case JPEGR_COLORGAMUT_UNSPECIFIED:
+ return nullptr;
+ }
+ break;
+ case JPEGR_COLORGAMUT_UNSPECIFIED:
+ return nullptr;
+ }
+}
+
////////////////////////////////////////////////////////////////////////////////
// Recovery map calculations
@@ -175,6 +288,10 @@
// TODO: do we need something more clever for filtering either the map or images
// to generate the map?
+static size_t clamp(const size_t& val, const size_t& low, const size_t& high) {
+ return val < low ? low : (high < val ? high : val);
+}
+
static float mapUintToFloat(uint8_t map_uint) {
return (static_cast<float>(map_uint) - 127.5f) / 127.5f;
}
@@ -188,6 +305,11 @@
size_t y_lower = static_cast<size_t>(floor(y_map));
size_t y_upper = y_lower + 1;
+ x_lower = clamp(x_lower, 0, map->width - 1);
+ x_upper = clamp(x_upper, 0, map->width - 1);
+ y_lower = clamp(y_lower, 0, map->height - 1);
+ y_upper = clamp(y_upper, 0, map->height - 1);
+
float x_influence = x_map - static_cast<float>(x_lower);
float y_influence = y_map - static_cast<float>(y_lower);
@@ -225,9 +347,12 @@
size_t pixel_y_idx = x + y * image->width;
size_t pixel_uv_idx = x / 2 + (y / 2) * (image->width / 2);
- uint16_t y_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_y_idx];
- uint16_t u_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_count + pixel_uv_idx * 2];
- uint16_t v_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_count + pixel_uv_idx * 2 + 1];
+ uint16_t y_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_y_idx]
+ >> 6;
+ uint16_t u_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_count + pixel_uv_idx * 2]
+ >> 6;
+ uint16_t v_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_count + pixel_uv_idx * 2 + 1]
+ >> 6;
// Conversions include taking narrow-range into account.
return {{{ static_cast<float>(y_uint) / 940.0f,
@@ -257,4 +382,11 @@
return samplePixels(image, map_scale_factor, x, y, getP010Pixel);
}
+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)
+ | ((0x3ff & static_cast<uint32_t>(e_gamma.b * 1023.0f)) << 20)
+ | (0x3 << 30); // Set alpha to 1.0
+}
+
} // namespace android::recoverymap
diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/recoverymaputils.cpp
new file mode 100644
index 0000000..fe46cba
--- /dev/null
+++ b/libs/jpegrecoverymap/recoverymaputils.cpp
@@ -0,0 +1,153 @@
+/*
+ * 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 <jpegrecoverymap/recoverymaputils.h>
+#include <jpegrecoverymap/recoverymap.h>
+#include <image_io/xml/xml_reader.h>
+#include <image_io/base/message_handler.h>
+#include <image_io/xml/xml_element_rules.h>
+#include <image_io/xml/xml_handler.h>
+#include <image_io/xml/xml_rule.h>
+
+#include <string>
+#include <sstream>
+
+using namespace photos_editing_formats::image_io;
+using namespace std;
+
+namespace android::recoverymap {
+
+
+// Extremely simple XML Handler - just searches for interesting elements
+class XMPXmlHandler : public XmlHandler {
+public:
+
+ XMPXmlHandler() : XmlHandler() {
+ rangeScalingFactorState = NotStrarted;
+ }
+
+ enum ParseState {
+ NotStrarted,
+ Started,
+ Done
+ };
+
+ virtual DataMatchResult StartElement(const XmlTokenContext& context) {
+ string val;
+ if (context.BuildTokenValue(&val)) {
+ if (!val.compare(rangeScalingFactorName)) {
+ rangeScalingFactorState = Started;
+ } else {
+ if (rangeScalingFactorState != Done) {
+ rangeScalingFactorState = NotStrarted;
+ }
+ }
+ }
+ return context.GetResult();
+ }
+
+ virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
+ if (rangeScalingFactorState == Started) {
+ rangeScalingFactorState = Done;
+ }
+ return context.GetResult();
+ }
+
+ virtual DataMatchResult ElementContent(const XmlTokenContext& context) {
+ string val;
+ if (rangeScalingFactorState == Started) {
+ if (context.BuildTokenValue(&val)) {
+ rangeScalingFactorStr.assign(val);
+ }
+ }
+ return context.GetResult();
+ }
+
+ bool getRangeScalingFactor(float* scaling_factor) {
+ if (rangeScalingFactorState == Done) {
+ stringstream ss(rangeScalingFactorStr);
+ float val;
+ if (ss >> val) {
+ *scaling_factor = val;
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ bool getTransferFunction(jpegr_transfer_function* transfer_function) {
+ *transfer_function = JPEGR_TF_HLG;
+ return true;
+ }
+
+private:
+ static const string rangeScalingFactorName;
+ string rangeScalingFactorStr;
+ ParseState rangeScalingFactorState;
+};
+
+const string XMPXmlHandler::rangeScalingFactorName = "GContainer:rangeScalingFactor";
+
+
+bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
+ string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
+
+ if (xmp_size < nameSpace.size()+2) {
+ // Data too short
+ return false;
+ }
+
+ if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) {
+ // Not correct namespace
+ return false;
+ }
+
+ // Position the pointers to the start of XMP XML portion
+ xmp_data += nameSpace.size()+1;
+ xmp_size -= nameSpace.size()+1;
+ XMPXmlHandler handler;
+
+ // We need to remove tail data until the closing tag. Otherwise parser will throw an error.
+ while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) {
+ xmp_size--;
+ }
+
+ string str(reinterpret_cast<const char*>(xmp_data), xmp_size);
+ MessageHandler msg_handler;
+ unique_ptr<XmlRule> rule(new XmlElementRule);
+ XmlReader reader(&handler, &msg_handler);
+ reader.StartParse(std::move(rule));
+ reader.Parse(str);
+ reader.FinishParse();
+ if (reader.HasErrors()) {
+ // Parse error
+ return false;
+ }
+
+ if (!handler.getRangeScalingFactor(&metadata->rangeScalingFactor)) {
+ return false;
+ }
+
+ if (!handler.getTransferFunction(&metadata->transferFunction)) {
+ return false;
+ }
+ return true;
+}
+
+} // namespace android::recoverymap
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
index 226056b..b3cd37e 100644
--- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
@@ -37,9 +37,11 @@
TEST_F(RecoveryMapTest, build) {
// Force all of the recovery map lib to be linked by calling all public functions.
RecoveryMap recovery_map;
- recovery_map.encodeJPEGR(nullptr, nullptr, nullptr, 0, nullptr);
- recovery_map.encodeJPEGR(nullptr, nullptr, nullptr, nullptr);
- recovery_map.encodeJPEGR(nullptr, nullptr, nullptr);
+ recovery_map.encodeJPEGR(nullptr, nullptr, static_cast<jpegr_transfer_function>(0),
+ nullptr, 0, nullptr);
+ recovery_map.encodeJPEGR(nullptr, nullptr, nullptr, static_cast<jpegr_transfer_function>(0),
+ nullptr);
+ recovery_map.encodeJPEGR(nullptr, nullptr, static_cast<jpegr_transfer_function>(0), nullptr);
recovery_map.decodeJPEGR(nullptr, nullptr, nullptr, false);
}
diff --git a/libs/sensor/Android.bp b/libs/sensor/Android.bp
index 2b93c6e..b6b9cc4 100644
--- a/libs/sensor/Android.bp
+++ b/libs/sensor/Android.bp
@@ -21,9 +21,10 @@
default_applicable_licenses: ["frameworks_native_license"],
}
-cc_library_shared {
+cc_library {
name: "libsensor",
+ host_supported: true,
cflags: [
"-Wall",
"-Werror",
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index d1de551..b885435 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -60,7 +60,6 @@
name: "libinputflinger_sources",
srcs: [
"InputCommonConverter.cpp",
- "InputManager.cpp",
"InputProcessor.cpp",
"PreferStylusOverTouchBlocker.cpp",
"UnwantedInteractionBlocker.cpp",
@@ -116,6 +115,10 @@
"inputflinger_defaults",
"libinputflinger_defaults",
],
+ srcs: [
+ "InputManager.cpp",
+ // other sources are added via "defaults"
+ ],
cflags: [
// TODO(b/23084678): Move inputflinger to its own process and mark it hidden
//-fvisibility=hidden
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 4a0f2ec..b3b4fb1 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -41,6 +41,7 @@
"include-filter": "android.view.cts.input",
"include-filter": "android.view.cts.MotionEventTest",
"include-filter": "android.view.cts.PointerCaptureTest",
+ "include-filter": "android.view.cts.TooltipTest",
"include-filter": "android.view.cts.VerifyInputEventTest"
}
]
@@ -128,6 +129,7 @@
{
"include-filter": "android.view.cts.MotionEventTest",
"include-filter": "android.view.cts.PointerCaptureTest",
+ "include-filter": "android.view.cts.TooltipTest",
"include-filter": "android.view.cts.VerifyInputEventTest"
}
]
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 87a4ff4..6d30090 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -1769,15 +1769,16 @@
void InputDispatcher::logOutboundMotionDetails(const char* prefix, const MotionEntry& entry) {
if (DEBUG_OUTBOUND_EVENT_DETAILS) {
- ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%" PRId32
+ ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=%s, displayId=%" PRId32
", policyFlags=0x%x, "
"action=%s, actionButton=0x%x, flags=0x%x, "
"metaState=0x%x, buttonState=0x%x,"
"edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%" PRId64,
- prefix, entry.eventTime, entry.deviceId, entry.source, entry.displayId,
- entry.policyFlags, MotionEvent::actionToString(entry.action).c_str(),
- entry.actionButton, entry.flags, entry.metaState, entry.buttonState, entry.edgeFlags,
- entry.xPrecision, entry.yPrecision, entry.downTime);
+ prefix, entry.eventTime, entry.deviceId,
+ inputEventSourceToString(entry.source).c_str(), entry.displayId, entry.policyFlags,
+ MotionEvent::actionToString(entry.action).c_str(), entry.actionButton, entry.flags,
+ entry.metaState, entry.buttonState, entry.edgeFlags, entry.xPrecision,
+ entry.yPrecision, entry.downTime);
for (uint32_t i = 0; i < entry.pointerCount; i++) {
ALOGD(" Pointer %d: id=%d, toolType=%d, "
@@ -2305,6 +2306,20 @@
entry.eventTime);
}
}
+
+ // Update the pointerIds for non-splittable when it received pointer down.
+ if (!isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+ // If no split, we suppose all touched windows should receive pointer down.
+ const int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+ for (size_t i = 0; i < tempTouchState.windows.size(); i++) {
+ TouchedWindow& touchedWindow = tempTouchState.windows[i];
+ // Ignore drag window for it should just track one pointer.
+ if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) {
+ continue;
+ }
+ touchedWindow.pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
+ }
+ }
}
// Update dispatching for hover enter and exit.
@@ -2479,17 +2494,6 @@
}
i += 1;
}
- } else if (!isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
- // If no split, we suppose all touched windows should receive pointer down.
- const int32_t pointerIndex = getMotionEventActionPointerIndex(action);
- for (size_t i = 0; i < tempTouchState.windows.size(); i++) {
- TouchedWindow& touchedWindow = tempTouchState.windows[i];
- // Ignore drag window for it should just track one pointer.
- if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) {
- continue;
- }
- touchedWindow.pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
- }
}
// Save changes unless the action was scroll in which case the temporary touch
@@ -2502,6 +2506,10 @@
}
}
+ if (tempTouchState.windows.empty()) {
+ mTouchStatesByDisplay.erase(displayId);
+ }
+
// Update hover state.
mLastHoverWindowHandle = newHoverWindowHandle;
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index ee7da93..114e0bf 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -148,7 +148,8 @@
std::string TouchState::dump() const {
std::string out;
- out += StringPrintf("deviceId=%d, source=0x%08x\n", deviceId, source);
+ out += StringPrintf("deviceId=%d, source=%s\n", deviceId,
+ inputEventSourceToString(source).c_str());
if (!windows.empty()) {
out += " Windows:\n";
for (size_t i = 0; i < windows.size(); i++) {
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index ccff353..b193dff 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -119,6 +119,18 @@
if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
}
+ } else if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS && !mStylusMtToolSeen) {
+ mStylusMtToolSeen = true;
+ // The multi-touch device produced a stylus event with MT_TOOL_PEN. Dynamically
+ // re-configure this input device so that we add SOURCE_STYLUS if we haven't already.
+ // This is to cover the case where we cannot reliably detect whether a multi-touch
+ // device will ever produce stylus events when it is initially being configured.
+ if (!isFromSource(mSource, AINPUT_SOURCE_STYLUS)) {
+ // Add the stylus source immediately so that it is included in any events generated
+ // before we have a chance to re-configure the device.
+ mSource |= AINPUT_SOURCE_STYLUS;
+ bumpGeneration();
+ }
}
if (shouldSimulateStylusWithTouch() &&
outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) {
@@ -200,7 +212,8 @@
}
bool MultiTouchInputMapper::hasStylus() const {
- return mTouchButtonAccumulator.hasStylus() || shouldSimulateStylusWithTouch();
+ return mStylusMtToolSeen || mTouchButtonAccumulator.hasStylus() ||
+ shouldSimulateStylusWithTouch();
}
bool MultiTouchInputMapper::shouldSimulateStylusWithTouch() const {
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
index ddf9e80..5f8bccf 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
@@ -50,6 +50,8 @@
// Specifies the pointer id bits that are in use, and their associated tracking id.
BitSet32 mPointerIdBits;
int32_t mPointerTrackingIdMap[MAX_POINTER_ID + 1];
+
+ bool mStylusMtToolSeen{false};
};
} // namespace android
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index 29a1bda..06d4dc3 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -97,35 +97,34 @@
std::list<NotifyArgs> RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readTime) {
std::list<NotifyArgs> out;
- PointerCoords pointerCoords;
- pointerCoords.clear();
-
- PointerProperties pointerProperties;
- pointerProperties.clear();
- pointerProperties.id = 0;
- pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
float scroll = mRotaryEncoderScrollAccumulator.getRelativeVWheel();
bool scrolled = scroll != 0;
- // This is not a pointer, so it's not associated with a display.
- int32_t displayId = ADISPLAY_ID_NONE;
-
- // Moving the rotary encoder should wake the device (if specified).
- uint32_t policyFlags = 0;
- if (scrolled && getDeviceContext().isExternal()) {
- policyFlags |= POLICY_FLAG_WAKE;
- }
-
- if (mOrientation == DISPLAY_ORIENTATION_180) {
- scroll = -scroll;
- }
-
// Send motion event.
if (scrolled) {
int32_t metaState = getContext()->getGlobalMetaState();
+ // This is not a pointer, so it's not associated with a display.
+ int32_t displayId = ADISPLAY_ID_NONE;
+
+ if (mOrientation == DISPLAY_ORIENTATION_180) {
+ scroll = -scroll;
+ }
+
+ PointerCoords pointerCoords;
+ pointerCoords.clear();
pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_SCROLL, scroll * mScalingFactor);
+ PointerProperties pointerProperties;
+ pointerProperties.clear();
+ pointerProperties.id = 0;
+ pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+
+ uint32_t policyFlags = 0;
+ if (getDeviceContext().isExternal()) {
+ policyFlags |= POLICY_FLAG_WAKE;
+ }
+
out.push_back(
NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0,
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index bc55fe5..5be694f 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -3763,15 +3763,12 @@
PointerCoords pointerCoords[MAX_POINTERS];
PointerProperties pointerProperties[MAX_POINTERS];
uint32_t pointerCount = 0;
- bool stylusToolFound = false;
while (!idBits.isEmpty()) {
uint32_t id = idBits.clearFirstMarkedBit();
uint32_t index = idToIndex[id];
pointerProperties[pointerCount].copyFrom(properties[index]);
pointerCoords[pointerCount].copyFrom(coords[index]);
- stylusToolFound |= isStylusToolType(pointerProperties[pointerCount].toolType);
-
if (changedId >= 0 && id == uint32_t(changedId)) {
action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
}
@@ -3803,12 +3800,6 @@
if (mDeviceMode == DeviceMode::POINTER) {
mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
}
- if (stylusToolFound) {
- // Dynamically add the stylus source when there's a stylus tool being used to cover the case
- // where we cannot reliably detect whether a multi-touch device will ever produce stylus
- // events when it is initially being configured.
- source |= AINPUT_SOURCE_STYLUS;
- }
const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
const int32_t deviceId = getDeviceId();
std::vector<TouchVideoFrame> frames = getDeviceContext().getVideoFrames();
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index b6d0709..e55e121 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -81,9 +81,6 @@
"libc++fs",
"libgmock",
],
- shared_libs: [
- "libinputreader",
- ],
require_root: true,
test_options: {
unit_test: true,
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index aaf50ce..7817eca 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -22,6 +22,7 @@
#include <android-base/thread_annotations.h>
#include <binder/Binder.h>
#include <fcntl.h>
+#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <input/Input.h>
#include <linux/input.h>
@@ -43,6 +44,7 @@
namespace android::inputdispatcher {
using namespace ftl::flag_operators;
+using testing::AllOf;
// An arbitrary time value.
static constexpr nsecs_t ARBITRARY_TIME = 1234;
@@ -102,6 +104,28 @@
<< MotionEvent::actionToString(receivedAction);
}
+MATCHER_P(WithMotionAction, action, "MotionEvent with specified action") {
+ bool matches = action == arg.getAction();
+ if (!matches) {
+ *result_listener << "expected action " << MotionEvent::actionToString(action)
+ << ", but got " << MotionEvent::actionToString(arg.getAction());
+ }
+ if (action == AMOTION_EVENT_ACTION_CANCEL) {
+ if (!matches) {
+ *result_listener << "; ";
+ }
+ *result_listener << "expected FLAG_CANCELED to be set with ACTION_CANCEL, but was not set";
+ matches &= (arg.getFlags() & AMOTION_EVENT_FLAG_CANCELED) != 0;
+ }
+ return matches;
+}
+
+MATCHER_P(WithSource, source, "InputEvent with specified source") {
+ *result_listener << "expected source " << inputEventSourceToString(source) << ", but got "
+ << inputEventSourceToString(arg.getSource());
+ return arg.getSource() == source;
+}
+
// --- FakeInputDispatcherPolicy ---
class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
@@ -1205,6 +1229,12 @@
mInputReceiver->consumeCaptureEvent(hasCapture);
}
+ void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
+ MotionEvent* motionEvent = consumeMotion();
+ ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event";
+ ASSERT_THAT(*motionEvent, matcher);
+ }
+
void consumeEvent(int32_t expectedEventType, int32_t expectedAction,
std::optional<int32_t> expectedDisplayId,
std::optional<int32_t> expectedFlags) {
@@ -2207,6 +2237,48 @@
ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
}
+/**
+ * Inject a mouse hover event followed by a tap from touchscreen.
+ * In the current implementation, the tap does not cause a HOVER_EXIT event.
+ */
+TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) {
+ 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, 100, 100));
+
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+ // Inject a hover_move from mouse.
+ NotifyMotionArgs motionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE,
+ ADISPLAY_ID_DEFAULT, {{50, 50}});
+ motionArgs.xCursorPosition = 50;
+ motionArgs.yCursorPosition = 50;
+ mDispatcher->notifyMotion(&motionArgs);
+ 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_DOWN),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
+
+ motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {{10, 10}});
+ mDispatcher->notifyMotion(&motionArgs);
+ ASSERT_NO_FATAL_FAILURE(
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
+}
+
TEST_F(InputDispatcherTest, DispatchMouseEventsUnderCursor) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
@@ -2400,6 +2472,43 @@
window->assertNoEvents();
}
+TEST_F(InputDispatcherTest, NonSplitTouchableWindowReceivesMultiTouch) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
+ // Ensure window is non-split and have some transform.
+ window->setPreventSplitting(true);
+ window->setWindowOffset(20, 40);
+ mDispatcher->onWindowInfosChanged({*window->getInfo()}, {});
+
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {50, 50}))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+ const MotionEvent secondFingerDownEvent =
+ MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .displayId(ADISPLAY_ID_DEFAULT)
+ .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+ .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(-30)
+ .y(-50))
+ .build();
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+ InputEventInjectionSync::WAIT_FOR_RESULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+ const MotionEvent* event = window->consumeMotion();
+ EXPECT_EQ(POINTER_1_DOWN, event->getAction());
+ EXPECT_EQ(70, event->getX(0)); // 50 + 20
+ EXPECT_EQ(90, event->getY(0)); // 50 + 40
+ EXPECT_EQ(-10, event->getX(1)); // -30 + 20
+ EXPECT_EQ(-10, event->getY(1)); // -50 + 40
+}
+
/**
* Ensure the correct coordinate spaces are used by InputDispatcher.
*
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 5c5fc77..6b6d0bb 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -10788,11 +10788,12 @@
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
}
-TEST_F(MultiTouchInputMapperTest, ToolTypeSource) {
+TEST_F(MultiTouchInputMapperTest, StylusSourceIsAddedDynamicallyFromToolType) {
addConfigurationProperty("touch.deviceType", "touchScreen");
prepareDisplay(DISPLAY_ORIENTATION_0);
prepareAxes(POSITION | ID | SLOT | PRESSURE | TOOL_TYPE);
MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
// Even if the device supports reporting the ABS_MT_TOOL_TYPE axis, which could give it the
// ability to report MT_TOOL_PEN, we do not report the device as coming from a stylus source.
@@ -10801,7 +10802,7 @@
ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources());
// However, if the device ever ends up reporting an event with MT_TOOL_PEN, it should be
- // reported with the stylus source, even through the device doesn't support the stylus source.
+ // reported with the stylus source.
processId(mapper, FIRST_TRACKING_ID);
processToolType(mapper, MT_TOOL_PEN);
processPosition(mapper, 100, 200);
@@ -10812,15 +10813,27 @@
WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+ // Now that we know the device supports styluses, ensure that the device is re-configured with
+ // the stylus source.
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, mapper.getSources());
+ {
+ const auto& devices = mReader->getInputDevices();
+ auto deviceInfo =
+ std::find_if(devices.begin(), devices.end(),
+ [](const InputDeviceInfo& info) { return info.getId() == DEVICE_ID; });
+ LOG_ALWAYS_FATAL_IF(deviceInfo == devices.end(), "Cannot find InputDevice");
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, deviceInfo->getSources());
+ }
+
+ // Ensure the device was not reset to prevent interruptions of any ongoing gestures.
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasNotCalled());
+
processId(mapper, INVALID_TRACKING_ID);
processSync(mapper);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
-
- // The mapper should still report only a touchscreen source.
- ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources());
}
// --- MultiTouchInputMapperTest_ExternalDevice ---
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
index 8721bd8..9db3422 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -46,7 +46,8 @@
}
MATCHER_P(WithSource, source, "InputEvent with specified source") {
- *result_listener << "expected source " << source << ", but got " << arg.source;
+ *result_listener << "expected source " << inputEventSourceToString(source) << ", but got "
+ << inputEventSourceToString(arg.source);
return arg.source == source;
}
diff --git a/services/sensorservice/AidlSensorHalWrapper.cpp b/services/sensorservice/AidlSensorHalWrapper.cpp
index f67c610..f5b360f 100644
--- a/services/sensorservice/AidlSensorHalWrapper.cpp
+++ b/services/sensorservice/AidlSensorHalWrapper.cpp
@@ -23,6 +23,7 @@
#include <aidlcommonsupport/NativeHandle.h>
#include <android-base/logging.h>
#include <android/binder_manager.h>
+#include <aidl/sensors/convert.h>
using ::aidl::android::hardware::sensors::AdditionalInfo;
using ::aidl::android::hardware::sensors::DynamicSensorInfo;
@@ -34,469 +35,16 @@
using ::android::AidlMessageQueue;
using ::android::hardware::EventFlag;
using ::android::hardware::sensors::V2_1::implementation::MAX_RECEIVE_BUFFER_EVENT_COUNT;
+using ::android::hardware::sensors::implementation::convertToStatus;
+using ::android::hardware::sensors::implementation::convertToSensor;
+using ::android::hardware::sensors::implementation::convertToSensorEvent;
+using ::android::hardware::sensors::implementation::convertFromSensorEvent;
+
namespace android {
namespace {
-status_t convertToStatus(ndk::ScopedAStatus status) {
- if (status.isOk()) {
- return OK;
- } else {
- switch (status.getExceptionCode()) {
- case EX_ILLEGAL_ARGUMENT: {
- return BAD_VALUE;
- }
- case EX_SECURITY: {
- return PERMISSION_DENIED;
- }
- case EX_UNSUPPORTED_OPERATION: {
- return INVALID_OPERATION;
- }
- case EX_SERVICE_SPECIFIC: {
- switch (status.getServiceSpecificError()) {
- case ISensors::ERROR_BAD_VALUE: {
- return BAD_VALUE;
- }
- case ISensors::ERROR_NO_MEMORY: {
- return NO_MEMORY;
- }
- default: {
- return UNKNOWN_ERROR;
- }
- }
- }
- default: {
- return UNKNOWN_ERROR;
- }
- }
- }
-}
-
-void convertToSensor(const SensorInfo &src, sensor_t *dst) {
- dst->name = strdup(src.name.c_str());
- dst->vendor = strdup(src.vendor.c_str());
- dst->version = src.version;
- dst->handle = src.sensorHandle;
- dst->type = (int)src.type;
- dst->maxRange = src.maxRange;
- dst->resolution = src.resolution;
- dst->power = src.power;
- dst->minDelay = src.minDelayUs;
- dst->fifoReservedEventCount = src.fifoReservedEventCount;
- dst->fifoMaxEventCount = src.fifoMaxEventCount;
- dst->stringType = strdup(src.typeAsString.c_str());
- dst->requiredPermission = strdup(src.requiredPermission.c_str());
- dst->maxDelay = src.maxDelayUs;
- dst->flags = src.flags;
- dst->reserved[0] = dst->reserved[1] = 0;
-}
-
-void convertToSensorEvent(const Event &src, sensors_event_t *dst) {
- *dst = {.version = sizeof(sensors_event_t),
- .sensor = src.sensorHandle,
- .type = (int32_t)src.sensorType,
- .reserved0 = 0,
- .timestamp = src.timestamp};
-
- switch (src.sensorType) {
- case SensorType::META_DATA: {
- // Legacy HALs expect the handle reference in the meta data field.
- // Copy it over from the handle of the event.
- dst->meta_data.what = (int32_t)src.payload.get<Event::EventPayload::meta>().what;
- dst->meta_data.sensor = src.sensorHandle;
- // Set the sensor handle to 0 to maintain compatibility.
- dst->sensor = 0;
- break;
- }
-
- case SensorType::ACCELEROMETER:
- case SensorType::MAGNETIC_FIELD:
- case SensorType::ORIENTATION:
- case SensorType::GYROSCOPE:
- case SensorType::GRAVITY:
- case SensorType::LINEAR_ACCELERATION: {
- dst->acceleration.x = src.payload.get<Event::EventPayload::vec3>().x;
- dst->acceleration.y = src.payload.get<Event::EventPayload::vec3>().y;
- dst->acceleration.z = src.payload.get<Event::EventPayload::vec3>().z;
- dst->acceleration.status = (int32_t)src.payload.get<Event::EventPayload::vec3>().status;
- break;
- }
-
- case SensorType::GAME_ROTATION_VECTOR: {
- dst->data[0] = src.payload.get<Event::EventPayload::vec4>().x;
- dst->data[1] = src.payload.get<Event::EventPayload::vec4>().y;
- dst->data[2] = src.payload.get<Event::EventPayload::vec4>().z;
- dst->data[3] = src.payload.get<Event::EventPayload::vec4>().w;
- break;
- }
-
- case SensorType::ROTATION_VECTOR:
- case SensorType::GEOMAGNETIC_ROTATION_VECTOR: {
- dst->data[0] = src.payload.get<Event::EventPayload::data>().values[0];
- dst->data[1] = src.payload.get<Event::EventPayload::data>().values[1];
- dst->data[2] = src.payload.get<Event::EventPayload::data>().values[2];
- dst->data[3] = src.payload.get<Event::EventPayload::data>().values[3];
- dst->data[4] = src.payload.get<Event::EventPayload::data>().values[4];
- break;
- }
-
- case SensorType::MAGNETIC_FIELD_UNCALIBRATED:
- case SensorType::GYROSCOPE_UNCALIBRATED:
- case SensorType::ACCELEROMETER_UNCALIBRATED: {
- dst->uncalibrated_gyro.x_uncalib = src.payload.get<Event::EventPayload::uncal>().x;
- dst->uncalibrated_gyro.y_uncalib = src.payload.get<Event::EventPayload::uncal>().y;
- dst->uncalibrated_gyro.z_uncalib = src.payload.get<Event::EventPayload::uncal>().z;
- dst->uncalibrated_gyro.x_bias = src.payload.get<Event::EventPayload::uncal>().xBias;
- dst->uncalibrated_gyro.y_bias = src.payload.get<Event::EventPayload::uncal>().yBias;
- dst->uncalibrated_gyro.z_bias = src.payload.get<Event::EventPayload::uncal>().zBias;
- break;
- }
-
- case SensorType::HINGE_ANGLE:
- case SensorType::DEVICE_ORIENTATION:
- case SensorType::LIGHT:
- case SensorType::PRESSURE:
- case SensorType::PROXIMITY:
- case SensorType::RELATIVE_HUMIDITY:
- case SensorType::AMBIENT_TEMPERATURE:
- case SensorType::SIGNIFICANT_MOTION:
- case SensorType::STEP_DETECTOR:
- case SensorType::TILT_DETECTOR:
- case SensorType::WAKE_GESTURE:
- case SensorType::GLANCE_GESTURE:
- case SensorType::PICK_UP_GESTURE:
- case SensorType::WRIST_TILT_GESTURE:
- case SensorType::STATIONARY_DETECT:
- case SensorType::MOTION_DETECT:
- case SensorType::HEART_BEAT:
- case SensorType::LOW_LATENCY_OFFBODY_DETECT: {
- dst->data[0] = src.payload.get<Event::EventPayload::scalar>();
- break;
- }
-
- case SensorType::STEP_COUNTER: {
- dst->u64.step_counter = src.payload.get<Event::EventPayload::stepCount>();
- break;
- }
-
- case SensorType::HEART_RATE: {
- dst->heart_rate.bpm = src.payload.get<Event::EventPayload::heartRate>().bpm;
- dst->heart_rate.status =
- (int8_t)src.payload.get<Event::EventPayload::heartRate>().status;
- break;
- }
-
- case SensorType::POSE_6DOF: { // 15 floats
- for (size_t i = 0; i < 15; ++i) {
- dst->data[i] = src.payload.get<Event::EventPayload::pose6DOF>().values[i];
- }
- break;
- }
-
- case SensorType::DYNAMIC_SENSOR_META: {
- dst->dynamic_sensor_meta.connected =
- src.payload.get<Event::EventPayload::dynamic>().connected;
- dst->dynamic_sensor_meta.handle =
- src.payload.get<Event::EventPayload::dynamic>().sensorHandle;
- dst->dynamic_sensor_meta.sensor = NULL; // to be filled in later
-
- memcpy(dst->dynamic_sensor_meta.uuid,
- src.payload.get<Event::EventPayload::dynamic>().uuid.values.data(), 16);
-
- break;
- }
-
- case SensorType::ADDITIONAL_INFO: {
- const AdditionalInfo &srcInfo = src.payload.get<Event::EventPayload::additional>();
-
- additional_info_event_t *dstInfo = &dst->additional_info;
- dstInfo->type = (int32_t)srcInfo.type;
- dstInfo->serial = srcInfo.serial;
-
- switch (srcInfo.payload.getTag()) {
- case AdditionalInfo::AdditionalInfoPayload::Tag::dataInt32: {
- const auto &values =
- srcInfo.payload.get<AdditionalInfo::AdditionalInfoPayload::dataInt32>()
- .values;
- CHECK_EQ(values.size() * sizeof(int32_t), sizeof(dstInfo->data_int32));
- memcpy(dstInfo->data_int32, values.data(), sizeof(dstInfo->data_int32));
- break;
- }
- case AdditionalInfo::AdditionalInfoPayload::Tag::dataFloat: {
- const auto &values =
- srcInfo.payload.get<AdditionalInfo::AdditionalInfoPayload::dataFloat>()
- .values;
- CHECK_EQ(values.size() * sizeof(float), sizeof(dstInfo->data_float));
- memcpy(dstInfo->data_float, values.data(), sizeof(dstInfo->data_float));
- break;
- }
- default: {
- ALOGE("Invalid sensor additional info tag: %d", (int)srcInfo.payload.getTag());
- }
- }
- break;
- }
-
- case SensorType::HEAD_TRACKER: {
- const auto &ht = src.payload.get<Event::EventPayload::headTracker>();
- dst->head_tracker.rx = ht.rx;
- dst->head_tracker.ry = ht.ry;
- dst->head_tracker.rz = ht.rz;
- dst->head_tracker.vx = ht.vx;
- dst->head_tracker.vy = ht.vy;
- dst->head_tracker.vz = ht.vz;
- dst->head_tracker.discontinuity_count = ht.discontinuityCount;
- break;
- }
-
- case SensorType::ACCELEROMETER_LIMITED_AXES:
- case SensorType::GYROSCOPE_LIMITED_AXES:
- dst->limited_axes_imu.x = src.payload.get<Event::EventPayload::limitedAxesImu>().x;
- dst->limited_axes_imu.y = src.payload.get<Event::EventPayload::limitedAxesImu>().y;
- dst->limited_axes_imu.z = src.payload.get<Event::EventPayload::limitedAxesImu>().z;
- dst->limited_axes_imu.x_supported =
- src.payload.get<Event::EventPayload::limitedAxesImu>().xSupported;
- dst->limited_axes_imu.y_supported =
- src.payload.get<Event::EventPayload::limitedAxesImu>().ySupported;
- dst->limited_axes_imu.z_supported =
- src.payload.get<Event::EventPayload::limitedAxesImu>().zSupported;
- break;
-
- case SensorType::ACCELEROMETER_LIMITED_AXES_UNCALIBRATED:
- case SensorType::GYROSCOPE_LIMITED_AXES_UNCALIBRATED:
- dst->limited_axes_imu_uncalibrated.x_uncalib =
- src.payload.get<Event::EventPayload::limitedAxesImuUncal>().x;
- dst->limited_axes_imu_uncalibrated.y_uncalib =
- src.payload.get<Event::EventPayload::limitedAxesImuUncal>().y;
- dst->limited_axes_imu_uncalibrated.z_uncalib =
- src.payload.get<Event::EventPayload::limitedAxesImuUncal>().z;
- dst->limited_axes_imu_uncalibrated.x_bias =
- src.payload.get<Event::EventPayload::limitedAxesImuUncal>().xBias;
- dst->limited_axes_imu_uncalibrated.y_bias =
- src.payload.get<Event::EventPayload::limitedAxesImuUncal>().yBias;
- dst->limited_axes_imu_uncalibrated.z_bias =
- src.payload.get<Event::EventPayload::limitedAxesImuUncal>().zBias;
- dst->limited_axes_imu_uncalibrated.x_supported =
- src.payload.get<Event::EventPayload::limitedAxesImuUncal>().xSupported;
- dst->limited_axes_imu_uncalibrated.y_supported =
- src.payload.get<Event::EventPayload::limitedAxesImuUncal>().ySupported;
- dst->limited_axes_imu_uncalibrated.z_supported =
- src.payload.get<Event::EventPayload::limitedAxesImuUncal>().zSupported;
- break;
-
- case SensorType::HEADING:
- dst->heading.heading = src.payload.get<Event::EventPayload::heading>().heading;
- dst->heading.accuracy = src.payload.get<Event::EventPayload::heading>().accuracy;
- break;
-
- default: {
- CHECK_GE((int32_t)src.sensorType, (int32_t)SensorType::DEVICE_PRIVATE_BASE);
-
- memcpy(dst->data, src.payload.get<Event::EventPayload::data>().values.data(),
- 16 * sizeof(float));
- break;
- }
- }
-}
-
-void convertFromSensorEvent(const sensors_event_t &src, Event *dst) {
- *dst = {
- .timestamp = src.timestamp,
- .sensorHandle = src.sensor,
- .sensorType = (SensorType)src.type,
- };
-
- switch (dst->sensorType) {
- case SensorType::META_DATA: {
- Event::EventPayload::MetaData meta;
- meta.what = (Event::EventPayload::MetaData::MetaDataEventType)src.meta_data.what;
- // Legacy HALs contain the handle reference in the meta data field.
- // Copy that over to the handle of the event. In legacy HALs this
- // field was expected to be 0.
- dst->sensorHandle = src.meta_data.sensor;
- dst->payload.set<Event::EventPayload::Tag::meta>(meta);
- break;
- }
-
- case SensorType::ACCELEROMETER:
- case SensorType::MAGNETIC_FIELD:
- case SensorType::ORIENTATION:
- case SensorType::GYROSCOPE:
- case SensorType::GRAVITY:
- case SensorType::LINEAR_ACCELERATION: {
- Event::EventPayload::Vec3 vec3;
- vec3.x = src.acceleration.x;
- vec3.y = src.acceleration.y;
- vec3.z = src.acceleration.z;
- vec3.status = (SensorStatus)src.acceleration.status;
- dst->payload.set<Event::EventPayload::Tag::vec3>(vec3);
- break;
- }
-
- case SensorType::GAME_ROTATION_VECTOR: {
- Event::EventPayload::Vec4 vec4;
- vec4.x = src.data[0];
- vec4.y = src.data[1];
- vec4.z = src.data[2];
- vec4.w = src.data[3];
- dst->payload.set<Event::EventPayload::Tag::vec4>(vec4);
- break;
- }
-
- case SensorType::ROTATION_VECTOR:
- case SensorType::GEOMAGNETIC_ROTATION_VECTOR: {
- Event::EventPayload::Data data;
- memcpy(data.values.data(), src.data, 5 * sizeof(float));
- dst->payload.set<Event::EventPayload::Tag::data>(data);
- break;
- }
-
- case SensorType::MAGNETIC_FIELD_UNCALIBRATED:
- case SensorType::GYROSCOPE_UNCALIBRATED:
- case SensorType::ACCELEROMETER_UNCALIBRATED: {
- Event::EventPayload::Uncal uncal;
- uncal.x = src.uncalibrated_gyro.x_uncalib;
- uncal.y = src.uncalibrated_gyro.y_uncalib;
- uncal.z = src.uncalibrated_gyro.z_uncalib;
- uncal.xBias = src.uncalibrated_gyro.x_bias;
- uncal.yBias = src.uncalibrated_gyro.y_bias;
- uncal.zBias = src.uncalibrated_gyro.z_bias;
- dst->payload.set<Event::EventPayload::Tag::uncal>(uncal);
- break;
- }
-
- case SensorType::DEVICE_ORIENTATION:
- case SensorType::LIGHT:
- case SensorType::PRESSURE:
- case SensorType::PROXIMITY:
- case SensorType::RELATIVE_HUMIDITY:
- case SensorType::AMBIENT_TEMPERATURE:
- case SensorType::SIGNIFICANT_MOTION:
- case SensorType::STEP_DETECTOR:
- case SensorType::TILT_DETECTOR:
- case SensorType::WAKE_GESTURE:
- case SensorType::GLANCE_GESTURE:
- case SensorType::PICK_UP_GESTURE:
- case SensorType::WRIST_TILT_GESTURE:
- case SensorType::STATIONARY_DETECT:
- case SensorType::MOTION_DETECT:
- case SensorType::HEART_BEAT:
- case SensorType::LOW_LATENCY_OFFBODY_DETECT:
- case SensorType::HINGE_ANGLE: {
- dst->payload.set<Event::EventPayload::Tag::scalar>((float)src.data[0]);
- break;
- }
-
- case SensorType::STEP_COUNTER: {
- dst->payload.set<Event::EventPayload::Tag::stepCount>(src.u64.step_counter);
- break;
- }
-
- case SensorType::HEART_RATE: {
- Event::EventPayload::HeartRate heartRate;
- heartRate.bpm = src.heart_rate.bpm;
- heartRate.status = (SensorStatus)src.heart_rate.status;
- dst->payload.set<Event::EventPayload::Tag::heartRate>(heartRate);
- break;
- }
-
- case SensorType::POSE_6DOF: { // 15 floats
- Event::EventPayload::Pose6Dof pose6DOF;
- for (size_t i = 0; i < 15; ++i) {
- pose6DOF.values[i] = src.data[i];
- }
- dst->payload.set<Event::EventPayload::Tag::pose6DOF>(pose6DOF);
- break;
- }
-
- case SensorType::DYNAMIC_SENSOR_META: {
- DynamicSensorInfo dynamic;
- dynamic.connected = src.dynamic_sensor_meta.connected;
- dynamic.sensorHandle = src.dynamic_sensor_meta.handle;
-
- memcpy(dynamic.uuid.values.data(), src.dynamic_sensor_meta.uuid, 16);
- dst->payload.set<Event::EventPayload::Tag::dynamic>(dynamic);
- break;
- }
-
- case SensorType::ADDITIONAL_INFO: {
- AdditionalInfo info;
- const additional_info_event_t &srcInfo = src.additional_info;
- info.type = (AdditionalInfo::AdditionalInfoType)srcInfo.type;
- info.serial = srcInfo.serial;
-
- AdditionalInfo::AdditionalInfoPayload::Int32Values data;
- CHECK_EQ(data.values.size() * sizeof(int32_t), sizeof(srcInfo.data_int32));
- memcpy(data.values.data(), srcInfo.data_int32, sizeof(srcInfo.data_int32));
- info.payload.set<AdditionalInfo::AdditionalInfoPayload::Tag::dataInt32>(data);
-
- dst->payload.set<Event::EventPayload::Tag::additional>(info);
- break;
- }
-
- case SensorType::HEAD_TRACKER: {
- Event::EventPayload::HeadTracker headTracker;
- headTracker.rx = src.head_tracker.rx;
- headTracker.ry = src.head_tracker.ry;
- headTracker.rz = src.head_tracker.rz;
- headTracker.vx = src.head_tracker.vx;
- headTracker.vy = src.head_tracker.vy;
- headTracker.vz = src.head_tracker.vz;
- headTracker.discontinuityCount = src.head_tracker.discontinuity_count;
-
- dst->payload.set<Event::EventPayload::Tag::headTracker>(headTracker);
- break;
- }
-
- case SensorType::ACCELEROMETER_LIMITED_AXES:
- case SensorType::GYROSCOPE_LIMITED_AXES: {
- Event::EventPayload::LimitedAxesImu limitedAxesImu;
- limitedAxesImu.x = src.limited_axes_imu.x;
- limitedAxesImu.y = src.limited_axes_imu.y;
- limitedAxesImu.z = src.limited_axes_imu.z;
- limitedAxesImu.xSupported = src.limited_axes_imu.x_supported;
- limitedAxesImu.ySupported = src.limited_axes_imu.y_supported;
- limitedAxesImu.zSupported = src.limited_axes_imu.z_supported;
- dst->payload.set<Event::EventPayload::Tag::limitedAxesImu>(limitedAxesImu);
- break;
- }
-
- case SensorType::ACCELEROMETER_LIMITED_AXES_UNCALIBRATED:
- case SensorType::GYROSCOPE_LIMITED_AXES_UNCALIBRATED: {
- Event::EventPayload::LimitedAxesImuUncal limitedAxesImuUncal;
- limitedAxesImuUncal.x = src.limited_axes_imu_uncalibrated.x_uncalib;
- limitedAxesImuUncal.y = src.limited_axes_imu_uncalibrated.y_uncalib;
- limitedAxesImuUncal.z = src.limited_axes_imu_uncalibrated.z_uncalib;
- limitedAxesImuUncal.xBias = src.limited_axes_imu_uncalibrated.x_bias;
- limitedAxesImuUncal.yBias = src.limited_axes_imu_uncalibrated.y_bias;
- limitedAxesImuUncal.zBias = src.limited_axes_imu_uncalibrated.z_bias;
- limitedAxesImuUncal.xSupported = src.limited_axes_imu_uncalibrated.x_supported;
- limitedAxesImuUncal.ySupported = src.limited_axes_imu_uncalibrated.y_supported;
- limitedAxesImuUncal.zSupported = src.limited_axes_imu_uncalibrated.z_supported;
- dst->payload.set<Event::EventPayload::Tag::limitedAxesImuUncal>(limitedAxesImuUncal);
- break;
- }
-
- case SensorType::HEADING: {
- Event::EventPayload::Heading heading;
- heading.heading = src.heading.heading;
- heading.accuracy = src.heading.accuracy;
- dst->payload.set<Event::EventPayload::heading>(heading);
- break;
- }
-
- default: {
- CHECK_GE((int32_t)dst->sensorType, (int32_t)SensorType::DEVICE_PRIVATE_BASE);
-
- Event::EventPayload::Data data;
- memcpy(data.values.data(), src.data, 16 * sizeof(float));
- dst->payload.set<Event::EventPayload::Tag::data>(data);
- break;
- }
- }
-}
-
void serviceDied(void *cookie) {
ALOGW("Sensors HAL died, attempting to reconnect.");
((AidlSensorHalWrapper *)cookie)->prepareForReconnect();
diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp
index 5ad4815..0eeb820 100644
--- a/services/sensorservice/Android.bp
+++ b/services/sensorservice/Android.bp
@@ -75,6 +75,7 @@
static_libs: [
"libaidlcommonsupport",
"android.hardware.sensors@1.0-convert",
+ "android.hardware.sensors-V1-convert",
"android.hardware.sensors-V1-ndk",
],
diff --git a/services/sensorservice/aidl/Android.bp b/services/sensorservice/aidl/Android.bp
new file mode 100644
index 0000000..34d1de7
--- /dev/null
+++ b/services/sensorservice/aidl/Android.bp
@@ -0,0 +1,44 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_native_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_library {
+ name: "libsensorserviceaidl",
+ srcs: [
+ "EventQueue.cpp",
+ "DirectReportChannel.cpp",
+ "SensorManager.cpp",
+ "utils.cpp",
+ ],
+ host_supported: true,
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ header_libs: ["jni_headers"],
+ shared_libs: [
+ "libbase",
+ "libutils",
+ "libcutils",
+ "libbinder_ndk",
+ "libsensor",
+ "android.frameworks.sensorservice-V1-ndk",
+ "android.hardware.sensors-V1-ndk",
+ ],
+ export_include_dirs: [
+ "include/",
+ ],
+ static_libs: [
+ "android.hardware.sensors-V1-convert",
+ ],
+
+ export_header_lib_headers: ["jni_headers"],
+ local_include_dirs: [
+ "include/sensorserviceaidl/",
+ ],
+}
diff --git a/services/sensorservice/aidl/DirectReportChannel.cpp b/services/sensorservice/aidl/DirectReportChannel.cpp
new file mode 100644
index 0000000..cab53c1
--- /dev/null
+++ b/services/sensorservice/aidl/DirectReportChannel.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#include "DirectReportChannel.h"
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+DirectReportChannel::DirectReportChannel(::android::SensorManager& manager, int channelId)
+ : mManager(manager), mId(channelId) {}
+
+DirectReportChannel::~DirectReportChannel() {
+ mManager.destroyDirectChannel(mId);
+}
+
+ndk::ScopedAStatus DirectReportChannel::configure(
+ int32_t sensorHandle, ::aidl::android::hardware::sensors::ISensors::RateLevel rate,
+ int32_t* _aidl_return) {
+ int token = mManager.configureDirectChannel(mId, sensorHandle, static_cast<int>(rate));
+ if (token <= 0) {
+ return ndk::ScopedAStatus::fromServiceSpecificError(token);
+ }
+ *_aidl_return = token;
+ return ndk::ScopedAStatus::ok();
+}
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/DirectReportChannel.h b/services/sensorservice/aidl/DirectReportChannel.h
new file mode 100644
index 0000000..d9ea73d
--- /dev/null
+++ b/services/sensorservice/aidl/DirectReportChannel.h
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <aidl/android/frameworks/sensorservice/BnDirectReportChannel.h>
+#include <aidl/android/hardware/sensors/ISensors.h>
+#include <sensor/SensorManager.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+class DirectReportChannel final
+ : public ::aidl::android::frameworks::sensorservice::BnDirectReportChannel {
+public:
+ DirectReportChannel(::android::SensorManager& manager, int channelId);
+ ~DirectReportChannel();
+
+ ndk::ScopedAStatus configure(int32_t sensorHandle,
+ ::aidl::android::hardware::sensors::ISensors::RateLevel rate,
+ int32_t* _aidl_return) override;
+
+private:
+ ::android::SensorManager& mManager;
+ const int mId;
+};
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/EventQueue.cpp b/services/sensorservice/aidl/EventQueue.cpp
new file mode 100644
index 0000000..88ab7a7
--- /dev/null
+++ b/services/sensorservice/aidl/EventQueue.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#include "EventQueue.h"
+#include "utils.h"
+
+#include <android-base/logging.h>
+#include <utils/Looper.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+using ::aidl::android::frameworks::sensorservice::IEventQueueCallback;
+using ::aidl::android::hardware::sensors::Event;
+
+class EventQueueLooperCallback : public ::android::LooperCallback {
+public:
+ EventQueueLooperCallback(sp<::android::SensorEventQueue> queue,
+ std::shared_ptr<IEventQueueCallback> callback)
+ : mQueue(queue), mCallback(callback) {}
+
+ int handleEvent(int /* fd */, int /* events */, void* /* data */) {
+ ASensorEvent event;
+ ssize_t actual;
+
+ auto internalQueue = mQueue.promote();
+ if (internalQueue == nullptr) {
+ return 1;
+ }
+
+ while ((actual = internalQueue->read(&event, 1)) > 0) {
+ internalQueue->sendAck(&event, actual);
+ ndk::ScopedAStatus ret = mCallback->onEvent(convertEvent(event));
+ if (!ret.isOk()) {
+ LOG(ERROR) << "Failed to envoke EventQueueCallback: " << ret;
+ }
+ }
+
+ return 1; // continue to receive callbacks
+ }
+
+private:
+ wp<::android::SensorEventQueue> mQueue;
+ std::shared_ptr<IEventQueueCallback> mCallback;
+};
+
+EventQueue::EventQueue(std::shared_ptr<IEventQueueCallback> callback, sp<::android::Looper> looper,
+ sp<::android::SensorEventQueue> internalQueue)
+ : mLooper(looper), mInternalQueue(internalQueue) {
+ mLooper->addFd(internalQueue->getFd(), ALOOPER_POLL_CALLBACK, ALOOPER_EVENT_INPUT,
+ new EventQueueLooperCallback(internalQueue, callback), nullptr);
+}
+
+// FIXME why was this on onLastStrongRef instead of dtor?
+EventQueue::~EventQueue() {
+ mLooper->removeFd(mInternalQueue->getFd());
+}
+
+ndk::ScopedAStatus EventQueue::enableSensor(int32_t in_sensorHandle, int32_t in_samplingPeriodUs,
+ int64_t in_maxBatchReportLatencyUs) {
+ return convertResult(mInternalQueue->enableSensor(in_sensorHandle, in_samplingPeriodUs,
+ in_maxBatchReportLatencyUs, 0));
+}
+
+ndk::ScopedAStatus EventQueue::disableSensor(int32_t in_sensorHandle) {
+ return convertResult(mInternalQueue->disableSensor(in_sensorHandle));
+}
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/EventQueue.h b/services/sensorservice/aidl/EventQueue.h
new file mode 100644
index 0000000..0ae1eba
--- /dev/null
+++ b/services/sensorservice/aidl/EventQueue.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include "SensorManagerAidl.h"
+
+#include <aidl/android/frameworks/sensorservice/BnEventQueue.h>
+#include <sensor/SensorManager.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+struct EventQueue final : public aidl::android::frameworks::sensorservice::BnEventQueue {
+ EventQueue(
+ std::shared_ptr<aidl::android::frameworks::sensorservice::IEventQueueCallback> callback,
+ sp<::android::Looper> looper, sp<::android::SensorEventQueue> internalQueue);
+ ~EventQueue();
+
+ ndk::ScopedAStatus enableSensor(int32_t in_sensorHandle, int32_t in_samplingPeriodUs,
+ int64_t in_maxBatchReportLatencyUs) override;
+ ndk::ScopedAStatus disableSensor(int32_t sensorHandle) override;
+
+private:
+ friend class EventQueueLooperCallback;
+ sp<::android::Looper> mLooper;
+ sp<::android::SensorEventQueue> mInternalQueue;
+};
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/SensorManager.cpp b/services/sensorservice/aidl/SensorManager.cpp
new file mode 100644
index 0000000..6d8d574
--- /dev/null
+++ b/services/sensorservice/aidl/SensorManager.cpp
@@ -0,0 +1,254 @@
+/*
+ * 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.
+ */
+
+// LOG_TAG defined via build flag.
+#ifndef LOG_TAG
+#define LOG_TAG "AidlSensorManager"
+#endif
+
+#include "DirectReportChannel.h"
+#include "EventQueue.h"
+#include "SensorManagerAidl.h"
+#include "utils.h"
+
+#include <aidl/android/hardware/sensors/ISensors.h>
+#include <android-base/logging.h>
+#include <android/binder_ibinder.h>
+#include <sched.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+using ::aidl::android::frameworks::sensorservice::IDirectReportChannel;
+using ::aidl::android::frameworks::sensorservice::IEventQueue;
+using ::aidl::android::frameworks::sensorservice::IEventQueueCallback;
+using ::aidl::android::frameworks::sensorservice::ISensorManager;
+using ::aidl::android::hardware::common::Ashmem;
+using ::aidl::android::hardware::sensors::ISensors;
+using ::aidl::android::hardware::sensors::SensorInfo;
+using ::aidl::android::hardware::sensors::SensorType;
+using ::android::frameworks::sensorservice::implementation::SensorManagerAidl;
+
+static const char* POLL_THREAD_NAME = "aidl_ssvc_poll";
+
+SensorManagerAidl::SensorManagerAidl(JavaVM* vm)
+ : mLooper(new Looper(false)), mStopThread(true), mJavaVm(vm) {}
+SensorManagerAidl::~SensorManagerAidl() {
+ // Stops pollAll inside the thread.
+ std::lock_guard<std::mutex> lock(mThreadMutex);
+
+ mStopThread = true;
+ if (mLooper != nullptr) {
+ mLooper->wake();
+ }
+ if (mPollThread.joinable()) {
+ mPollThread.join();
+ }
+}
+
+ndk::ScopedAStatus createDirectChannel(::android::SensorManager& manager, size_t size, int type,
+ const native_handle_t* handle,
+ std::shared_ptr<IDirectReportChannel>* chan) {
+ int channelId = manager.createDirectChannel(size, type, handle);
+ if (channelId < 0) {
+ return convertResult(channelId);
+ }
+ if (channelId == 0) {
+ return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_UNKNOWN_ERROR);
+ }
+ *chan = ndk::SharedRefBase::make<DirectReportChannel>(manager, channelId);
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus SensorManagerAidl::createAshmemDirectChannel(
+ const Ashmem& in_mem, int64_t in_size,
+ std::shared_ptr<IDirectReportChannel>* _aidl_return) {
+ if (in_size > in_mem.size || in_size < ISensors::DIRECT_REPORT_SENSOR_EVENT_TOTAL_LENGTH) {
+ return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_BAD_VALUE);
+ }
+ native_handle_t* handle = native_handle_create(1, 0);
+ handle->data[0] = dup(in_mem.fd.get());
+
+ auto status = createDirectChannel(getInternalManager(), in_size, SENSOR_DIRECT_MEM_TYPE_ASHMEM,
+ handle, _aidl_return);
+ int result = native_handle_close(handle);
+ CHECK(result == 0) << "Failed to close the native_handle_t: " << result;
+ result = native_handle_delete(handle);
+ CHECK(result == 0) << "Failed to delete the native_handle_t: " << result;
+
+ return status;
+}
+
+ndk::ScopedAStatus SensorManagerAidl::createGrallocDirectChannel(
+ const ndk::ScopedFileDescriptor& in_mem, int64_t in_size,
+ std::shared_ptr<IDirectReportChannel>* _aidl_return) {
+ native_handle_t* handle = native_handle_create(1, 0);
+ handle->data[0] = dup(in_mem.get());
+
+ auto status = createDirectChannel(getInternalManager(), in_size, SENSOR_DIRECT_MEM_TYPE_GRALLOC,
+ handle, _aidl_return);
+ int result = native_handle_close(handle);
+ CHECK(result == 0) << "Failed to close the native_handle_t: " << result;
+ result = native_handle_delete(handle);
+ CHECK(result == 0) << "Failed to delete the native_handle_t: " << result;
+
+ return status;
+}
+
+ndk::ScopedAStatus SensorManagerAidl::createEventQueue(
+ const std::shared_ptr<IEventQueueCallback>& in_callback,
+ std::shared_ptr<IEventQueue>* _aidl_return) {
+ if (in_callback == nullptr) {
+ return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_BAD_VALUE);
+ }
+
+ sp<::android::Looper> looper = getLooper();
+ if (looper == nullptr) {
+ LOG(ERROR) << "::android::SensorManagerAidl::createEventQueue cannot initialize looper";
+ return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_UNKNOWN_ERROR);
+ }
+
+ String8 package(String8::format("aidl_client_pid_%d", AIBinder_getCallingPid()));
+ sp<::android::SensorEventQueue> internalQueue = getInternalManager().createEventQueue(package);
+ if (internalQueue == nullptr) {
+ LOG(ERROR) << "::android::SensorManagerAidl::createEventQueue returns nullptr.";
+ return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_UNKNOWN_ERROR);
+ }
+
+ *_aidl_return = ndk::SharedRefBase::make<EventQueue>(in_callback, looper, internalQueue);
+
+ return ndk::ScopedAStatus::ok();
+}
+
+SensorInfo convertSensor(Sensor src) {
+ SensorInfo dst;
+ dst.sensorHandle = src.getHandle();
+ dst.name = src.getName();
+ dst.vendor = src.getVendor();
+ dst.version = src.getVersion();
+ dst.type = static_cast<SensorType>(src.getType());
+ dst.typeAsString = src.getStringType();
+ // maxRange uses maxValue because ::android::Sensor wraps the
+ // internal sensor_t in this way.
+ dst.maxRange = src.getMaxValue();
+ dst.resolution = src.getResolution();
+ dst.power = src.getPowerUsage();
+ dst.minDelayUs = src.getMinDelay();
+ dst.fifoReservedEventCount = src.getFifoReservedEventCount();
+ dst.fifoMaxEventCount = src.getFifoMaxEventCount();
+ dst.requiredPermission = src.getRequiredPermission();
+ dst.maxDelayUs = src.getMaxDelay();
+ dst.flags = src.getFlags();
+ return dst;
+}
+
+ndk::ScopedAStatus SensorManagerAidl::getDefaultSensor(SensorType in_type,
+ SensorInfo* _aidl_return) {
+ ::android::Sensor const* sensor =
+ getInternalManager().getDefaultSensor(static_cast<int>(in_type));
+ if (!sensor) {
+ return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_NOT_EXIST);
+ }
+ *_aidl_return = convertSensor(*sensor);
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus SensorManagerAidl::getSensorList(std::vector<SensorInfo>* _aidl_return) {
+ Sensor const* const* list;
+ _aidl_return->clear();
+ ssize_t count = getInternalManager().getSensorList(&list);
+ if (count < 0 || list == nullptr) {
+ LOG(ERROR) << "SensorMAanger::getSensorList failed with count: " << count;
+ return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_UNKNOWN_ERROR);
+ }
+ _aidl_return->reserve(static_cast<size_t>(count));
+ for (ssize_t i = 0; i < count; ++i) {
+ _aidl_return->push_back(convertSensor(*list[i]));
+ }
+
+ return ndk::ScopedAStatus::ok();
+}
+
+::android::SensorManager& SensorManagerAidl::getInternalManager() {
+ std::lock_guard<std::mutex> lock(mInternalManagerMutex);
+ if (mInternalManager == nullptr) {
+ mInternalManager = &::android::SensorManager::getInstanceForPackage(
+ String16(ISensorManager::descriptor));
+ }
+ return *mInternalManager;
+}
+
+/* One global looper for all event queues created from this SensorManager. */
+sp<Looper> SensorManagerAidl::getLooper() {
+ std::lock_guard<std::mutex> lock(mThreadMutex);
+
+ if (!mPollThread.joinable()) {
+ // if thread not initialized, start thread
+ mStopThread = false;
+ std::thread pollThread{[&stopThread = mStopThread, looper = mLooper, javaVm = mJavaVm] {
+ struct sched_param p = {0};
+ p.sched_priority = 10;
+ if (sched_setscheduler(0 /* current thread*/, SCHED_FIFO, &p) != 0) {
+ LOG(ERROR) << "Could not use SCHED_FIFO for looper thread: " << strerror(errno);
+ }
+
+ // set looper
+ Looper::setForThread(looper);
+
+ // Attach the thread to JavaVM so that pollAll do not crash if the thread
+ // eventually calls into Java.
+ JavaVMAttachArgs args{.version = JNI_VERSION_1_2,
+ .name = POLL_THREAD_NAME,
+ .group = nullptr};
+ JNIEnv* env;
+ if (javaVm->AttachCurrentThread(&env, &args) != JNI_OK) {
+ LOG(FATAL) << "Cannot attach SensorManager looper thread to Java VM.";
+ }
+
+ LOG(INFO) << POLL_THREAD_NAME << " started.";
+ for (;;) {
+ int pollResult = looper->pollAll(-1 /* timeout */);
+ if (pollResult == Looper::POLL_WAKE) {
+ if (stopThread == true) {
+ LOG(INFO) << POLL_THREAD_NAME << ": requested to stop";
+ break;
+ } else {
+ LOG(INFO) << POLL_THREAD_NAME << ": spurious wake up, back to work";
+ }
+ } else {
+ LOG(ERROR) << POLL_THREAD_NAME << ": Looper::pollAll returns unexpected "
+ << pollResult;
+ break;
+ }
+ }
+
+ if (javaVm->DetachCurrentThread() != JNI_OK) {
+ LOG(ERROR) << "Cannot detach SensorManager looper thread from Java VM.";
+ }
+
+ LOG(INFO) << POLL_THREAD_NAME << " is terminated.";
+ }};
+ mPollThread = std::move(pollThread);
+ }
+ return mLooper;
+}
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/fuzzer/Android.bp b/services/sensorservice/aidl/fuzzer/Android.bp
new file mode 100644
index 0000000..0d6e476
--- /dev/null
+++ b/services/sensorservice/aidl/fuzzer/Android.bp
@@ -0,0 +1,52 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_native_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_fuzz {
+ name: "libsensorserviceaidl_fuzzer",
+ defaults: [
+ "service_fuzzer_defaults",
+ ],
+ host_supported: true,
+ static_libs: [
+ "libsensorserviceaidl",
+ "libpermission",
+ "android.frameworks.sensorservice-V1-ndk",
+ "android.hardware.sensors-V1-convert",
+ "android.hardware.sensors-V1-ndk",
+ "android.hardware.common-V2-ndk",
+ "libsensor",
+ "libfakeservicemanager",
+ "libcutils",
+ "liblog",
+ ],
+ srcs: [
+ "fuzzer.cpp",
+ ],
+ fuzz_config: {
+ cc: [
+ "android-sensors@google.com",
+ "devinmoore@google.com",
+ ],
+ },
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ "unsigned-integer-overflow",
+ ],
+ diag: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ "unsigned-integer-overflow",
+ ],
+ },
+ address: true,
+ integer_overflow: true,
+ },
+
+}
diff --git a/services/sensorservice/aidl/fuzzer/fuzzer.cpp b/services/sensorservice/aidl/fuzzer/fuzzer.cpp
new file mode 100644
index 0000000..1b63d76
--- /dev/null
+++ b/services/sensorservice/aidl/fuzzer/fuzzer.cpp
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+#include <fuzzbinder/libbinder_ndk_driver.h>
+#include <fuzzer/FuzzedDataProvider.h>
+
+#include <ServiceManager.h>
+#include <android-base/logging.h>
+#include <android/binder_interface_utils.h>
+#include <fuzzbinder/random_binder.h>
+#include <sensorserviceaidl/SensorManagerAidl.h>
+
+using android::fuzzService;
+using android::frameworks::sensorservice::implementation::SensorManagerAidl;
+using ndk::SharedRefBase;
+
+[[clang::no_destroy]] static std::once_flag gSmOnce;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ static android::sp<android::ServiceManager> fakeServiceManager = new android::ServiceManager();
+ std::call_once(gSmOnce, [&] { setDefaultServiceManager(fakeServiceManager); });
+ fakeServiceManager->clear();
+
+ FuzzedDataProvider fdp(data, size);
+ android::sp<android::IBinder> binder = android::getRandomBinder(&fdp);
+ if (binder == nullptr) {
+ // Nothing to do if we get a null binder. It will cause SensorManager to
+ // hang while trying to get sensorservice.
+ return 0;
+ }
+
+ CHECK(android::NO_ERROR == fakeServiceManager->addService(android::String16("sensorservice"),
+ binder));
+
+ std::shared_ptr<SensorManagerAidl> sensorService =
+ ndk::SharedRefBase::make<SensorManagerAidl>(nullptr);
+
+ fuzzService(sensorService->asBinder().get(), std::move(fdp));
+
+ return 0;
+}
diff --git a/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h b/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h
new file mode 100644
index 0000000..c77ee88
--- /dev/null
+++ b/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <aidl/android/frameworks/sensorservice/BnSensorManager.h>
+#include <jni.h>
+#include <sensor/SensorManager.h>
+#include <utils/Looper.h>
+#include <mutex>
+#include <thread>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+class SensorManagerAidl : public ::aidl::android::frameworks::sensorservice::BnSensorManager {
+public:
+ explicit SensorManagerAidl(JavaVM* vm);
+ ~SensorManagerAidl();
+
+ ::ndk::ScopedAStatus createAshmemDirectChannel(
+ const ::aidl::android::hardware::common::Ashmem& in_mem, int64_t in_size,
+ std::shared_ptr<::aidl::android::frameworks::sensorservice::IDirectReportChannel>*
+ _aidl_return) override;
+ ::ndk::ScopedAStatus createEventQueue(
+ const std::shared_ptr<::aidl::android::frameworks::sensorservice::IEventQueueCallback>&
+ in_callback,
+ std::shared_ptr<::aidl::android::frameworks::sensorservice::IEventQueue>* _aidl_return)
+ override;
+ ::ndk::ScopedAStatus createGrallocDirectChannel(
+ const ::ndk::ScopedFileDescriptor& in_buffer, int64_t in_size,
+ std::shared_ptr<::aidl::android::frameworks::sensorservice::IDirectReportChannel>*
+ _aidl_return) override;
+ ::ndk::ScopedAStatus getDefaultSensor(
+ ::aidl::android::hardware::sensors::SensorType in_type,
+ ::aidl::android::hardware::sensors::SensorInfo* _aidl_return) override;
+ ::ndk::ScopedAStatus getSensorList(
+ std::vector<::aidl::android::hardware::sensors::SensorInfo>* _aidl_return) override;
+
+private:
+ // Block until ::android::SensorManager is initialized.
+ ::android::SensorManager& getInternalManager();
+ sp<Looper> getLooper();
+
+ std::mutex mInternalManagerMutex;
+ ::android::SensorManager* mInternalManager = nullptr; // does not own
+ sp<Looper> mLooper;
+
+ volatile bool mStopThread;
+ std::mutex mThreadMutex; // protects mPollThread
+ std::thread mPollThread;
+
+ JavaVM* mJavaVm;
+};
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/utils.cpp b/services/sensorservice/aidl/utils.cpp
new file mode 100644
index 0000000..26bcdc5
--- /dev/null
+++ b/services/sensorservice/aidl/utils.cpp
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+#include "utils.h"
+
+#include <aidl/android/frameworks/sensorservice/ISensorManager.h>
+#include <aidl/sensors/convert.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+ndk::ScopedAStatus convertResult(status_t src) {
+ using ::aidl::android::frameworks::sensorservice::ISensorManager;
+
+ int err = 0;
+ switch (src) {
+ case OK:
+ return ndk::ScopedAStatus::ok();
+ case NAME_NOT_FOUND:
+ err = ISensorManager::RESULT_NOT_EXIST;
+ break;
+ case NO_MEMORY:
+ err = ISensorManager::RESULT_NO_MEMORY;
+ break;
+ case NO_INIT:
+ err = ISensorManager::RESULT_NO_INIT;
+ break;
+ case PERMISSION_DENIED:
+ err = ISensorManager::RESULT_PERMISSION_DENIED;
+ break;
+ case BAD_VALUE:
+ err = ISensorManager::RESULT_BAD_VALUE;
+ break;
+ case INVALID_OPERATION:
+ err = ISensorManager::RESULT_INVALID_OPERATION;
+ break;
+ default:
+ err = ISensorManager::RESULT_UNKNOWN_ERROR;
+ }
+ return ndk::ScopedAStatus::fromServiceSpecificError(err);
+}
+
+::aidl::android::hardware::sensors::Event convertEvent(const ::ASensorEvent& src) {
+ ::aidl::android::hardware::sensors::Event dst;
+ ::android::hardware::sensors::implementation::
+ convertFromSensorEvent(reinterpret_cast<const sensors_event_t&>(src), &dst);
+ return dst;
+}
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/utils.h b/services/sensorservice/aidl/utils.h
new file mode 100644
index 0000000..06ba59e
--- /dev/null
+++ b/services/sensorservice/aidl/utils.h
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <aidl/android/hardware/sensors/Event.h>
+#include <sensor/Sensor.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+::ndk::ScopedAStatus convertResult(status_t status);
+::aidl::android::hardware::sensors::Event convertEvent(const ::ASensorEvent& event);
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 14fdd12..b1bd705 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/LayerLifecycleManager.cpp",
+ "FrontEnd/RequestedLayerState.cpp",
"FrontEnd/TransactionHandler.cpp",
"FlagManager.cpp",
"FpsReporter.cpp",
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 758b346..33caa7a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -139,8 +139,8 @@
MOCK_METHOD(Hwc2::AidlTransform, getPhysicalDisplayOrientation, (PhysicalDisplayId),
(const, override));
MOCK_METHOD(bool, getValidateSkipped, (HalDisplayId), (const, override));
- MOCK_METHOD(status_t, getOverlaySupport,
- (aidl::android::hardware::graphics::composer3::OverlayProperties*));
+ MOCK_METHOD(const aidl::android::hardware::graphics::composer3::OverlayProperties&,
+ getOverlaySupport, (), (const, override));
};
} // namespace mock
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 6e522d3..b40c6d1 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -41,7 +41,7 @@
#include "Display/DisplaySnapshot.h"
#include "DisplayDevice.h"
-#include "FrontEnd/FrontEndDisplayInfo.h"
+#include "FrontEnd/DisplayInfo.h"
#include "Layer.h"
#include "RefreshRateOverlay.h"
#include "SurfaceFlinger.h"
@@ -134,7 +134,7 @@
}
}
-auto DisplayDevice::getFrontEndInfo() const -> FrontEndDisplayInfo {
+auto DisplayDevice::getFrontEndInfo() const -> frontend::DisplayInfo {
gui::DisplayInfo info;
info.displayId = getLayerStack().id;
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index afa13e5..4b64aab 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -45,7 +45,7 @@
#include "DisplayHardware/DisplayMode.h"
#include "DisplayHardware/Hal.h"
#include "DisplayHardware/PowerAdvisor.h"
-#include "FrontEnd/FrontEndDisplayInfo.h"
+#include "FrontEnd/DisplayInfo.h"
#include "Scheduler/RefreshRateSelector.h"
#include "ThreadContext.h"
#include "TracedOrdinal.h"
@@ -167,7 +167,7 @@
void setDisplayName(const std::string& displayName);
const std::string& getDisplayName() const { return mDisplayName; }
- surfaceflinger::FrontEndDisplayInfo getFrontEndInfo() const;
+ surfaceflinger::frontend::DisplayInfo getFrontEndInfo() const;
/* ------------------------------------------------------------------------
* Display power mode management.
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index eff5130..3782c6c 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -542,8 +542,12 @@
return Error::NONE;
}
-Error AidlComposer::getOverlaySupport(AidlOverlayProperties* /*outProperties*/) {
- // TODO(b/242588489): implement details
+Error AidlComposer::getOverlaySupport(AidlOverlayProperties* outProperties) {
+ const auto status = mAidlComposerClient->getOverlaySupport(outProperties);
+ if (!status.isOk()) {
+ ALOGE("getOverlaySupport failed %s", status.getDescription().c_str());
+ return static_cast<Error>(status.getServiceSpecificError());
+ }
return Error::NONE;
}
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index a9337d8..e264570 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -334,9 +334,9 @@
return Error::NONE;
}
-Error Display::getOverlaySupport(OverlayProperties* /*outProperties*/) const {
- // TODO(b/242588489): implement details
- return Error::NONE;
+Error Display::getOverlaySupport(OverlayProperties* outProperties) const {
+ auto intError = mComposer.getOverlaySupport(outProperties);
+ return static_cast<Error>(intError);
}
Error Display::getDisplayedContentSamplingAttributes(hal::PixelFormat* outFormat,
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 5f11cb8..10fde2a 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -96,6 +96,7 @@
void HWComposer::setCallback(HWC2::ComposerCallback& callback) {
loadCapabilities();
loadLayerMetadataSupport();
+ loadOverlayProperties();
if (mRegisteredCallback) {
ALOGW("Callback already registered. Ignored extra registration attempt.");
@@ -652,9 +653,9 @@
return NO_ERROR;
}
-status_t HWComposer::getOverlaySupport(
- aidl::android::hardware::graphics::composer3::OverlayProperties* /*outProperties*/) {
- return NO_ERROR;
+const aidl::android::hardware::graphics::composer3::OverlayProperties&
+HWComposer::getOverlaySupport() const {
+ return mOverlayProperties;
}
int32_t HWComposer::getSupportedPerFrameMetadata(HalDisplayId displayId) const {
@@ -974,6 +975,10 @@
}
}
+void HWComposer::loadOverlayProperties() {
+ mComposer->getOverlaySupport(&mOverlayProperties);
+}
+
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 8235b88..78d4a68 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -179,8 +179,8 @@
// Fetches the HDR capabilities of the given display
virtual status_t getHdrCapabilities(HalDisplayId, HdrCapabilities* outCapabilities) = 0;
- virtual status_t getOverlaySupport(
- aidl::android::hardware::graphics::composer3::OverlayProperties* outProperties) = 0;
+ virtual const aidl::android::hardware::graphics::composer3::OverlayProperties&
+ getOverlaySupport() const = 0;
virtual int32_t getSupportedPerFrameMetadata(HalDisplayId) const = 0;
@@ -366,8 +366,8 @@
// Fetches the HDR capabilities of the given display
status_t getHdrCapabilities(HalDisplayId, HdrCapabilities* outCapabilities) override;
- status_t getOverlaySupport(aidl::android::hardware::graphics::composer3::OverlayProperties*
- outProperties) override;
+ const aidl::android::hardware::graphics::composer3::OverlayProperties& getOverlaySupport()
+ const override;
int32_t getSupportedPerFrameMetadata(HalDisplayId) const override;
@@ -489,11 +489,13 @@
void loadCapabilities();
void loadLayerMetadataSupport();
+ void loadOverlayProperties();
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::unordered_map<std::string, bool> mSupportedLayerGenericMetadata;
bool mRegisteredCallback = false;
diff --git a/services/surfaceflinger/FrontEnd/FrontEndDisplayInfo.h b/services/surfaceflinger/FrontEnd/DisplayInfo.h
similarity index 84%
rename from services/surfaceflinger/FrontEnd/FrontEndDisplayInfo.h
rename to services/surfaceflinger/FrontEnd/DisplayInfo.h
index 95e69b3..0c7b24a 100644
--- a/services/surfaceflinger/FrontEnd/FrontEndDisplayInfo.h
+++ b/services/surfaceflinger/FrontEnd/DisplayInfo.h
@@ -18,11 +18,10 @@
#include <gui/DisplayInfo.h>
-// TODO (b/259553365) fix namespace to be consistent with other components
-namespace android::surfaceflinger {
+namespace android::surfaceflinger::frontend {
// Display information needed to populate input and calculate layer geometry.
-struct FrontEndDisplayInfo {
+struct DisplayInfo {
gui::DisplayInfo info;
ui::Transform transform;
bool receivesInput;
@@ -32,4 +31,4 @@
ui::Transform::RotationFlags rotationFlags;
};
-} // namespace android::surfaceflinger
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
new file mode 100644
index 0000000..1108246
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -0,0 +1,317 @@
+/*
+ * 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 "LayerLifecycleManager"
+
+#include "LayerLifecycleManager.h"
+#include "Layer.h" // temporarily needed for LayerHandle
+#include "LayerHandle.h"
+#include "SwapErase.h"
+
+namespace android::surfaceflinger::frontend {
+
+using namespace ftl::flag_operators;
+
+void LayerLifecycleManager::addLayers(std::vector<std::unique_ptr<RequestedLayerState>> newLayers) {
+ if (newLayers.empty()) {
+ return;
+ }
+
+ mGlobalChanges |= RequestedLayerState::Changes::Hierarchy;
+ for (auto& newLayer : newLayers) {
+ RequestedLayerState& layer = *newLayer.get();
+ auto [it, inserted] = mIdToLayer.try_emplace(layer.id, References{.owner = layer});
+ if (!inserted) {
+ LOG_ALWAYS_FATAL("Duplicate layer id %d found. Existing layer: %s", layer.id,
+ it->second.owner.getDebugString().c_str());
+ }
+
+ linkLayer(layer.parentId, layer.id);
+ linkLayer(layer.relativeParentId, layer.id);
+ linkLayer(layer.mirrorId, layer.id);
+ linkLayer(layer.touchCropId, layer.id);
+
+ mLayers.emplace_back(std::move(newLayer));
+ }
+}
+
+void LayerLifecycleManager::onHandlesDestroyed(const std::vector<uint32_t>& destroyedHandles) {
+ std::vector<uint32_t> layersToBeDestroyed;
+ for (const auto& layerId : destroyedHandles) {
+ auto it = mIdToLayer.find(layerId);
+ if (it == mIdToLayer.end()) {
+ LOG_ALWAYS_FATAL("%s Layerid not found %d", __func__, layerId);
+ continue;
+ }
+ RequestedLayerState& layer = it->second.owner;
+ layer.handleAlive = false;
+ if (!layer.canBeDestroyed()) {
+ continue;
+ }
+ layer.changes |= RequestedLayerState::Changes::Destroyed;
+ layersToBeDestroyed.emplace_back(layerId);
+ }
+
+ if (layersToBeDestroyed.empty()) {
+ return;
+ }
+
+ mGlobalChanges |= RequestedLayerState::Changes::Hierarchy;
+ for (size_t i = 0; i < layersToBeDestroyed.size(); i++) {
+ uint32_t layerId = layersToBeDestroyed[i];
+ auto it = mIdToLayer.find(layerId);
+ if (it == mIdToLayer.end()) {
+ LOG_ALWAYS_FATAL("%s Layer with id %d not found", __func__, layerId);
+ continue;
+ }
+
+ RequestedLayerState& layer = it->second.owner;
+
+ unlinkLayer(layer.parentId, layer.id);
+ unlinkLayer(layer.relativeParentId, layer.id);
+ unlinkLayer(layer.mirrorId, layer.id);
+ unlinkLayer(layer.touchCropId, layer.id);
+
+ auto& references = it->second.references;
+ for (uint32_t linkedLayerId : references) {
+ RequestedLayerState* linkedLayer = getLayerFromId(linkedLayerId);
+ if (!linkedLayer) {
+ LOG_ALWAYS_FATAL("%s Layerid reference %d not found for %d", __func__,
+ linkedLayerId, layer.id);
+ continue;
+ };
+ if (linkedLayer->parentId == layer.id) {
+ linkedLayer->parentId = UNASSIGNED_LAYER_ID;
+ if (linkedLayer->canBeDestroyed()) {
+ linkedLayer->changes |= RequestedLayerState::Changes::Destroyed;
+ layersToBeDestroyed.emplace_back(linkedLayer->id);
+ }
+ }
+ if (linkedLayer->relativeParentId == layer.id) {
+ linkedLayer->relativeParentId = UNASSIGNED_LAYER_ID;
+ }
+ if (linkedLayer->mirrorId == layer.id) {
+ linkedLayer->mirrorId = UNASSIGNED_LAYER_ID;
+ }
+ if (linkedLayer->touchCropId == layer.id) {
+ linkedLayer->touchCropId = UNASSIGNED_LAYER_ID;
+ }
+ }
+ mIdToLayer.erase(it);
+ }
+
+ auto it = mLayers.begin();
+ while (it != mLayers.end()) {
+ RequestedLayerState* layer = it->get();
+ if (layer->changes.test(RequestedLayerState::Changes::Destroyed)) {
+ ALOGV("%s destroyed layer %s", __func__, layer->getDebugStringShort().c_str());
+ std::iter_swap(it, mLayers.end() - 1);
+ mDestroyedLayers.emplace_back(std::move(mLayers.back()));
+ mLayers.erase(mLayers.end() - 1);
+ } else {
+ it++;
+ }
+ }
+}
+
+void LayerLifecycleManager::applyTransactions(const std::vector<TransactionState>& transactions) {
+ for (const auto& transaction : transactions) {
+ for (const auto& resolvedComposerState : transaction.states) {
+ const auto& clientState = resolvedComposerState.state;
+ uint32_t layerId = LayerHandle::getLayerId(clientState.surface);
+ if (layerId == UNASSIGNED_LAYER_ID) {
+ ALOGW("%s Handle %p is not valid", __func__, clientState.surface.get());
+ continue;
+ }
+
+ RequestedLayerState* layer = getLayerFromId(layerId);
+ if (layer == nullptr) {
+ LOG_ALWAYS_FATAL("%s Layer with handle %p (layerid=%d) not found", __func__,
+ clientState.surface.get(), layerId);
+ continue;
+ }
+
+ if (!layer->handleAlive) {
+ LOG_ALWAYS_FATAL("%s Layer's handle %p (layerid=%d) is not alive. Possible out of "
+ "order LayerLifecycleManager updates",
+ __func__, clientState.surface.get(), layerId);
+ continue;
+ }
+
+ uint32_t oldParentId = layer->parentId;
+ uint32_t oldRelativeParentId = layer->relativeParentId;
+ uint32_t oldTouchCropId = layer->touchCropId;
+ layer->merge(resolvedComposerState);
+
+ if (layer->what & layer_state_t::eBackgroundColorChanged) {
+ if (layer->bgColorLayerId == UNASSIGNED_LAYER_ID && layer->bgColorAlpha != 0) {
+ LayerCreationArgs backgroundLayerArgs{nullptr,
+ nullptr,
+ layer->name + "BackgroundColorLayer",
+ ISurfaceComposerClient::eFXSurfaceEffect,
+ {}};
+ std::vector<std::unique_ptr<RequestedLayerState>> newLayers;
+ newLayers.emplace_back(
+ std::make_unique<RequestedLayerState>(backgroundLayerArgs));
+ RequestedLayerState* backgroundLayer = newLayers.back().get();
+ backgroundLayer->handleAlive = false;
+ backgroundLayer->parentId = layer->id;
+ backgroundLayer->z = std::numeric_limits<int32_t>::min();
+ backgroundLayer->color.rgb = layer->color.rgb;
+ backgroundLayer->color.a = layer->bgColorAlpha;
+ backgroundLayer->dataspace = layer->bgColorDataspace;
+
+ layer->bgColorLayerId = backgroundLayer->id;
+ addLayers({std::move(newLayers)});
+ } else if (layer->bgColorLayerId != UNASSIGNED_LAYER_ID &&
+ layer->bgColorAlpha == 0) {
+ RequestedLayerState* bgColorLayer = getLayerFromId(layer->bgColorLayerId);
+ bgColorLayer->parentId = UNASSIGNED_LAYER_ID;
+ onHandlesDestroyed({layer->bgColorLayerId});
+ } else if (layer->bgColorLayerId != UNASSIGNED_LAYER_ID) {
+ RequestedLayerState* bgColorLayer = getLayerFromId(layer->bgColorLayerId);
+ bgColorLayer->color.rgb = layer->color.rgb;
+ bgColorLayer->color.a = layer->bgColorAlpha;
+ bgColorLayer->dataspace = layer->bgColorDataspace;
+ mGlobalChanges |= RequestedLayerState::Changes::Content;
+ }
+ }
+
+ if (oldParentId != layer->parentId) {
+ unlinkLayer(oldParentId, layer->id);
+ linkLayer(layer->parentId, layer->id);
+ }
+ if (oldRelativeParentId != layer->relativeParentId) {
+ unlinkLayer(oldRelativeParentId, layer->id);
+ linkLayer(layer->relativeParentId, layer->id);
+ }
+ if (oldTouchCropId != layer->touchCropId) {
+ unlinkLayer(oldTouchCropId, layer->id);
+ linkLayer(layer->touchCropId, layer->id);
+ }
+
+ mGlobalChanges |= layer->changes &
+ (RequestedLayerState::Changes::Hierarchy |
+ RequestedLayerState::Changes::Geometry |
+ RequestedLayerState::Changes::Content);
+ }
+ }
+}
+
+void LayerLifecycleManager::commitChanges() {
+ for (auto& layer : mLayers) {
+ if (layer->changes.test(RequestedLayerState::Changes::Created)) {
+ for (auto listener : mListeners) {
+ listener->onLayerAdded(*layer);
+ }
+ }
+ layer->what = 0;
+ layer->changes.clear();
+ }
+
+ for (auto& destroyedLayer : mDestroyedLayers) {
+ if (destroyedLayer->changes.test(RequestedLayerState::Changes::Created)) {
+ for (auto listener : mListeners) {
+ listener->onLayerAdded(*destroyedLayer);
+ }
+ }
+
+ for (auto listener : mListeners) {
+ listener->onLayerDestroyed(*destroyedLayer);
+ }
+ }
+ mDestroyedLayers.clear();
+ mGlobalChanges.clear();
+}
+
+void LayerLifecycleManager::addLifecycleListener(std::shared_ptr<ILifecycleListener> listener) {
+ mListeners.emplace_back(std::move(listener));
+}
+
+void LayerLifecycleManager::removeLifecycleListener(std::shared_ptr<ILifecycleListener> listener) {
+ swapErase(mListeners, listener);
+}
+
+const std::vector<std::unique_ptr<RequestedLayerState>>& LayerLifecycleManager::getLayers() const {
+ return mLayers;
+}
+
+const std::vector<std::unique_ptr<RequestedLayerState>>& LayerLifecycleManager::getDestroyedLayers()
+ const {
+ return mDestroyedLayers;
+}
+
+const ftl::Flags<RequestedLayerState::Changes> LayerLifecycleManager::getGlobalChanges() const {
+ return mGlobalChanges;
+}
+
+RequestedLayerState* LayerLifecycleManager::getLayerFromId(uint32_t id) {
+ if (id == UNASSIGNED_LAYER_ID) {
+ return nullptr;
+ }
+ auto it = mIdToLayer.find(id);
+ if (it == mIdToLayer.end()) {
+ return nullptr;
+ }
+ return &it->second.owner;
+}
+
+std::vector<uint32_t>* LayerLifecycleManager::getLinkedLayersFromId(uint32_t id) {
+ if (id == UNASSIGNED_LAYER_ID) {
+ return nullptr;
+ }
+ auto it = mIdToLayer.find(id);
+ if (it == mIdToLayer.end()) {
+ return nullptr;
+ }
+ return &it->second.references;
+}
+
+void LayerLifecycleManager::linkLayer(uint32_t layerId, uint32_t layerToLink) {
+ if (layerToLink && layerId != UNASSIGNED_LAYER_ID) {
+ std::vector<uint32_t>* linkedLayers = getLinkedLayersFromId(layerId);
+ if (!linkedLayers) {
+ LOG_ALWAYS_FATAL("Could not find layer id %d to link %d", layerId, layerToLink);
+ return;
+ }
+ linkedLayers->emplace_back(layerToLink);
+ }
+}
+
+void LayerLifecycleManager::unlinkLayer(uint32_t& inOutLayerId, uint32_t linkedLayer) {
+ uint32_t layerId = inOutLayerId;
+ inOutLayerId = UNASSIGNED_LAYER_ID;
+
+ std::vector<uint32_t>* linkedLayers = getLinkedLayersFromId(layerId);
+ if (!linkedLayers) {
+ return;
+ }
+ swapErase(*linkedLayers, linkedLayer);
+}
+
+std::string LayerLifecycleManager::References::getDebugString() const {
+ std::string debugInfo = owner.name + "[" + std::to_string(owner.id) + "] refs:";
+ std::for_each(references.begin(), references.end(),
+ [&debugInfo = debugInfo](const uint32_t& reference) mutable {
+ debugInfo += std::to_string(reference) + ",";
+ });
+ return debugInfo;
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
new file mode 100644
index 0000000..ad70d3f
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
@@ -0,0 +1,95 @@
+/*
+ * 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 "RequestedLayerState.h"
+#include "TransactionState.h"
+
+namespace android::surfaceflinger::frontend {
+
+// Owns a collection of RequestedLayerStates and manages their lifecycle
+// and state changes.
+//
+// RequestedLayerStates are tracked and destroyed if they have no parent and
+// no handle left to keep them alive. The handle does not keep a reference to
+// the RequestedLayerState but a layer id associated with the RequestedLayerState.
+// If the handle is destroyed and the RequestedLayerState does not have a parent,
+// the LayerLifecycleManager destroys the RequestedLayerState.
+//
+// Threading: This class is not thread safe, it requires external synchronization.
+//
+// Typical usage: Input states (new layers, transactions, destroyed layer handles)
+// are collected in the background passed into the LayerLifecycleManager to update
+// layer lifecycle and layer state at start of composition.
+class LayerLifecycleManager {
+public:
+ // External state changes should be updated in the following order:
+ void addLayers(std::vector<std::unique_ptr<RequestedLayerState>>);
+ void applyTransactions(const std::vector<TransactionState>&);
+ void onHandlesDestroyed(const std::vector<uint32_t>&);
+
+ // Destroys RequestedLayerStates that are marked to be destroyed. Invokes all
+ // ILifecycleListener callbacks and clears any change flags from previous state
+ // updates. This function should be called outside the hot path since it's not
+ // critical to composition.
+ void commitChanges();
+
+ class ILifecycleListener {
+ public:
+ virtual ~ILifecycleListener() = default;
+ // Called on commitChanges when a layer is added. The callback includes
+ // the layer state the client was created with as well as any state updates
+ // until changes were committed.
+ virtual void onLayerAdded(const RequestedLayerState&) = 0;
+ // Called on commitChanges when a layer has been destroyed. The callback
+ // includes the final state before the layer was destroyed.
+ virtual void onLayerDestroyed(const RequestedLayerState&) = 0;
+ };
+ void addLifecycleListener(std::shared_ptr<ILifecycleListener>);
+ void removeLifecycleListener(std::shared_ptr<ILifecycleListener>);
+ const std::vector<std::unique_ptr<RequestedLayerState>>& getLayers() const;
+ const std::vector<std::unique_ptr<RequestedLayerState>>& getDestroyedLayers() const;
+ const ftl::Flags<RequestedLayerState::Changes> getGlobalChanges() const;
+
+private:
+ friend class LayerLifecycleManagerTest;
+ friend class HierarchyBuilderTest;
+ friend class android::SurfaceFlinger;
+
+ RequestedLayerState* getLayerFromId(uint32_t);
+ std::vector<uint32_t>* getLinkedLayersFromId(uint32_t);
+ void linkLayer(uint32_t layerId, uint32_t layerToLink);
+ void unlinkLayer(uint32_t& inOutLayerId, uint32_t linkedLayer);
+
+ struct References {
+ // Lifetime tied to mLayers
+ RequestedLayerState& owner;
+ std::vector<uint32_t> references;
+ std::string getDebugString() const;
+ };
+ std::unordered_map<uint32_t, References> mIdToLayer;
+ // Listeners are invoked once changes are committed.
+ std::vector<std::shared_ptr<ILifecycleListener>> mListeners;
+
+ // Aggregation of changes since last commit.
+ ftl::Flags<RequestedLayerState::Changes> mGlobalChanges;
+ std::vector<std::unique_ptr<RequestedLayerState>> mLayers;
+ // Layers pending destruction. Layers will be destroyed once changes are committed.
+ std::vector<std::unique_ptr<RequestedLayerState>> mDestroyedLayers;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
new file mode 100644
index 0000000..45058d9
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -0,0 +1,358 @@
+/*
+ * 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 "FrontEnd/LayerCreationArgs.h"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#undef LOG_TAG
+#define LOG_TAG "RequestedLayerState"
+
+#include <private/android_filesystem_config.h>
+#include <sys/types.h>
+
+#include "Layer.h"
+#include "LayerHandle.h"
+#include "RequestedLayerState.h"
+
+namespace android::surfaceflinger::frontend {
+using ftl::Flags;
+using namespace ftl::flag_operators;
+
+namespace {
+uint32_t getLayerIdFromSurfaceControl(sp<SurfaceControl> surfaceControl) {
+ if (!surfaceControl) {
+ return UNASSIGNED_LAYER_ID;
+ }
+
+ return LayerHandle::getLayerId(surfaceControl->getHandle());
+}
+
+std::string layerIdToString(uint32_t layerId) {
+ return layerId == UNASSIGNED_LAYER_ID ? std::to_string(layerId) : "none";
+}
+
+} // namespace
+
+RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args)
+ : id(args.sequence),
+ name(args.name),
+ canBeRoot(args.addToRoot),
+ layerCreationFlags(args.flags),
+ textureName(args.textureName),
+ ownerUid(args.ownerUid),
+ ownerPid(args.ownerPid) {
+ layerId = static_cast<int32_t>(args.sequence);
+ changes |= RequestedLayerState::Changes::Created;
+ metadata.merge(args.metadata);
+ changes |= RequestedLayerState::Changes::Metadata;
+ handleAlive = true;
+ parentId = LayerHandle::getLayerId(args.parentHandle.promote());
+ mirrorId = LayerHandle::getLayerId(args.mirrorLayerHandle.promote());
+ if (mirrorId != UNASSIGNED_LAYER_ID) {
+ changes |= RequestedLayerState::Changes::Mirror;
+ }
+
+ flags = 0;
+ if (args.flags & ISurfaceComposerClient::eHidden) flags |= layer_state_t::eLayerHidden;
+ if (args.flags & ISurfaceComposerClient::eOpaque) flags |= layer_state_t::eLayerOpaque;
+ if (args.flags & ISurfaceComposerClient::eSecure) flags |= layer_state_t::eLayerSecure;
+ if (args.flags & ISurfaceComposerClient::eSkipScreenshot) {
+ flags |= layer_state_t::eLayerSkipScreenshot;
+ }
+ premultipliedAlpha = !(args.flags & ISurfaceComposerClient::eNonPremultiplied);
+ potentialCursor = args.flags & ISurfaceComposerClient::eCursorWindow;
+ protectedByApp = args.flags & ISurfaceComposerClient::eProtectedByApp;
+ if (args.flags & ISurfaceComposerClient::eNoColorFill) {
+ // Set an invalid color so there is no color fill.
+ // (b/259981098) use an explicit flag instead of relying on invalid values.
+ color.r = -1.0_hf;
+ color.g = -1.0_hf;
+ color.b = -1.0_hf;
+ } else {
+ color.rgb = {0.0_hf, 0.0_hf, 0.0_hf};
+ }
+ color.a = 1.0f;
+
+ crop.makeInvalid();
+ z = 0;
+ layerStack = ui::DEFAULT_LAYER_STACK;
+ transformToDisplayInverse = false;
+ dataspace = ui::Dataspace::UNKNOWN;
+ dataspaceRequested = false;
+ hdrMetadata.validTypes = 0;
+ surfaceDamageRegion = Region::INVALID_REGION;
+ cornerRadius = 0.0f;
+ backgroundBlurRadius = 0;
+ api = -1;
+ hasColorTransform = false;
+ bufferTransform = 0;
+ requestedTransform.reset();
+ bufferData = std::make_shared<BufferData>();
+ bufferData->frameNumber = 0;
+ bufferData->acquireFence = sp<Fence>::make(-1);
+ acquireFenceTime = std::make_shared<FenceTime>(bufferData->acquireFence);
+ colorSpaceAgnostic = false;
+ frameRateSelectionPriority = Layer::PRIORITY_UNSET;
+ shadowRadius = 0.f;
+ fixedTransformHint = ui::Transform::ROT_INVALID;
+ destinationFrame.makeInvalid();
+ isTrustedOverlay = false;
+ dropInputMode = gui::DropInputMode::NONE;
+ dimmingEnabled = true;
+ defaultFrameRateCompatibility =
+ static_cast<int8_t>(scheduler::LayerInfo::FrameRateCompatibility::Default);
+ dataspace = ui::Dataspace::V0_SRGB;
+}
+
+void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerState) {
+ bool oldFlags = flags;
+ Rect oldBufferSize = getBufferSize();
+ const layer_state_t& clientState = resolvedComposerState.state;
+
+ uint64_t clientChanges = what | layer_state_t::diff(clientState);
+ layer_state_t::merge(clientState);
+ what = clientChanges;
+
+ if (clientState.what & layer_state_t::eFlagsChanged) {
+ if ((oldFlags ^ flags) & layer_state_t::eLayerHidden) {
+ changes |= RequestedLayerState::Changes::Visibility;
+ }
+ if ((oldFlags ^ flags) & layer_state_t::eIgnoreDestinationFrame) {
+ changes |= RequestedLayerState::Changes::Geometry;
+ }
+ }
+ if (clientState.what & layer_state_t::eBufferChanged && oldBufferSize != getBufferSize()) {
+ changes |= RequestedLayerState::Changes::Geometry;
+ }
+ if (clientChanges & layer_state_t::HIERARCHY_CHANGES)
+ changes |= RequestedLayerState::Changes::Hierarchy;
+ if (clientChanges & layer_state_t::CONTENT_CHANGES)
+ changes |= RequestedLayerState::Changes::Content;
+ if (clientChanges & layer_state_t::GEOMETRY_CHANGES)
+ changes |= RequestedLayerState::Changes::Geometry;
+
+ if (clientState.what & layer_state_t::eColorTransformChanged) {
+ static const mat4 identityMatrix = mat4();
+ hasColorTransform = colorTransform != identityMatrix;
+ }
+ if (clientState.what & layer_state_t::eLayerChanged) {
+ changes |= RequestedLayerState::Changes::Z;
+ }
+ if (clientState.what & layer_state_t::eReparent) {
+ changes |= RequestedLayerState::Changes::Parent;
+ parentId = getLayerIdFromSurfaceControl(clientState.parentSurfaceControlForChild);
+ parentSurfaceControlForChild = nullptr;
+ }
+ if (clientState.what & layer_state_t::eRelativeLayerChanged) {
+ changes |= RequestedLayerState::Changes::RelativeParent;
+ relativeParentId = getLayerIdFromSurfaceControl(clientState.relativeLayerSurfaceControl);
+ isRelativeOf = true;
+ relativeLayerSurfaceControl = nullptr;
+ }
+ if ((clientState.what & layer_state_t::eLayerChanged ||
+ (clientState.what & layer_state_t::eReparent && parentId == UNASSIGNED_LAYER_ID)) &&
+ isRelativeOf) {
+ // clear out relz data
+ relativeParentId = UNASSIGNED_LAYER_ID;
+ isRelativeOf = false;
+ changes |= RequestedLayerState::Changes::RelativeParent;
+ }
+ if (clientState.what & layer_state_t::eReparent && parentId == relativeParentId) {
+ // provide a hint that we are are now a direct child and not a relative child.
+ changes |= RequestedLayerState::Changes::RelativeParent;
+ }
+ if (clientState.what & layer_state_t::eInputInfoChanged) {
+ wp<IBinder>& touchableRegionCropHandle =
+ windowInfoHandle->editInfo()->touchableRegionCropHandle;
+ touchCropId = LayerHandle::getLayerId(touchableRegionCropHandle.promote());
+ changes |= RequestedLayerState::Changes::Input;
+ touchableRegionCropHandle.clear();
+ }
+ if (clientState.what & layer_state_t::eStretchChanged) {
+ stretchEffect.sanitize();
+ }
+
+ if (clientState.what & layer_state_t::eHasListenerCallbacksChanged) {
+ // TODO(b/238781169) handle callbacks
+ }
+
+ if (clientState.what & layer_state_t::eBufferChanged) {
+ externalTexture = resolvedComposerState.externalTexture;
+ hwcBufferSlot = resolvedComposerState.hwcBufferSlot;
+ }
+
+ if (clientState.what & layer_state_t::ePositionChanged) {
+ requestedTransform.set(x, y);
+ }
+
+ if (clientState.what & layer_state_t::eMatrixChanged) {
+ requestedTransform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy);
+ }
+}
+
+ui::Transform RequestedLayerState::getTransform() 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.
+ return requestedTransform;
+ }
+
+ Rect destRect = destinationFrame;
+ int32_t destW = destRect.width();
+ int32_t destH = destRect.height();
+ if (destRect.left < 0) {
+ destRect.left = 0;
+ destRect.right = destW;
+ }
+ if (destRect.top < 0) {
+ destRect.top = 0;
+ destRect.bottom = destH;
+ }
+
+ if (!externalTexture) {
+ ui::Transform transform;
+ transform.set(static_cast<float>(destRect.left), static_cast<float>(destRect.top));
+ 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);
+ }
+ }
+
+ float sx = static_cast<float>(destW) / static_cast<float>(bufferWidth);
+ float sy = static_cast<float>(destH) / static_cast<float>(bufferHeight);
+ ui::Transform transform;
+ transform.set(sx, 0, 0, sy);
+ transform.set(static_cast<float>(destRect.left), static_cast<float>(destRect.top));
+ return transform;
+}
+
+std::string RequestedLayerState::getDebugString() const {
+ return "[" + std::to_string(id) + "]" + name + ",parent=" + layerIdToString(parentId) +
+ ",relativeParent=" + layerIdToString(relativeParentId) +
+ ",isRelativeOf=" + std::to_string(isRelativeOf) +
+ ",mirrorId=" + layerIdToString(mirrorId) +
+ ",handleAlive=" + std::to_string(handleAlive);
+}
+
+std::string RequestedLayerState::getDebugStringShort() const {
+ return "[" + std::to_string(id) + "]" + name;
+}
+
+bool RequestedLayerState::canBeDestroyed() const {
+ return !handleAlive && parentId == UNASSIGNED_LAYER_ID;
+}
+bool RequestedLayerState::isRoot() const {
+ return canBeRoot && parentId == UNASSIGNED_LAYER_ID;
+}
+bool RequestedLayerState::isHiddenByPolicy() const {
+ return (flags & layer_state_t::eLayerHidden) == layer_state_t::eLayerHidden;
+};
+half4 RequestedLayerState::getColor() const {
+ if ((sidebandStream != nullptr) || (externalTexture != nullptr)) {
+ return {0._hf, 0._hf, 0._hf, color.a};
+ }
+ return color;
+}
+Rect RequestedLayerState::getBufferSize() const {
+ // for buffer state layers we use the display frame size as the buffer size.
+ if (!externalTexture) {
+ return Rect::INVALID_RECT;
+ }
+
+ uint32_t bufWidth = externalTexture->getWidth();
+ uint32_t bufHeight = externalTexture->getHeight();
+
+ // Undo any transformations on the buffer and return the result.
+ if (bufferTransform & ui::Transform::ROT_90) {
+ std::swap(bufWidth, bufHeight);
+ }
+
+ if (transformToDisplayInverse) {
+ // TODO(b/238781169) pass in display metrics (would be useful for input info as well
+ uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags();
+ if (invTransform & ui::Transform::ROT_90) {
+ std::swap(bufWidth, bufHeight);
+ }
+ }
+
+ return Rect(0, 0, static_cast<int32_t>(bufWidth), static_cast<int32_t>(bufHeight));
+}
+
+Rect RequestedLayerState::getCroppedBufferSize() const {
+ Rect size = getBufferSize();
+ if (!crop.isEmpty() && size.isValid()) {
+ size.intersect(crop, &size);
+ } else if (!crop.isEmpty()) {
+ size = crop;
+ }
+ return size;
+}
+
+Rect RequestedLayerState::getBufferCrop() const {
+ // this is the crop rectangle that applies to the buffer
+ // itself (as opposed to the window)
+ if (!bufferCrop.isEmpty()) {
+ // if the buffer crop is defined, we use that
+ return bufferCrop;
+ } else if (externalTexture != nullptr) {
+ // otherwise we use the whole buffer
+ return externalTexture->getBounds();
+ } else {
+ // if we don't have a buffer yet, we use an empty/invalid crop
+ return Rect();
+ }
+}
+
+aidl::android::hardware::graphics::composer3::Composition RequestedLayerState::getCompositionType()
+ const {
+ using aidl::android::hardware::graphics::composer3::Composition;
+ // TODO(b/238781169) check about sidestream ready flag
+ if (sidebandStream.get()) {
+ return Composition::SIDEBAND;
+ }
+ if (!externalTexture) {
+ return Composition::SOLID_COLOR;
+ }
+ if (flags & layer_state_t::eLayerIsDisplayDecoration) {
+ return Composition::DISPLAY_DECORATION;
+ }
+ if (potentialCursor) {
+ return Composition::CURSOR;
+ }
+ return Composition::DEVICE;
+}
+
+Rect RequestedLayerState::reduce(const Rect& win, const Region& exclude) {
+ if (CC_LIKELY(exclude.isEmpty())) {
+ return win;
+ }
+ if (exclude.isRect()) {
+ return win.reduce(exclude.getBounds());
+ }
+ return Region(win).subtract(exclude).getBounds();
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
new file mode 100644
index 0000000..0ddf5e2
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -0,0 +1,102 @@
+/*
+ * 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 <aidl/android/hardware/graphics/composer3/Composition.h>
+#include <ftl/flags.h>
+#include <gui/LayerState.h>
+#include <renderengine/ExternalTexture.h>
+
+#include "LayerCreationArgs.h"
+#include "TransactionState.h"
+
+namespace android::surfaceflinger::frontend {
+
+// Stores client requested states for a layer.
+// This struct does not store any other states or states pertaining to
+// other layers. Links to other layers that are part of the client
+// requested state such as parent are translated to layer id so
+// we can avoid extending the lifetime of layer handles.
+struct RequestedLayerState : layer_state_t {
+ // Changes in state after merging with new state. This includes additional state
+ // changes found in layer_state_t::what.
+ enum class Changes : uint32_t {
+ Created = 1u << 0,
+ Destroyed = 1u << 1,
+ Hierarchy = 1u << 2,
+ Geometry = 1u << 3,
+ Content = 1u << 4,
+ Input = 1u << 5,
+ Z = 1u << 6,
+ Mirror = 1u << 7,
+ Parent = 1u << 8,
+ RelativeParent = 1u << 9,
+ Metadata = 1u << 10,
+ Visibility = 1u << 11,
+ };
+ static Rect reduce(const Rect& win, const Region& exclude);
+ RequestedLayerState(const LayerCreationArgs&);
+ void merge(const ResolvedComposerState&);
+ ui::Transform getTransform() const;
+ bool canBeDestroyed() const;
+ bool isRoot() const;
+ bool isHiddenByPolicy() const;
+ half4 getColor() const;
+ Rect getBufferSize() const;
+ Rect getCroppedBufferSize() const;
+ Rect getBufferCrop() const;
+ std::string getDebugString() const;
+ std::string getDebugStringShort() const;
+ aidl::android::hardware::graphics::composer3::Composition getCompositionType() 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
+ // the same.
+ const uint32_t id;
+ const std::string name;
+ const bool canBeRoot = false;
+ const uint32_t layerCreationFlags;
+ const uint32_t textureName;
+ // The owner of the layer. If created from a non system process, it will be the calling uid.
+ // If created from a system process, the value can be passed in.
+ const uid_t ownerUid;
+ // The owner pid of the layer. If created from a non system process, it will be the calling pid.
+ // If created from a system process, the value can be passed in.
+ const pid_t ownerPid;
+ bool dataspaceRequested;
+ bool hasColorTransform;
+ bool premultipliedAlpha{true};
+ // This layer can be a cursor on some displays.
+ bool potentialCursor{false};
+ bool protectedByApp{false}; // application requires protected path to external sink
+ ui::Transform requestedTransform;
+ std::shared_ptr<FenceTime> acquireFenceTime;
+ std::shared_ptr<renderengine::ExternalTexture> externalTexture;
+ int hwcBufferSlot = 0;
+
+ // book keeping states
+ bool handleAlive = true;
+ bool isRelativeOf = false;
+ uint32_t parentId = UNASSIGNED_LAYER_ID;
+ uint32_t relativeParentId = UNASSIGNED_LAYER_ID;
+ uint32_t mirrorId = UNASSIGNED_LAYER_ID;
+ uint32_t touchCropId = UNASSIGNED_LAYER_ID;
+ uint32_t bgColorLayerId = UNASSIGNED_LAYER_ID;
+ ftl::Flags<RequestedLayerState::Changes> changes;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/SwapErase.h b/services/surfaceflinger/FrontEnd/SwapErase.h
new file mode 100644
index 0000000..f672f99
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/SwapErase.h
@@ -0,0 +1,45 @@
+/*
+ * 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 <vector>
+
+namespace android::surfaceflinger::frontend {
+// Erases the first element in vec that matches value. This is a more optimal way to
+// remove an element from a vector that avoids relocating all the elements after the one
+// that is erased.
+template <typename T>
+void swapErase(std::vector<T>& vec, const T& value) {
+ auto it = std::find(vec.begin(), vec.end(), value);
+ if (it != vec.end()) {
+ std::iter_swap(it, vec.end() - 1);
+ vec.erase(vec.end() - 1);
+ }
+}
+
+// Similar to swapErase(std::vector<T>& vec, const T& value) but erases the first element
+// that returns true for predicate.
+template <typename T, class P>
+void swapErase(std::vector<T>& vec, P predicate) {
+ auto it = std::find_if(vec.begin(), vec.end(), predicate);
+ if (it != vec.end()) {
+ std::iter_swap(it, vec.end() - 1);
+ vec.erase(vec.end() - 1);
+ }
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
index 95961fe..8629671 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
@@ -24,7 +24,7 @@
#include "TransactionHandler.h"
-namespace android {
+namespace android::surfaceflinger::frontend {
void TransactionHandler::queueTransaction(TransactionState&& state) {
mLocklessTransactionQueue.push(std::move(state));
@@ -186,4 +186,4 @@
mStalledTransactions.erase(it);
}
}
-} // namespace android
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h
index 2b6f07d..a06b870 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.h
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h
@@ -18,9 +18,6 @@
#include <semaphore.h>
#include <cstdint>
-#include <mutex>
-#include <queue>
-#include <thread>
#include <vector>
#include <LocklessQueue.h>
@@ -30,6 +27,10 @@
#include <ftl/small_vector.h>
namespace android {
+
+class TestableSurfaceFlinger;
+namespace surfaceflinger::frontend {
+
class TransactionHandler {
public:
struct TransactionFlushState {
@@ -60,7 +61,7 @@
private:
// For unit tests
- friend class TestableSurfaceFlinger;
+ friend class ::android::TestableSurfaceFlinger;
int flushPendingTransactionQueues(std::vector<TransactionState>&, TransactionFlushState&);
TransactionReadiness applyFilters(TransactionFlushState&);
@@ -71,5 +72,5 @@
ftl::SmallVector<TransactionFilter, 2> mTransactionReadyFilters;
std::vector<uint64_t> mStalledTransactions;
};
-
+} // namespace surfaceflinger::frontend
} // namespace android
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 8a3e784..d53f0b1 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1123,7 +1123,7 @@
// We return whether this layer ot its children has a vote. We ignore ExactOrMultiple votes for
// the same reason we are allowing touch boost for those layers. See
- // RefreshRateSelector::rankRefreshRates for details.
+ // RefreshRateSelector::rankFrameRates for details.
const auto layerVotedWithDefaultCompatibility =
frameRate.rate.isValid() && frameRate.type == FrameRateCompatibility::Default;
const auto layerVotedWithNoVote = frameRate.type == FrameRateCompatibility::NoVote;
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 7669bab..f743896 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -83,6 +83,7 @@
} // namespace frametimeline
class Layer : public virtual RefBase {
+public:
// The following constants represent priority of the window. SF uses this information when
// deciding which window has a priority when deciding about the refresh rate of the screen.
// Priority 0 is considered the highest priority. -1 means that the priority is unset.
@@ -94,7 +95,6 @@
// Windows that are not in focus, but voted for a specific mode ID.
static constexpr int32_t PRIORITY_NOT_FOCUSED_WITH_MODE = 2;
-public:
enum { // flags for doTransaction()
eDontUpdateGeometryState = 0x00000001,
eVisibleRegion = 0x00000002,
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 8d4ea05..fd1a733 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -24,6 +24,7 @@
#include <chrono>
#include <cmath>
#include <deque>
+#include <map>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
@@ -31,6 +32,7 @@
#include <ftl/fake_guard.h>
#include <ftl/match.h>
#include <ftl/unit.h>
+#include <scheduler/FrameRateMode.h>
#include <utils/Trace.h>
#include "../SurfaceFlingerProperties.h"
@@ -43,7 +45,7 @@
namespace {
struct RefreshRateScore {
- DisplayModeIterator modeIt;
+ FrameRateMode frameRateMode;
float overallScore;
struct {
float modeBelowThreshold;
@@ -77,19 +79,11 @@
return knownFrameRates;
}
-// The Filter is a `bool(const DisplayMode&)` predicate.
-template <typename Filter>
-std::vector<DisplayModeIterator> sortByRefreshRate(const DisplayModes& modes, Filter&& filter) {
+std::vector<DisplayModeIterator> sortByRefreshRate(const DisplayModes& modes) {
std::vector<DisplayModeIterator> sortedModes;
sortedModes.reserve(modes.size());
-
for (auto it = modes.begin(); it != modes.end(); ++it) {
- const auto& [id, mode] = *it;
-
- if (filter(*mode)) {
- ALOGV("%s: including mode %d", __func__, id.value());
- sortedModes.push_back(it);
- }
+ sortedModes.push_back(it);
}
std::sort(sortedModes.begin(), sortedModes.end(), [](auto it1, auto it2) {
@@ -106,6 +100,21 @@
return sortedModes;
}
+std::pair<unsigned, unsigned> divisorRange(Fps fps, FpsRange range,
+ RefreshRateSelector::Config::FrameRateOverride config) {
+ if (config != RefreshRateSelector::Config::FrameRateOverride::Enabled) {
+ return {1, 1};
+ }
+
+ using fps_approx_ops::operator/;
+ const auto start = std::max(1u, fps / range.max - 1);
+ const auto end = fps /
+ std::max(range.min, RefreshRateSelector::kMinSupportedFrameRate,
+ fps_approx_ops::operator<);
+
+ return {start, end};
+}
+
bool shouldEnableFrameRateOverride(const std::vector<DisplayModeIterator>& sortedModes) {
for (const auto it1 : sortedModes) {
const auto& mode1 = it1->second;
@@ -136,27 +145,109 @@
} // namespace
+auto RefreshRateSelector::createFrameRateModes(
+ std::function<bool(const DisplayMode&)>&& filterModes, const FpsRange& renderRange) const
+ -> std::vector<FrameRateMode> {
+ struct Key {
+ Fps fps;
+ int32_t group;
+ };
+
+ struct KeyLess {
+ bool operator()(const Key& a, const Key& b) const {
+ using namespace fps_approx_ops;
+ if (a.fps != b.fps) {
+ return a.fps < b.fps;
+ }
+
+ // For the same fps the order doesn't really matter, but we still
+ // want the behaviour of a strictly less operator.
+ // We use the group id as the secondary ordering for that.
+ return a.group < b.group;
+ }
+ };
+
+ std::map<Key, DisplayModeIterator, KeyLess> ratesMap;
+ for (auto it = mDisplayModes.begin(); it != mDisplayModes.end(); ++it) {
+ const auto& [id, mode] = *it;
+
+ if (!filterModes(*mode)) {
+ continue;
+ }
+ const auto [start, end] =
+ divisorRange(mode->getFps(), renderRange, mConfig.enableFrameRateOverride);
+ for (auto divisor = start; divisor <= end; divisor++) {
+ const auto fps = mode->getFps() / divisor;
+ using fps_approx_ops::operator<;
+ if (fps < kMinSupportedFrameRate) {
+ break;
+ }
+
+ if (mConfig.enableFrameRateOverride == Config::FrameRateOverride::Enabled &&
+ !renderRange.includes(fps)) {
+ continue;
+ }
+
+ if (mConfig.enableFrameRateOverride ==
+ Config::FrameRateOverride::AppOverrideNativeRefreshRates &&
+ !isNativeRefreshRate(fps)) {
+ continue;
+ }
+
+ const auto [existingIter, emplaceHappened] =
+ ratesMap.try_emplace(Key{fps, mode->getGroup()}, it);
+ if (emplaceHappened) {
+ ALOGV("%s: including %s (%s)", __func__, to_string(fps).c_str(),
+ to_string(mode->getFps()).c_str());
+ } else {
+ // We might need to update the map as we found a lower refresh rate
+ if (isStrictlyLess(mode->getFps(), existingIter->second->second->getFps())) {
+ existingIter->second = it;
+ ALOGV("%s: changing %s (%s)", __func__, to_string(fps).c_str(),
+ to_string(mode->getFps()).c_str());
+ }
+ }
+ }
+ }
+
+ std::vector<FrameRateMode> frameRateModes;
+ frameRateModes.reserve(ratesMap.size());
+ for (const auto& [key, mode] : ratesMap) {
+ frameRateModes.emplace_back(FrameRateMode{key.fps, mode->second});
+ }
+
+ // We always want that the lowest frame rate will be corresponding to the
+ // lowest mode for power saving.
+ const auto lowestRefreshRateIt =
+ std::min_element(frameRateModes.begin(), frameRateModes.end(),
+ [](const FrameRateMode& lhs, const FrameRateMode& rhs) {
+ return isStrictlyLess(lhs.modePtr->getFps(),
+ rhs.modePtr->getFps());
+ });
+ frameRateModes.erase(frameRateModes.begin(), lowestRefreshRateIt);
+
+ return frameRateModes;
+}
+
struct RefreshRateSelector::RefreshRateScoreComparator {
bool operator()(const RefreshRateScore& lhs, const RefreshRateScore& rhs) const {
- const auto& [modeIt, overallScore, _] = lhs;
+ const auto& [frameRateMode, overallScore, _] = lhs;
- std::string name = to_string(modeIt->second->getFps());
+ std::string name = to_string(frameRateMode);
+
ALOGV("%s sorting scores %.2f", name.c_str(), overallScore);
-
ATRACE_INT(name.c_str(), static_cast<int>(std::round(overallScore * 100)));
- if (!ScoredRefreshRate::scoresEqual(overallScore, rhs.overallScore)) {
+ if (!ScoredFrameRate::scoresEqual(overallScore, rhs.overallScore)) {
return overallScore > rhs.overallScore;
}
- // If overallScore tie we will pick the higher refresh rate if
- // high refresh rate is the priority else the lower refresh rate.
if (refreshRateOrder == RefreshRateOrder::Descending) {
using fps_approx_ops::operator>;
- return modeIt->second->getFps() > rhs.modeIt->second->getFps();
+ return frameRateMode.fps > rhs.frameRateMode.fps;
} else {
using fps_approx_ops::operator<;
- return modeIt->second->getFps() < rhs.modeIt->second->getFps();
+ return frameRateMode.fps < rhs.frameRateMode.fps;
}
}
@@ -210,7 +301,15 @@
if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||
layer.vote == LayerVoteType::Heuristic) {
- if (isFractionalPairOrMultiple(refreshRate, layer.desiredRefreshRate)) {
+ const float multiplier = refreshRate.getValue() / layer.desiredRefreshRate.getValue();
+
+ // We only want to score this layer as a fractional pair if the content is not
+ // significantly faster than the display rate, at it would cause a significant frame drop.
+ // It is more appropriate to choose a higher display rate even if
+ // a pull-down will be required.
+ constexpr float kMinMultiplier = 0.25f;
+ if (multiplier >= kMinMultiplier &&
+ isFractionalPairOrMultiple(refreshRate, layer.desiredRefreshRate)) {
return kScoreForFractionalPairs;
}
@@ -245,9 +344,9 @@
return 0;
}
-float RefreshRateSelector::calculateRefreshRateScoreForFps(Fps refreshRate) const {
- const float ratio =
- refreshRate.getValue() / mAppRequestRefreshRates.back()->second->getFps().getValue();
+float RefreshRateSelector::calculateDistanceScoreFromMax(Fps refreshRate) const {
+ const auto& maxFps = mAppRequestFrameRates.back().fps;
+ const float ratio = refreshRate.getValue() / maxFps.getValue();
// Use ratio^2 to get a lower score the more we get further from peak
return ratio * ratio;
}
@@ -260,12 +359,12 @@
// If the layer wants Max, give higher score to the higher refresh rate
if (layer.vote == LayerVoteType::Max) {
- return calculateRefreshRateScoreForFps(refreshRate);
+ return calculateDistanceScoreFromMax(refreshRate);
}
if (layer.vote == LayerVoteType::ExplicitExact) {
const int divisor = getFrameRateDivisor(refreshRate, layer.desiredRefreshRate);
- if (supportsFrameRateOverrideByContent()) {
+ if (supportsAppFrameRateOverrideByContent()) {
// Since we support frame rate override, allow refresh rates which are
// multiples of the layer's request, as those apps would be throttled
// down to run at the desired refresh rate.
@@ -289,33 +388,33 @@
kNonExactMatchingPenalty;
}
-auto RefreshRateSelector::getRankedRefreshRates(const std::vector<LayerRequirement>& layers,
- GlobalSignals signals) const -> RankedRefreshRates {
+auto RefreshRateSelector::getRankedFrameRates(const std::vector<LayerRequirement>& layers,
+ GlobalSignals signals) const -> RankedFrameRates {
std::lock_guard lock(mLock);
- if (mGetRankedRefreshRatesCache &&
- mGetRankedRefreshRatesCache->arguments == std::make_pair(layers, signals)) {
- return mGetRankedRefreshRatesCache->result;
+ if (mGetRankedFrameRatesCache &&
+ mGetRankedFrameRatesCache->arguments == std::make_pair(layers, signals)) {
+ return mGetRankedFrameRatesCache->result;
}
- const auto result = getRankedRefreshRatesLocked(layers, signals);
- mGetRankedRefreshRatesCache = GetRankedRefreshRatesCache{{layers, signals}, result};
+ const auto result = getRankedFrameRatesLocked(layers, signals);
+ mGetRankedFrameRatesCache = GetRankedFrameRatesCache{{layers, signals}, result};
return result;
}
-auto RefreshRateSelector::getRankedRefreshRatesLocked(const std::vector<LayerRequirement>& layers,
- GlobalSignals signals) const
- -> RankedRefreshRates {
+auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
+ GlobalSignals signals) const
+ -> RankedFrameRates {
using namespace fps_approx_ops;
ATRACE_CALL();
ALOGV("%s: %zu layers", __func__, layers.size());
const auto& activeMode = *getActiveModeItLocked()->second;
- // Keep the display at max refresh rate for the duration of powering on the display.
+ // Keep the display at max frame rate for the duration of powering on the display.
if (signals.powerOnImminent) {
ALOGV("Power On Imminent");
- return {rankRefreshRates(activeMode.getGroup(), RefreshRateOrder::Descending),
+ return {rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Descending),
GlobalSignals{.powerOnImminent = true}};
}
@@ -375,7 +474,7 @@
// selected a refresh rate to see if we should apply touch boost.
if (signals.touch && !hasExplicitVoteLayers) {
ALOGV("Touch Boost");
- return {rankRefreshRates(anchorGroup, RefreshRateOrder::Descending),
+ return {rankFrameRates(anchorGroup, RefreshRateOrder::Descending),
GlobalSignals{.touch = true}};
}
@@ -387,27 +486,27 @@
if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
ALOGV("Idle");
- return {rankRefreshRates(activeMode.getGroup(), RefreshRateOrder::Ascending),
+ return {rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending),
GlobalSignals{.idle = true}};
}
if (layers.empty() || noVoteLayers == layers.size()) {
ALOGV("No layers with votes");
- return {rankRefreshRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
+ return {rankFrameRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
}
// Only if all layers want Min we should return Min
if (noVoteLayers + minVoteLayers == layers.size()) {
ALOGV("All layers Min");
- return {rankRefreshRates(activeMode.getGroup(), RefreshRateOrder::Ascending), kNoSignals};
+ return {rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending), kNoSignals};
}
// Find the best refresh rate based on score
std::vector<RefreshRateScore> scores;
- scores.reserve(mAppRequestRefreshRates.size());
+ scores.reserve(mAppRequestFrameRates.size());
- for (const DisplayModeIterator modeIt : mAppRequestRefreshRates) {
- scores.emplace_back(RefreshRateScore{modeIt, 0.0f});
+ for (const FrameRateMode& it : mAppRequestFrameRates) {
+ scores.emplace_back(RefreshRateScore{it, 0.0f});
}
for (const auto& layer : layers) {
@@ -420,13 +519,13 @@
const auto weight = layer.weight;
- for (auto& [modeIt, overallScore, fixedRateBelowThresholdLayersScore] : scores) {
- const auto& [id, mode] = *modeIt;
- const bool isSeamlessSwitch = mode->getGroup() == activeMode.getGroup();
+ for (auto& [mode, overallScore, fixedRateBelowThresholdLayersScore] : scores) {
+ const auto& [fps, modePtr] = mode;
+ const bool isSeamlessSwitch = modePtr->getGroup() == activeMode.getGroup();
if (layer.seamlessness == Seamlessness::OnlySeamless && !isSeamlessSwitch) {
ALOGV("%s ignores %s to avoid non-seamless switch. Current mode = %s",
- formatLayerInfo(layer, weight).c_str(), to_string(*mode).c_str(),
+ formatLayerInfo(layer, weight).c_str(), to_string(*modePtr).c_str(),
to_string(activeMode).c_str());
continue;
}
@@ -435,7 +534,7 @@
!layer.focused) {
ALOGV("%s ignores %s because it's not focused and the switch is going to be seamed."
" Current mode = %s",
- formatLayerInfo(layer, weight).c_str(), to_string(*mode).c_str(),
+ formatLayerInfo(layer, weight).c_str(), to_string(*modePtr).c_str(),
to_string(activeMode).c_str());
continue;
}
@@ -445,14 +544,14 @@
// mode group otherwise. In second case, if the current mode group is different
// from the default, this means a layer with seamlessness=SeamedAndSeamless has just
// disappeared.
- const bool isInPolicyForDefault = mode->getGroup() == anchorGroup;
+ const bool isInPolicyForDefault = modePtr->getGroup() == anchorGroup;
if (layer.seamlessness == Seamlessness::Default && !isInPolicyForDefault) {
ALOGV("%s ignores %s. Current mode = %s", formatLayerInfo(layer, weight).c_str(),
- to_string(*mode).c_str(), to_string(activeMode).c_str());
+ to_string(*modePtr).c_str(), to_string(activeMode).c_str());
continue;
}
- const bool inPrimaryRange = policy->primaryRanges.physical.includes(mode->getFps());
+ const bool inPrimaryRange = policy->primaryRanges.physical.includes(modePtr->getFps());
if ((primaryRangeIsSingleRate || !inPrimaryRange) &&
!(layer.focused &&
(layer.vote == LayerVoteType::ExplicitDefault ||
@@ -462,8 +561,7 @@
continue;
}
- const float layerScore =
- calculateLayerScoreLocked(layer, mode->getFps(), isSeamlessSwitch);
+ const float layerScore = calculateLayerScoreLocked(layer, fps, isSeamlessSwitch);
const float weightedLayerScore = weight * layerScore;
// Layer with fixed source has a special consideration which depends on the
@@ -491,21 +589,21 @@
Fps::fromValue(mConfig.frameRateMultipleThreshold / 2);
if (fixedSourceLayer && layerBelowThreshold) {
const bool modeAboveThreshold =
- mode->getFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold);
+ modePtr->getFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold);
if (modeAboveThreshold) {
- ALOGV("%s gives %s fixed source (above threshold) score of %.4f",
- formatLayerInfo(layer, weight).c_str(), to_string(mode->getFps()).c_str(),
- layerScore);
+ ALOGV("%s gives %s (%s) fixed source (above threshold) score of %.4f",
+ formatLayerInfo(layer, weight).c_str(), to_string(fps).c_str(),
+ to_string(modePtr->getFps()).c_str(), layerScore);
fixedRateBelowThresholdLayersScore.modeAboveThreshold += weightedLayerScore;
} else {
- ALOGV("%s gives %s fixed source (below threshold) score of %.4f",
- formatLayerInfo(layer, weight).c_str(), to_string(mode->getFps()).c_str(),
- layerScore);
+ ALOGV("%s gives %s (%s) fixed source (below threshold) score of %.4f",
+ formatLayerInfo(layer, weight).c_str(), to_string(fps).c_str(),
+ to_string(modePtr->getFps()).c_str(), layerScore);
fixedRateBelowThresholdLayersScore.modeBelowThreshold += weightedLayerScore;
}
} else {
- ALOGV("%s gives %s score of %.4f", formatLayerInfo(layer, weight).c_str(),
- to_string(mode->getFps()).c_str(), layerScore);
+ ALOGV("%s gives %s (%s) score of %.4f", formatLayerInfo(layer, weight).c_str(),
+ to_string(fps).c_str(), to_string(modePtr->getFps()).c_str(), layerScore);
overallScore += weightedLayerScore;
}
}
@@ -524,25 +622,26 @@
const auto maxScoreIt =
std::max_element(scores.begin(), scores.end(),
[](RefreshRateScore max, RefreshRateScore current) {
- const auto& [modeIt, overallScore, _] = current;
- return overallScore > max.overallScore;
+ return current.overallScore > max.overallScore;
});
- ALOGV("%s is the best refresh rate without fixed source layers. It is %s the threshold for "
+ ALOGV("%s (%s) is the best refresh rate without fixed source layers. It is %s the "
+ "threshold for "
"refresh rate multiples",
- to_string(maxScoreIt->modeIt->second->getFps()).c_str(),
+ to_string(maxScoreIt->frameRateMode.fps).c_str(),
+ to_string(maxScoreIt->frameRateMode.modePtr->getFps()).c_str(),
maxScoreAboveThreshold ? "above" : "below");
- return maxScoreIt->modeIt->second->getFps() >=
+ return maxScoreIt->frameRateMode.modePtr->getFps() >=
Fps::fromValue(mConfig.frameRateMultipleThreshold);
}();
// Now we can add the fixed rate layers score
- for (auto& [modeIt, overallScore, fixedRateBelowThresholdLayersScore] : scores) {
+ for (auto& [frameRateMode, overallScore, fixedRateBelowThresholdLayersScore] : scores) {
overallScore += fixedRateBelowThresholdLayersScore.modeBelowThreshold;
if (maxScoreAboveThreshold) {
overallScore += fixedRateBelowThresholdLayersScore.modeAboveThreshold;
}
- ALOGV("%s adjusted overallScore is %.4f", to_string(modeIt->second->getFps()).c_str(),
- overallScore);
+ ALOGV("%s (%s) adjusted overallScore is %.4f", to_string(frameRateMode.fps).c_str(),
+ to_string(frameRateMode.modePtr->getFps()).c_str(), overallScore);
}
// Now that we scored all the refresh rates we need to pick the one that got the highest
@@ -552,12 +651,12 @@
std::sort(scores.begin(), scores.end(),
RefreshRateScoreComparator{.refreshRateOrder = refreshRateOrder});
- RefreshRateRanking ranking;
+ FrameRateRanking ranking;
ranking.reserve(scores.size());
std::transform(scores.begin(), scores.end(), back_inserter(ranking),
[](const RefreshRateScore& score) {
- return ScoredRefreshRate{score.modeIt->second, score.overallScore};
+ return ScoredFrameRate{score.frameRateMode, score.overallScore};
});
const bool noLayerScore = std::all_of(scores.begin(), scores.end(), [](RefreshRateScore score) {
@@ -569,7 +668,7 @@
// range instead of picking a random score from the app range.
if (noLayerScore) {
ALOGV("Layers not scored");
- return {rankRefreshRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
+ return {rankFrameRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
} else {
return {ranking, kNoSignals};
}
@@ -580,7 +679,7 @@
// vote we should not change it if we get a touch event. Only apply touch boost if it will
// actually increase the refresh rate over the normal selection.
const bool touchBoostForExplicitExact = [&] {
- if (supportsFrameRateOverrideByContent()) {
+ if (supportsAppFrameRateOverrideByContent()) {
// Enable touch boost if there are other layers besides exact
return explicitExact + noVoteLayers != layers.size();
} else {
@@ -589,12 +688,11 @@
}
}();
- const auto touchRefreshRates = rankRefreshRates(anchorGroup, RefreshRateOrder::Descending);
-
+ const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
using fps_approx_ops::operator<;
if (signals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
- scores.front().modeIt->second->getFps() < touchRefreshRates.front().modePtr->getFps()) {
+ scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) {
ALOGV("Touch Boost");
return {touchRefreshRates, GlobalSignals{.touch = true}};
}
@@ -603,7 +701,7 @@
// current config
if (noLayerScore && refreshRateOrder == RefreshRateOrder::Ascending) {
const auto preferredDisplayMode = activeMode.getId();
- return {rankRefreshRates(anchorGroup, RefreshRateOrder::Ascending, preferredDisplayMode),
+ return {rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, preferredDisplayMode),
kNoSignals};
}
@@ -649,20 +747,13 @@
GlobalSignals globalSignals) const
-> UidToFrameRateOverride {
ATRACE_CALL();
- ALOGV("%s: %zu layers", __func__, layers.size());
-
- std::lock_guard lock(mLock);
-
- // Prepare a set of supported display refresh rates for easy lookup
- constexpr size_t kStaticCapacity = 8;
- ftl::SmallMap<Fps, ftl::Unit, kStaticCapacity, FpsApproxEqual> supportedDisplayRefreshRates;
- if (mConfig.enableFrameRateOverride ==
- Config::FrameRateOverride::EnabledForNativeRefreshRates) {
- for (const auto& [_, modePtr] : mDisplayModes) {
- supportedDisplayRefreshRates.try_emplace(modePtr->getFps(), ftl::unit);
- }
+ if (mConfig.enableFrameRateOverride == Config::FrameRateOverride::Disabled) {
+ return {};
}
+ ALOGV("%s: %zu layers", __func__, layers.size());
+ std::lock_guard lock(mLock);
+
const auto* policyPtr = getCurrentPolicyLocked();
// We don't want to run lower than 30fps
const Fps minFrameRate = std::max(policyPtr->appRequestRanges.render.min, 30_Hz, isApproxLess);
@@ -676,8 +767,8 @@
for (unsigned n = numMultiples; n > 0; n--) {
const Fps divisor = displayRefreshRate / n;
if (mConfig.enableFrameRateOverride ==
- Config::FrameRateOverride::EnabledForNativeRefreshRates &&
- !supportedDisplayRefreshRates.contains(divisor)) {
+ Config::FrameRateOverride::AppOverrideNativeRefreshRates &&
+ !isNativeRefreshRate(divisor)) {
continue;
}
@@ -736,7 +827,7 @@
[](const auto& lhsPair, const auto& rhsPair) {
const float lhs = lhsPair.second;
const float rhs = rhsPair.second;
- return lhs < rhs && !ScoredRefreshRate::scoresEqual(lhs, rhs);
+ return lhs < rhs && !ScoredFrameRate::scoresEqual(lhs, rhs);
});
ALOGV("%s: overriding to %s for uid=%d", __func__, to_string(overrideFps).c_str(), uid);
frameRateOverrides.emplace(uid, overrideFps);
@@ -765,10 +856,9 @@
const DisplayModePtr& RefreshRateSelector::getMinRefreshRateByPolicyLocked() const {
const auto& activeMode = *getActiveModeItLocked()->second;
- for (const DisplayModeIterator modeIt : mPrimaryRefreshRates) {
- const auto& mode = modeIt->second;
- if (activeMode.getGroup() == mode->getGroup()) {
- return mode;
+ for (const FrameRateMode& mode : mPrimaryFrameRates) {
+ if (activeMode.getGroup() == mode.modePtr->getGroup()) {
+ return mode.modePtr;
}
}
@@ -776,55 +866,72 @@
to_string(activeMode).c_str());
// Default to the lowest refresh rate.
- return mPrimaryRefreshRates.front()->second;
+ return mPrimaryFrameRates.front().modePtr;
}
const DisplayModePtr& RefreshRateSelector::getMaxRefreshRateByPolicyLocked(int anchorGroup) const {
- for (auto it = mPrimaryRefreshRates.rbegin(); it != mPrimaryRefreshRates.rend(); ++it) {
- const auto& mode = (*it)->second;
- if (anchorGroup == mode->getGroup()) {
- return mode;
+ const DisplayModePtr* maxByAnchor = &mPrimaryFrameRates.back().modePtr;
+ const DisplayModePtr* max = &mPrimaryFrameRates.back().modePtr;
+
+ bool maxByAnchorFound = false;
+ for (auto it = mPrimaryFrameRates.rbegin(); it != mPrimaryFrameRates.rend(); ++it) {
+ using namespace fps_approx_ops;
+ if (it->modePtr->getFps() > (*max)->getFps()) {
+ max = &it->modePtr;
}
+
+ if (anchorGroup == it->modePtr->getGroup() &&
+ it->modePtr->getFps() >= (*maxByAnchor)->getFps()) {
+ maxByAnchorFound = true;
+ maxByAnchor = &it->modePtr;
+ }
+ }
+
+ if (maxByAnchorFound) {
+ return *maxByAnchor;
}
ALOGE("Can't find max refresh rate by policy with the same group %d", anchorGroup);
// Default to the highest refresh rate.
- return mPrimaryRefreshRates.back()->second;
+ return *max;
}
-auto RefreshRateSelector::rankRefreshRates(
- std::optional<int> anchorGroupOpt, RefreshRateOrder refreshRateOrder,
- std::optional<DisplayModeId> preferredDisplayModeOpt) const -> RefreshRateRanking {
- std::deque<ScoredRefreshRate> ranking;
-
- const auto rankRefreshRate = [&](DisplayModeIterator it) REQUIRES(mLock) {
- const auto& mode = it->second;
- if (anchorGroupOpt && mode->getGroup() != anchorGroupOpt) {
+auto RefreshRateSelector::rankFrameRates(std::optional<int> anchorGroupOpt,
+ RefreshRateOrder refreshRateOrder,
+ std::optional<DisplayModeId> preferredDisplayModeOpt) const
+ -> FrameRateRanking {
+ const char* const whence = __func__;
+ std::deque<ScoredFrameRate> ranking;
+ const auto rankFrameRate = [&](const FrameRateMode& frameRateMode) REQUIRES(mLock) {
+ const auto& modePtr = frameRateMode.modePtr;
+ if (anchorGroupOpt && modePtr->getGroup() != anchorGroupOpt) {
return;
}
- float score = calculateRefreshRateScoreForFps(mode->getFps());
+ float score = calculateDistanceScoreFromMax(frameRateMode.fps);
const bool inverseScore = (refreshRateOrder == RefreshRateOrder::Ascending);
if (inverseScore) {
score = 1.0f / score;
}
if (preferredDisplayModeOpt) {
- if (*preferredDisplayModeOpt == mode->getId()) {
+ if (*preferredDisplayModeOpt == modePtr->getId()) {
constexpr float kScore = std::numeric_limits<float>::max();
- ranking.push_front(ScoredRefreshRate{mode, kScore});
+ ranking.emplace_front(ScoredFrameRate{frameRateMode, kScore});
return;
}
constexpr float kNonPreferredModePenalty = 0.95f;
score *= kNonPreferredModePenalty;
}
- ranking.push_back(ScoredRefreshRate{mode, score});
+ ALOGV("%s(%s) %s (%s) scored %.2f", whence, ftl::enum_string(refreshRateOrder).c_str(),
+ to_string(frameRateMode.fps).c_str(), to_string(modePtr->getFps()).c_str(), score);
+ ranking.emplace_back(ScoredFrameRate{frameRateMode, score});
};
if (refreshRateOrder == RefreshRateOrder::Ascending) {
- std::for_each(mPrimaryRefreshRates.begin(), mPrimaryRefreshRates.end(), rankRefreshRate);
+ std::for_each(mPrimaryFrameRates.begin(), mPrimaryFrameRates.end(), rankFrameRate);
} else {
- std::for_each(mPrimaryRefreshRates.rbegin(), mPrimaryRefreshRates.rend(), rankRefreshRate);
+ std::for_each(mPrimaryFrameRates.rbegin(), mPrimaryFrameRates.rend(), rankFrameRate);
}
if (!ranking.empty() || !anchorGroupOpt) {
@@ -836,7 +943,7 @@
refreshRateOrder == RefreshRateOrder::Ascending ? "min" : "max", anchorGroupOpt.value());
constexpr std::optional<int> kNoAnchorGroup = std::nullopt;
- return rankRefreshRates(kNoAnchorGroup, refreshRateOrder, preferredDisplayModeOpt);
+ return rankFrameRates(kNoAnchorGroup, refreshRateOrder, preferredDisplayModeOpt);
}
DisplayModePtr RefreshRateSelector::getActiveModePtr() const {
@@ -858,9 +965,9 @@
void RefreshRateSelector::setActiveModeId(DisplayModeId modeId) {
std::lock_guard lock(mLock);
- // Invalidate the cached invocation to getRankedRefreshRates. This forces
- // the refresh rate to be recomputed on the next call to getRankedRefreshRates.
- mGetRankedRefreshRatesCache.reset();
+ // Invalidate the cached invocation to getRankedFrameRates. This forces
+ // the refresh rate to be recomputed on the next call to getRankedFrameRates.
+ mGetRankedFrameRatesCache.reset();
mActiveModeIt = mDisplayModes.find(modeId);
LOG_ALWAYS_FATAL_IF(mActiveModeIt == mDisplayModes.end());
@@ -895,16 +1002,15 @@
void RefreshRateSelector::updateDisplayModes(DisplayModes modes, DisplayModeId activeModeId) {
std::lock_guard lock(mLock);
- // Invalidate the cached invocation to getRankedRefreshRates. This forces
- // the refresh rate to be recomputed on the next call to getRankedRefreshRates.
- mGetRankedRefreshRatesCache.reset();
+ // Invalidate the cached invocation to getRankedFrameRates. This forces
+ // the refresh rate to be recomputed on the next call to getRankedFrameRates.
+ mGetRankedFrameRatesCache.reset();
mDisplayModes = std::move(modes);
mActiveModeIt = mDisplayModes.find(activeModeId);
LOG_ALWAYS_FATAL_IF(mActiveModeIt == mDisplayModes.end());
- const auto sortedModes =
- sortByRefreshRate(mDisplayModes, [](const DisplayMode&) { return true; });
+ const auto sortedModes = sortByRefreshRate(mDisplayModes);
mMinRefreshRateModeIt = sortedModes.front();
mMaxRefreshRateModeIt = sortedModes.back();
@@ -915,15 +1021,23 @@
mFrameRateOverrideConfig = [&] {
switch (mConfig.enableFrameRateOverride) {
case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverride:
case Config::FrameRateOverride::Enabled:
return mConfig.enableFrameRateOverride;
- case Config::FrameRateOverride::EnabledForNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
return shouldEnableFrameRateOverride(sortedModes)
- ? Config::FrameRateOverride::EnabledForNativeRefreshRates
+ ? Config::FrameRateOverride::AppOverrideNativeRefreshRates
: Config::FrameRateOverride::Disabled;
}
}();
+ if (mConfig.enableFrameRateOverride ==
+ Config::FrameRateOverride::AppOverrideNativeRefreshRates) {
+ for (const auto& [_, mode] : mDisplayModes) {
+ mAppOverrideNativeRefreshRates.try_emplace(mode->getFps(), ftl::unit);
+ }
+ }
+
constructAvailableRefreshRates();
}
@@ -939,9 +1053,13 @@
return false;
}
- using namespace fps_approx_ops;
- return policy.appRequestRanges.physical.min <= policy.primaryRanges.physical.min &&
- policy.appRequestRanges.physical.max >= policy.primaryRanges.physical.max;
+ const auto& primaryRanges = policy.primaryRanges;
+ const auto& appRequestRanges = policy.appRequestRanges;
+ ALOGE_IF(!appRequestRanges.physical.includes(primaryRanges.physical),
+ "Physical range is invalid");
+ ALOGE_IF(!appRequestRanges.render.includes(primaryRanges.render), "Render range is invalid");
+
+ return primaryRanges.valid() && appRequestRanges.valid();
}
auto RefreshRateSelector::setPolicy(const PolicyVariant& policy) -> SetPolicyResult {
@@ -979,7 +1097,7 @@
return SetPolicyResult::Invalid;
}
- mGetRankedRefreshRatesCache.reset();
+ mGetRankedFrameRatesCache.reset();
if (*getCurrentPolicyLocked() == oldPolicy) {
return SetPolicyResult::Unchanged;
@@ -1016,9 +1134,9 @@
bool RefreshRateSelector::isModeAllowed(DisplayModeId modeId) const {
std::lock_guard lock(mLock);
- return std::any_of(mAppRequestRefreshRates.begin(), mAppRequestRefreshRates.end(),
- [modeId](DisplayModeIterator modeIt) {
- return modeIt->second->getId() == modeId;
+ return std::any_of(mAppRequestFrameRates.begin(), mAppRequestFrameRates.end(),
+ [modeId](const FrameRateMode& frameRateMode) {
+ return frameRateMode.modePtr->getId() == modeId;
});
}
@@ -1029,33 +1147,35 @@
const auto& defaultMode = mDisplayModes.get(policy->defaultMode)->get();
- const auto filterRefreshRates = [&](FpsRange range, const char* rangeName) REQUIRES(mLock) {
- const auto filter = [&](const DisplayMode& mode) {
+ const auto filterRefreshRates = [&](const FpsRanges& ranges,
+ const char* rangeName) REQUIRES(mLock) {
+ const auto filterModes = [&](const DisplayMode& mode) {
return mode.getResolution() == defaultMode->getResolution() &&
mode.getDpi() == defaultMode->getDpi() &&
(policy->allowGroupSwitching || mode.getGroup() == defaultMode->getGroup()) &&
- range.includes(mode.getFps());
+ ranges.physical.includes(mode.getFps()) &&
+ (supportsFrameRateOverride() || ranges.render.includes(mode.getFps()));
};
- const auto modes = sortByRefreshRate(mDisplayModes, filter);
- LOG_ALWAYS_FATAL_IF(modes.empty(), "No matching modes for %s range %s", rangeName,
- to_string(range).c_str());
+ const auto frameRateModes = createFrameRateModes(filterModes, ranges.render);
+ LOG_ALWAYS_FATAL_IF(frameRateModes.empty(),
+ "No matching frame rate modes for %s physicalRange %s", rangeName,
+ to_string(ranges.physical).c_str());
const auto stringifyModes = [&] {
std::string str;
- for (const auto modeIt : modes) {
- str += to_string(modeIt->second->getFps());
- str.push_back(' ');
+ for (const auto& frameRateMode : frameRateModes) {
+ str += to_string(frameRateMode) + " ";
}
return str;
};
- ALOGV("%s refresh rates: %s", rangeName, stringifyModes().c_str());
+ ALOGV("%s render rates: %s", rangeName, stringifyModes().c_str());
- return modes;
+ return frameRateModes;
};
- mPrimaryRefreshRates = filterRefreshRates(policy->primaryRanges.physical, "primary");
- mAppRequestRefreshRates = filterRefreshRates(policy->appRequestRanges.physical, "app request");
+ mPrimaryFrameRates = filterRefreshRates(policy->primaryRanges, "primary");
+ mAppRequestFrameRates = filterRefreshRates(policy->appRequestRanges, "app request");
}
Fps RefreshRateSelector::findClosestKnownFrameRate(Fps frameRate) const {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index 65ee487..89ebeea 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -24,9 +24,11 @@
#include <ftl/concat.h>
#include <ftl/optional.h>
+#include <ftl/unit.h>
#include <gui/DisplayEventReceiver.h>
#include <scheduler/Fps.h>
+#include <scheduler/FrameRateMode.h>
#include <scheduler/Seamlessness.h>
#include "DisplayHardware/DisplayMode.h"
@@ -58,6 +60,9 @@
static constexpr nsecs_t MARGIN_FOR_PERIOD_CALCULATION =
std::chrono::nanoseconds(800us).count();
+ // The lowest Render Frame Rate that will ever be selected
+ static constexpr Fps kMinSupportedFrameRate = 20_Hz;
+
class Policy {
static constexpr int kAllowGroupSwitchingDefault = false;
@@ -196,12 +201,12 @@
}
};
- struct ScoredRefreshRate {
- DisplayModePtr modePtr;
+ struct ScoredFrameRate {
+ FrameRateMode frameRateMode;
float score = 0.0f;
- bool operator==(const ScoredRefreshRate& other) const {
- return modePtr == other.modePtr && score == other.score;
+ bool operator==(const ScoredFrameRate& other) const {
+ return frameRateMode == other.frameRateMode && score == other.score;
}
static bool scoresEqual(float lhs, float rhs) {
@@ -210,25 +215,25 @@
}
struct DescendingScore {
- bool operator()(const ScoredRefreshRate& lhs, const ScoredRefreshRate& rhs) const {
+ bool operator()(const ScoredFrameRate& lhs, const ScoredFrameRate& rhs) const {
return lhs.score > rhs.score && !scoresEqual(lhs.score, rhs.score);
}
};
};
- using RefreshRateRanking = std::vector<ScoredRefreshRate>;
+ using FrameRateRanking = std::vector<ScoredFrameRate>;
- struct RankedRefreshRates {
- RefreshRateRanking ranking; // Ordered by descending score.
+ struct RankedFrameRates {
+ FrameRateRanking ranking; // Ordered by descending score.
GlobalSignals consideredSignals;
- bool operator==(const RankedRefreshRates& other) const {
+ bool operator==(const RankedFrameRates& other) const {
return ranking == other.ranking && consideredSignals == other.consideredSignals;
}
};
- RankedRefreshRates getRankedRefreshRates(const std::vector<LayerRequirement>&,
- GlobalSignals) const EXCLUDES(mLock);
+ RankedFrameRates getRankedFrameRates(const std::vector<LayerRequirement>&, GlobalSignals) const
+ EXCLUDES(mLock);
FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
std::lock_guard lock(mLock);
@@ -257,9 +262,12 @@
// Override the frame rate for an app to a value which is also
// a display refresh rate
- EnabledForNativeRefreshRates,
+ AppOverrideNativeRefreshRates,
// Override the frame rate for an app to any value
+ AppOverride,
+
+ // Override the frame rate for all apps and all values.
Enabled,
ftl_last = Enabled
@@ -291,10 +299,13 @@
// Returns whether switching modes (refresh rate or resolution) is possible.
// TODO(b/158780872): Consider HAL support, and skip frame rate detection if the modes only
- // differ in resolution.
+ // differ in resolution. Once Config::FrameRateOverride::Enabled becomes the default,
+ // we can probably remove canSwitch altogether since all devices will be able
+ // to switch to a frame rate divisor.
bool canSwitch() const EXCLUDES(mLock) {
std::lock_guard lock(mLock);
- return mDisplayModes.size() > 1;
+ return mDisplayModes.size() > 1 ||
+ mFrameRateOverrideConfig == Config::FrameRateOverride::Enabled;
}
// Class to enumerate options around toggling the kernel timer on and off.
@@ -307,10 +318,14 @@
// refresh rates.
KernelIdleTimerAction getIdleTimerAction() const;
- bool supportsFrameRateOverrideByContent() const {
+ bool supportsAppFrameRateOverrideByContent() const {
return mFrameRateOverrideConfig != Config::FrameRateOverride::Disabled;
}
+ bool supportsFrameRateOverride() const {
+ return mFrameRateOverrideConfig == Config::FrameRateOverride::Enabled;
+ }
+
// Return the display refresh rate divisor to match the layer
// frame rate, or 0 if the display refresh rate is not a multiple of the
// layer refresh rate.
@@ -387,8 +402,8 @@
// See mActiveModeIt for thread safety.
DisplayModeIterator getActiveModeItLocked() const REQUIRES(mLock);
- RankedRefreshRates getRankedRefreshRatesLocked(const std::vector<LayerRequirement>&,
- GlobalSignals) const REQUIRES(mLock);
+ RankedFrameRates getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
+ GlobalSignals signals) const REQUIRES(mLock);
// Returns number of display frames and remainder when dividing the layer refresh period by
// display refresh period.
@@ -404,18 +419,24 @@
struct RefreshRateScoreComparator;
- enum class RefreshRateOrder { Ascending, Descending };
+ enum class RefreshRateOrder {
+ Ascending,
+ Descending,
+
+ ftl_last = Descending
+ };
// Only uses the primary range, not the app request range.
- RefreshRateRanking rankRefreshRates(std::optional<int> anchorGroupOpt, RefreshRateOrder,
- std::optional<DisplayModeId> preferredDisplayModeOpt =
- std::nullopt) const REQUIRES(mLock);
+ FrameRateRanking rankFrameRates(
+ std::optional<int> anchorGroupOpt, RefreshRateOrder refreshRateOrder,
+ std::optional<DisplayModeId> preferredDisplayModeOpt = std::nullopt) const
+ REQUIRES(mLock);
const Policy* getCurrentPolicyLocked() const REQUIRES(mLock);
bool isPolicyValidLocked(const Policy& policy) const REQUIRES(mLock);
// Returns the refresh rate score as a ratio to max refresh rate, which has a score of 1.
- float calculateRefreshRateScoreForFps(Fps refreshRate) const REQUIRES(mLock);
+ float calculateDistanceScoreFromMax(Fps refreshRate) const REQUIRES(mLock);
// calculates a score for a layer. Used to determine the display refresh rate
// and the frame rate override for certains applications.
float calculateLayerScoreLocked(const LayerRequirement&, Fps refreshRate,
@@ -436,11 +457,27 @@
: mIdleTimerCallbacks->platform;
}
+ bool isNativeRefreshRate(Fps fps) const REQUIRES(mLock) {
+ LOG_ALWAYS_FATAL_IF(mConfig.enableFrameRateOverride !=
+ Config::FrameRateOverride::AppOverrideNativeRefreshRates,
+ "should only be called when "
+ "Config::FrameRateOverride::AppOverrideNativeRefreshRates is used");
+ return mAppOverrideNativeRefreshRates.contains(fps);
+ }
+
+ std::vector<FrameRateMode> createFrameRateModes(
+ std::function<bool(const DisplayMode&)>&& filterModes, const FpsRange&) const
+ REQUIRES(mLock);
+
// The display modes of the active display. The DisplayModeIterators below are pointers into
// this container, so must be invalidated whenever the DisplayModes change. The Policy below
// is also dependent, so must be reset as well.
DisplayModes mDisplayModes GUARDED_BY(mLock);
+ // Set of supported display refresh rates for easy lookup
+ // when FrameRateOverride::AppOverrideNativeRefreshRates is in use.
+ ftl::SmallMap<Fps, ftl::Unit, 8, FpsApproxEqual> mAppOverrideNativeRefreshRates;
+
// Written under mLock exclusively from kMainThreadContext, so reads from kMainThreadContext
// need not be under mLock.
DisplayModeIterator mActiveModeIt GUARDED_BY(mLock) GUARDED_BY(kMainThreadContext);
@@ -449,8 +486,8 @@
DisplayModeIterator mMaxRefreshRateModeIt GUARDED_BY(mLock);
// Display modes that satisfy the Policy's ranges, filtered and sorted by refresh rate.
- std::vector<DisplayModeIterator> mPrimaryRefreshRates GUARDED_BY(mLock);
- std::vector<DisplayModeIterator> mAppRequestRefreshRates GUARDED_BY(mLock);
+ std::vector<FrameRateMode> mPrimaryFrameRates GUARDED_BY(mLock);
+ std::vector<FrameRateMode> mAppRequestFrameRates GUARDED_BY(mLock);
Policy mDisplayManagerPolicy GUARDED_BY(mLock);
std::optional<Policy> mOverridePolicy GUARDED_BY(mLock);
@@ -466,11 +503,11 @@
const Config mConfig;
Config::FrameRateOverride mFrameRateOverrideConfig;
- struct GetRankedRefreshRatesCache {
+ struct GetRankedFrameRatesCache {
std::pair<std::vector<LayerRequirement>, GlobalSignals> arguments;
- RankedRefreshRates result;
+ RankedFrameRates result;
};
- mutable std::optional<GetRankedRefreshRatesCache> mGetRankedRefreshRatesCache GUARDED_BY(mLock);
+ mutable std::optional<GetRankedFrameRatesCache> mGetRankedFrameRatesCache GUARDED_BY(mLock);
// Declare mIdleTimer last to ensure its thread joins before the mutex/callbacks are destroyed.
std::mutex mIdleTimerCallbacksMutex;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index f1fcc88..0c541f9 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -152,7 +152,7 @@
std::optional<Fps> Scheduler::getFrameRateOverride(uid_t uid) const {
const bool supportsFrameRateOverrideByContent =
- leaderSelectorPtr()->supportsFrameRateOverrideByContent();
+ leaderSelectorPtr()->supportsAppFrameRateOverrideByContent();
return mFrameRateOverrideMappings
.getFrameRateOverrideForUid(uid, supportsFrameRateOverrideByContent);
}
@@ -268,7 +268,7 @@
void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId) {
const bool supportsFrameRateOverrideByContent =
- leaderSelectorPtr()->supportsFrameRateOverrideByContent();
+ leaderSelectorPtr()->supportsAppFrameRateOverrideByContent();
std::vector<FrameRateOverride> overrides =
mFrameRateOverrideMappings.getAllFrameRateOverrides(supportsFrameRateOverrideByContent);
@@ -707,7 +707,7 @@
auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap {
ATRACE_CALL();
- using RankedRefreshRates = RefreshRateSelector::RankedRefreshRates;
+ using RankedRefreshRates = RefreshRateSelector::RankedFrameRates;
display::PhysicalDisplayVector<RankedRefreshRates> perDisplayRanking;
// Tallies the score of a refresh rate across `displayCount` displays.
@@ -726,9 +726,10 @@
for (const auto& [id, selectorPtr] : mRefreshRateSelectors) {
auto rankedRefreshRates =
- selectorPtr->getRankedRefreshRates(mPolicy.contentRequirements, globalSignals);
+ selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, globalSignals);
- for (const auto& [modePtr, score] : rankedRefreshRates.ranking) {
+ for (const auto& [frameRateMode, score] : rankedRefreshRates.ranking) {
+ const auto& modePtr = frameRateMode.modePtr;
const auto [it, inserted] = refreshRateTallies.try_emplace(modePtr->getFps(), score);
if (!inserted) {
@@ -771,16 +772,18 @@
for (auto& [ranking, signals] : perDisplayRanking) {
if (!chosenFps) {
- auto& [modePtr, _] = ranking.front();
+ const auto& [frameRateMode, _] = ranking.front();
+ const auto& modePtr = frameRateMode.modePtr;
modeChoices.try_emplace(modePtr->getPhysicalDisplayId(),
- DisplayModeChoice{std::move(modePtr), signals});
+ DisplayModeChoice{modePtr, signals});
continue;
}
- for (auto& [modePtr, _] : ranking) {
+ for (auto& [frameRateMode, _] : ranking) {
+ const auto& modePtr = frameRateMode.modePtr;
if (modePtr->getFps() == *chosenFps) {
modeChoices.try_emplace(modePtr->getPhysicalDisplayId(),
- DisplayModeChoice{std::move(modePtr), signals});
+ DisplayModeChoice{modePtr, signals});
break;
}
}
@@ -804,10 +807,10 @@
if (mPolicy.mode) {
const auto ranking =
leaderSelectorPtr()
- ->getRankedRefreshRates(mPolicy.contentRequirements, makeGlobalSignals())
+ ->getRankedFrameRates(mPolicy.contentRequirements, makeGlobalSignals())
.ranking;
- mPolicy.mode = ranking.front().modePtr;
+ mPolicy.mode = ranking.front().frameRateMode.modePtr;
}
return mPolicy.mode;
}
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
index 31b1d69..5522ff8 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
@@ -66,6 +66,7 @@
Fps max = Fps::fromValue(std::numeric_limits<float>::max());
bool includes(Fps) const;
+ bool includes(FpsRange) const;
};
struct FpsRanges {
@@ -75,6 +76,8 @@
// the range of frame rates that refers to the render rate, which is
// the rate that frames are swapped.
FpsRange render;
+
+ bool valid() const;
};
static_assert(std::is_trivially_copyable_v<Fps>);
@@ -159,6 +162,16 @@
return min <= fps && fps <= max;
}
+inline bool FpsRange::includes(FpsRange range) const {
+ using namespace fps_approx_ops;
+ return min <= range.min && max >= range.max;
+}
+
+inline bool FpsRanges::valid() const {
+ using fps_approx_ops::operator>=;
+ return physical.max >= render.max;
+}
+
struct FpsApproxEqual {
bool operator()(Fps lhs, Fps rhs) const { return isApproxEqual(lhs, rhs); }
};
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
new file mode 100644
index 0000000..670ab45
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
@@ -0,0 +1,44 @@
+/*
+ * 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 <scheduler/Fps.h>
+
+// TODO(b/241285191): Pull this to <ui/DisplayMode.h>
+#include "DisplayHardware/DisplayMode.h"
+
+namespace android::scheduler {
+
+struct FrameRateMode {
+ Fps fps; // The render frame rate, which is a divisor of modePtr->getFps().
+ DisplayModePtr modePtr;
+
+ bool operator==(const FrameRateMode& other) const {
+ return isApproxEqual(fps, other.fps) && modePtr == other.modePtr;
+ }
+
+ bool operator!=(const FrameRateMode& other) const { return !(*this == other); }
+};
+
+inline std::string to_string(const FrameRateMode& mode) {
+ if (mode.modePtr) {
+ return to_string(mode.fps) + " (" + to_string(mode.modePtr->getFps()) + ")";
+ }
+ return "{invalid}";
+}
+
+} // namespace android::scheduler
\ No newline at end of file
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 5df09d6..6961f7c 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -180,6 +180,7 @@
using base::StringAppendF;
using display::PhysicalDisplay;
using display::PhysicalDisplays;
+using frontend::TransactionHandler;
using gui::DisplayInfo;
using gui::GameMode;
using gui::IDisplayEventConnection;
@@ -1391,7 +1392,26 @@
return NO_ERROR;
}
-status_t SurfaceFlinger::getOverlaySupport(gui::OverlayProperties* /*outProperties*/) const {
+status_t SurfaceFlinger::getOverlaySupport(gui::OverlayProperties* outProperties) const {
+ const auto& aidlProperties = getHwComposer().getOverlaySupport();
+ // convert aidl OverlayProperties to gui::OverlayProperties
+ outProperties->combinations.reserve(aidlProperties.combinations.size());
+ for (const auto& combination : aidlProperties.combinations) {
+ std::vector<int32_t> pixelFormats;
+ pixelFormats.reserve(combination.pixelFormats.size());
+ std::transform(combination.pixelFormats.cbegin(), combination.pixelFormats.cend(),
+ std::back_inserter(pixelFormats),
+ [](const auto& val) { return static_cast<int32_t>(val); });
+ std::vector<int32_t> dataspaces;
+ dataspaces.reserve(combination.dataspaces.size());
+ std::transform(combination.dataspaces.cbegin(), combination.dataspaces.cend(),
+ std::back_inserter(dataspaces),
+ [](const auto& val) { return static_cast<int32_t>(val); });
+ gui::OverlayProperties::SupportedBufferCombinations outCombination;
+ outCombination.pixelFormats = std::move(pixelFormats);
+ outCombination.dataspaces = std::move(dataspaces);
+ outProperties->combinations.emplace_back(outCombination);
+ }
return NO_ERROR;
}
@@ -2790,7 +2810,11 @@
}
if (sysprop::frame_rate_override_for_native_rates(true)) {
- return Config::FrameRateOverride::EnabledForNativeRefreshRates;
+ return Config::FrameRateOverride::AppOverrideNativeRefreshRates;
+ }
+
+ if (!base::GetBoolProperty("debug.sf.frame_rate_override_global"s, false)) {
+ return Config::FrameRateOverride::AppOverride;
}
return Config::FrameRateOverride::Enabled;
@@ -3302,7 +3326,7 @@
if (!layer->needsInputInfo()) return;
const auto opt = mFrontEndDisplayInfos.get(layer->getLayerStack())
- .transform([](const FrontEndDisplayInfo& info) {
+ .transform([](const frontend::DisplayInfo& info) {
return Layer::InputDisplayArgs{&info.transform, info.isSecure};
});
@@ -6374,8 +6398,10 @@
std::unique_ptr<RenderArea> renderArea = renderAreaFuture.get();
if (!renderArea) {
ALOGW("Skipping screen capture because of invalid render area.");
- captureResults.fenceResult = base::unexpected(NO_MEMORY);
- captureListener->onScreenCaptureCompleted(captureResults);
+ if (captureListener) {
+ captureResults.fenceResult = base::unexpected(NO_MEMORY);
+ captureListener->onScreenCaptureCompleted(captureResults);
+ }
return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
}
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 8776904..751d1e5 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -66,7 +66,7 @@
#include "DisplayIdGenerator.h"
#include "Effects/Daltonizer.h"
#include "FlagManager.h"
-#include "FrontEnd/FrontEndDisplayInfo.h"
+#include "FrontEnd/DisplayInfo.h"
#include "FrontEnd/LayerCreationArgs.h"
#include "FrontEnd/TransactionHandler.h"
#include "LayerVector.h"
@@ -121,6 +121,7 @@
class ScreenCapturer;
class WindowInfosListenerInvoker;
+using frontend::TransactionHandler;
using gui::CaptureArgs;
using gui::DisplayCaptureArgs;
using gui::IRegionSamplingListener;
@@ -1372,7 +1373,7 @@
} mPowerHintSessionMode;
TransactionHandler mTransactionHandler;
- display::DisplayMap<ui::LayerStack, FrontEndDisplayInfo> mFrontEndDisplayInfos;
+ display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos;
};
class SurfaceComposerAIDL : public gui::BnSurfaceComposer {
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index e5a9dd4..e860d88 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -68,6 +68,8 @@
return SurfaceflingerStatsLayerInfo::GAME_MODE_PERFORMANCE;
case GameMode::Battery:
return SurfaceflingerStatsLayerInfo::GAME_MODE_BATTERY;
+ case GameMode::Custom:
+ return SurfaceflingerStatsLayerInfo::GAME_MODE_CUSTOM;
default:
return SurfaceflingerStatsLayerInfo::GAME_MODE_UNSPECIFIED;
}
diff --git a/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto b/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto
index e45757d..d4d444e 100644
--- a/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto
+++ b/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto
@@ -173,6 +173,7 @@
GAME_MODE_STANDARD = 2;
GAME_MODE_PERFORMANCE = 3;
GAME_MODE_BATTERY = 4;
+ GAME_MODE_CUSTOM = 5;
}
// Game mode that the layer was running at. Used to track user engagement
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index bd11a37..950e6d3 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -353,7 +353,7 @@
const RefreshRateSelector::GlobalSignals globalSignals = {.touch = false, .idle = false};
std::vector<LayerRequirement> layers = {{.weight = mFdp.ConsumeFloatingPoint<float>()}};
- refreshRateSelector.getRankedRefreshRates(layers, globalSignals);
+ refreshRateSelector.getRankedFrameRates(layers, globalSignals);
layers[0].name = mFdp.ConsumeRandomLengthString(kRandomStringLength);
layers[0].ownerUid = mFdp.ConsumeIntegral<uint16_t>();
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index d88da4d..0114577 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -85,6 +85,7 @@
"FpsTest.cpp",
"FramebufferSurfaceTest.cpp",
"FrameRateOverrideMappingsTest.cpp",
+ "FrameRateSelectionPriorityTest.cpp",
"FrameTimelineTest.cpp",
"GameModeTest.cpp",
"HWComposerTest.cpp",
@@ -92,6 +93,7 @@
"LayerHistoryTest.cpp",
"LayerInfoTest.cpp",
"LayerMetadataTest.cpp",
+ "LayerLifecycleManagerTest.cpp",
"LayerTest.cpp",
"LayerTestUtils.cpp",
"MessageQueueTest.cpp",
@@ -112,7 +114,6 @@
"SchedulerTest.cpp",
"SetFrameRateTest.cpp",
"RefreshRateSelectorTest.cpp",
- "RefreshRateSelectionTest.cpp",
"RefreshRateStatsTest.cpp",
"RegionSamplingTest.cpp",
"TimeStatsTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp
similarity index 100%
rename from services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp
rename to services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index 342c646..8f89a8c 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -146,6 +146,8 @@
{kMetadata2Name, kMetadata2Mandatory},
}),
Return(hardware::graphics::composer::V2_4::Error::NONE)));
+ EXPECT_CALL(*mHal, getOverlaySupport(_)).WillOnce(Return(HalError::NONE));
+
EXPECT_CALL(*mHal, registerCallback(_));
mHwc.setCallback(mCallback);
@@ -162,6 +164,7 @@
EXPECT_CALL(*mHal, getCapabilities()).WillOnce(Return(std::vector<aidl::Capability>{}));
EXPECT_CALL(*mHal, getLayerGenericMetadataKeys(_))
.WillOnce(Return(hardware::graphics::composer::V2_4::Error::UNSUPPORTED));
+ EXPECT_CALL(*mHal, getOverlaySupport(_)).WillOnce(Return(HalError::UNSUPPORTED));
EXPECT_CALL(*mHal, registerCallback(_));
mHwc.setCallback(mCallback);
diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
new file mode 100644
index 0000000..89440a6
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
@@ -0,0 +1,453 @@
+/*
+ * 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/LayerLifecycleManager.h"
+#include "Layer.h"
+#include "gui/SurfaceComposerClient.h"
+
+using namespace android::surfaceflinger;
+
+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 \
+ /data/nativetest/libsurfaceflinger_unittest/libsurfaceflinger_unittest \
+ --gtest_filter="LayerLifecycleManagerTest.*" --gtest_repeat=100 \
+ --gtest_shuffle \
+ --gtest_brief=1
+*/
+class ExpectLayerLifecycleListener : public LayerLifecycleManager::ILifecycleListener {
+public:
+ void onLayerAdded(const RequestedLayerState& layer) override {
+ mActualLayersAdded.emplace(layer.id);
+ };
+ void onLayerDestroyed(const RequestedLayerState& layer) override {
+ mActualLayersDestroyed.emplace(layer.id);
+ };
+
+ void expectLayersAdded(const std::unordered_set<uint32_t>& expectedLayersAdded) {
+ EXPECT_EQ(expectedLayersAdded, mActualLayersAdded);
+ mActualLayersAdded.clear();
+ }
+ void expectLayersDestroyed(const std::unordered_set<uint32_t>& expectedLayersDestroyed) {
+ EXPECT_EQ(expectedLayersDestroyed, mActualLayersDestroyed);
+ mActualLayersDestroyed.clear();
+ }
+
+ std::unordered_set<uint32_t> mActualLayersAdded;
+ std::unordered_set<uint32_t> mActualLayersDestroyed;
+};
+
+class LayerLifecycleManagerTest : public testing::Test {
+protected:
+ std::unique_ptr<RequestedLayerState> rootLayer(uint32_t id) {
+ return std::make_unique<RequestedLayerState>(
+ createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/nullptr, /*mirror=*/nullptr));
+ }
+
+ std::unique_ptr<RequestedLayerState> childLayer(uint32_t id, uint32_t parentId) {
+ mHandles[parentId] = sp<LayerHandle>::make(parentId);
+ return std::make_unique<RequestedLayerState>(createArgs(/*id=*/id, /*canBeRoot=*/false,
+ /*parent=*/mHandles[parentId],
+ /*mirror=*/nullptr));
+ }
+
+ TransactionState reparentLayer(uint32_t id, uint32_t newParentId) {
+ TransactionState transaction;
+ transaction.states.push_back({});
+
+ if (newParentId == UNASSIGNED_LAYER_ID) {
+ transaction.states.front().state.parentSurfaceControlForChild = nullptr;
+ } else {
+ transaction.states.front().state.parentSurfaceControlForChild =
+ sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(),
+ sp<LayerHandle>::make(newParentId),
+ static_cast<int32_t>(newParentId), "Test");
+ }
+ transaction.states.front().state.what = layer_state_t::eReparent;
+ transaction.states.front().state.surface = sp<LayerHandle>::make(id);
+ return transaction;
+ }
+
+ TransactionState setLayer(uint32_t id, int32_t z) {
+ TransactionState transaction;
+ transaction.states.push_back({});
+ transaction.states.front().state.z = z;
+ transaction.states.front().state.what = layer_state_t::eLayerChanged;
+ transaction.states.front().state.surface = sp<LayerHandle>::make(id);
+ return transaction;
+ }
+
+ TransactionState makeRelative(uint32_t id, uint32_t relativeParentId) {
+ TransactionState transaction;
+ transaction.states.push_back({});
+
+ if (relativeParentId == UNASSIGNED_LAYER_ID) {
+ transaction.states.front().state.relativeLayerSurfaceControl = nullptr;
+ } else {
+ transaction.states.front().state.relativeLayerSurfaceControl =
+ sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(),
+ sp<LayerHandle>::make(relativeParentId),
+ static_cast<int32_t>(relativeParentId), "Test");
+ }
+ transaction.states.front().state.what = layer_state_t::eRelativeLayerChanged;
+ transaction.states.front().state.surface = sp<LayerHandle>::make(id);
+ return transaction;
+ }
+
+ RequestedLayerState* getRequestedLayerState(LayerLifecycleManager& lifecycleManager,
+ uint32_t layerId) {
+ return lifecycleManager.getLayerFromId(layerId);
+ }
+
+ std::unordered_map<uint32_t, sp<LayerHandle>> mHandles;
+};
+
+TEST_F(LayerLifecycleManagerTest, addLayers) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ layers.emplace_back(rootLayer(2));
+ layers.emplace_back(rootLayer(3));
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.onHandlesDestroyed({1, 2, 3});
+ EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ lifecycleManager.commitChanges();
+ EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ listener->expectLayersAdded({1, 2, 3});
+ listener->expectLayersDestroyed({1, 2, 3});
+}
+
+TEST_F(LayerLifecycleManagerTest, updateLayerStates) {
+ LayerLifecycleManager lifecycleManager;
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ lifecycleManager.addLayers(std::move(layers));
+
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.z = 2;
+ transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
+ sp<LayerHandle> handle = sp<LayerHandle>::make(1u);
+ transactions.back().states.front().state.surface = handle;
+ lifecycleManager.applyTransactions(transactions);
+ transactions.clear();
+
+ auto& managedLayers = lifecycleManager.getLayers();
+ ASSERT_EQ(managedLayers.size(), 1u);
+
+ EXPECT_EQ(managedLayers.front()->z, 2);
+ EXPECT_TRUE(managedLayers.front()->changes.test(RequestedLayerState::Changes::Z));
+
+ EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ lifecycleManager.commitChanges();
+ EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ ASSERT_EQ(managedLayers.size(), 1u);
+ EXPECT_FALSE(managedLayers.front()->changes.test(RequestedLayerState::Changes::Z));
+
+ // apply transactions that do not affect the hierarchy
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.backgroundBlurRadius = 22;
+ transactions.back().states.front().state.what = layer_state_t::eBackgroundBlurRadiusChanged;
+ transactions.back().states.front().state.surface = handle;
+ lifecycleManager.applyTransactions(transactions);
+ EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ lifecycleManager.commitChanges();
+ EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ EXPECT_EQ(managedLayers.front()->backgroundBlurRadius, 22u);
+}
+
+TEST_F(LayerLifecycleManagerTest, layerWithoutHandleIsDestroyed) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ layers.emplace_back(rootLayer(2));
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.onHandlesDestroyed({1});
+ lifecycleManager.commitChanges();
+
+ SCOPED_TRACE("layerWithoutHandleIsDestroyed");
+ listener->expectLayersAdded({1, 2});
+ listener->expectLayersDestroyed({1});
+}
+
+TEST_F(LayerLifecycleManagerTest, rootLayerWithoutHandleIsDestroyed) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ layers.emplace_back(rootLayer(2));
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.onHandlesDestroyed({1});
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({1, 2});
+ listener->expectLayersDestroyed({1});
+}
+
+TEST_F(LayerLifecycleManagerTest, offscreenLayerIsDestroyed) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ layers.emplace_back(rootLayer(2));
+ layers.emplace_back(childLayer(3, /*parent*/ 2));
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({1, 2, 3});
+ listener->expectLayersDestroyed({});
+
+ lifecycleManager.applyTransactions({reparentLayer(3, UNASSIGNED_LAYER_ID)});
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({});
+ listener->expectLayersDestroyed({});
+
+ lifecycleManager.onHandlesDestroyed({3});
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({});
+ listener->expectLayersDestroyed({3});
+}
+
+TEST_F(LayerLifecycleManagerTest, offscreenChildLayerWithHandleIsNotDestroyed) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ layers.emplace_back(rootLayer(2));
+ layers.emplace_back(childLayer(3, /*parent*/ 2));
+ layers.emplace_back(childLayer(4, /*parent*/ 3));
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({1, 2, 3, 4});
+ listener->expectLayersDestroyed({});
+
+ lifecycleManager.applyTransactions({reparentLayer(3, UNASSIGNED_LAYER_ID)});
+ lifecycleManager.onHandlesDestroyed({3});
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({});
+ listener->expectLayersDestroyed({3});
+}
+
+TEST_F(LayerLifecycleManagerTest, offscreenChildLayerWithoutHandleIsDestroyed) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ layers.emplace_back(rootLayer(2));
+ layers.emplace_back(childLayer(3, /*parent*/ 2));
+ layers.emplace_back(childLayer(4, /*parent*/ 3));
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({1, 2, 3, 4});
+ listener->expectLayersDestroyed({});
+
+ lifecycleManager.applyTransactions({reparentLayer(3, UNASSIGNED_LAYER_ID)});
+ lifecycleManager.onHandlesDestroyed({3, 4});
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({});
+ listener->expectLayersDestroyed({3, 4});
+}
+
+TEST_F(LayerLifecycleManagerTest, reparentingDoesNotAffectRelativeZ) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ layers.emplace_back(rootLayer(2));
+ layers.emplace_back(childLayer(3, /*parent*/ 2));
+ layers.emplace_back(childLayer(4, /*parent*/ 3));
+
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({1, 2, 3, 4});
+ listener->expectLayersDestroyed({});
+
+ lifecycleManager.applyTransactions({makeRelative(4, 1)});
+ EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+ lifecycleManager.applyTransactions({reparentLayer(4, 2)});
+ EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({});
+ listener->expectLayersDestroyed({});
+}
+
+TEST_F(LayerLifecycleManagerTest, reparentingToNullRemovesRelativeZ) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ layers.emplace_back(rootLayer(2));
+ layers.emplace_back(childLayer(3, /*parent*/ 2));
+ layers.emplace_back(childLayer(4, /*parent*/ 3));
+
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({1, 2, 3, 4});
+ listener->expectLayersDestroyed({});
+
+ lifecycleManager.applyTransactions({makeRelative(4, 1)});
+ EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+ lifecycleManager.applyTransactions({reparentLayer(4, UNASSIGNED_LAYER_ID)});
+ EXPECT_FALSE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({});
+ listener->expectLayersDestroyed({});
+}
+
+TEST_F(LayerLifecycleManagerTest, setZRemovesRelativeZ) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ layers.emplace_back(rootLayer(2));
+ layers.emplace_back(childLayer(3, /*parent*/ 2));
+ layers.emplace_back(childLayer(4, /*parent*/ 3));
+
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({1, 2, 3, 4});
+ listener->expectLayersDestroyed({});
+
+ lifecycleManager.applyTransactions({makeRelative(4, 1)});
+ EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+ lifecycleManager.applyTransactions({setLayer(4, 1)});
+ EXPECT_FALSE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({});
+ listener->expectLayersDestroyed({});
+}
+
+TEST_F(LayerLifecycleManagerTest, canAddBackgroundLayer) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ lifecycleManager.addLayers(std::move(layers));
+
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.bgColorAlpha = 0.5;
+ transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+ sp<LayerHandle> handle = sp<LayerHandle>::make(1u);
+ transactions.back().states.front().state.surface = handle;
+ lifecycleManager.applyTransactions(transactions);
+
+ auto& managedLayers = lifecycleManager.getLayers();
+ ASSERT_EQ(managedLayers.size(), 2u);
+
+ EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({1, 2});
+ listener->expectLayersDestroyed({});
+ EXPECT_EQ(getRequestedLayerState(lifecycleManager, 2)->color.a, 0.5_hf);
+}
+
+TEST_F(LayerLifecycleManagerTest, canDestroyBackgroundLayer) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ lifecycleManager.addLayers(std::move(layers));
+
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.bgColorAlpha = 0.5;
+ transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+ sp<LayerHandle> handle = sp<LayerHandle>::make(1u);
+ transactions.back().states.front().state.surface = handle;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.bgColorAlpha = 0;
+ transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+ transactions.back().states.front().state.surface = handle;
+
+ lifecycleManager.applyTransactions(transactions);
+
+ ASSERT_EQ(lifecycleManager.getLayers().size(), 1u);
+ ASSERT_EQ(lifecycleManager.getDestroyedLayers().size(), 1u);
+
+ EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({1, 2});
+ listener->expectLayersDestroyed({2});
+}
+
+TEST_F(LayerLifecycleManagerTest, onParentDestroyDestroysBackgroundLayer) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ lifecycleManager.addLayers(std::move(layers));
+
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.bgColorAlpha = 0.5;
+ transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+ sp<LayerHandle> handle = sp<LayerHandle>::make(1u);
+ transactions.back().states.front().state.surface = handle;
+ transactions.emplace_back();
+ lifecycleManager.applyTransactions(transactions);
+ lifecycleManager.onHandlesDestroyed({1});
+
+ ASSERT_EQ(lifecycleManager.getLayers().size(), 0u);
+ ASSERT_EQ(lifecycleManager.getDestroyedLayers().size(), 2u);
+
+ EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({1, 2});
+ listener->expectLayersDestroyed({1, 2});
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index cedb7eb..8757e63 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -26,6 +26,7 @@
#include <log/log.h>
#include <ui/Size.h>
+#include <scheduler/FrameRateMode.h>
#include "DisplayHardware/HWC2.h"
#include "FpsOps.h"
#include "Scheduler/RefreshRateSelector.h"
@@ -44,9 +45,13 @@
using mock::createDisplayMode;
+// Use a C style macro to keep the line numbers printed in gtest
+#define EXPECT_SCORED_FRAME_RATE(modePtr, fps, scored) \
+ EXPECT_EQ((FrameRateMode{(fps), (modePtr)}), (scored).frameRateMode)
+
struct TestableRefreshRateSelector : RefreshRateSelector {
+ using RefreshRateSelector::FrameRateRanking;
using RefreshRateSelector::RefreshRateOrder;
- using RefreshRateSelector::RefreshRateRanking;
using RefreshRateSelector::RefreshRateSelector;
@@ -80,36 +85,41 @@
return getMaxRefreshRateByPolicyLocked(getActiveModeItLocked()->second->getGroup());
}
- RefreshRateRanking rankRefreshRates(std::optional<int> anchorGroupOpt,
- RefreshRateOrder refreshRateOrder) const {
+ FrameRateRanking rankRefreshRates(std::optional<int> anchorGroupOpt,
+ RefreshRateOrder refreshRateOrder) const {
std::lock_guard lock(mLock);
- return RefreshRateSelector::rankRefreshRates(anchorGroupOpt, refreshRateOrder);
+ return RefreshRateSelector::rankFrameRates(anchorGroupOpt, refreshRateOrder);
}
const std::vector<Fps>& knownFrameRates() const { return mKnownFrameRates; }
- using RefreshRateSelector::GetRankedRefreshRatesCache;
- auto& mutableGetRankedRefreshRatesCache() { return mGetRankedRefreshRatesCache; }
+ using RefreshRateSelector::GetRankedFrameRatesCache;
+ auto& mutableGetRankedRefreshRatesCache() { return mGetRankedFrameRatesCache; }
- auto getRankedRefreshRates(const std::vector<LayerRequirement>& layers,
- GlobalSignals signals) const {
- const auto result = RefreshRateSelector::getRankedRefreshRates(layers, signals);
+ auto getRankedFrameRates(const std::vector<LayerRequirement>& layers,
+ GlobalSignals signals) const {
+ const auto result = RefreshRateSelector::getRankedFrameRates(layers, signals);
EXPECT_TRUE(std::is_sorted(result.ranking.begin(), result.ranking.end(),
- ScoredRefreshRate::DescendingScore{}));
+ ScoredFrameRate::DescendingScore{}));
return result;
}
auto getRankedRefreshRatesAsPair(const std::vector<LayerRequirement>& layers,
GlobalSignals signals) const {
- const auto [ranking, consideredSignals] = getRankedRefreshRates(layers, signals);
+ const auto [ranking, consideredSignals] = getRankedFrameRates(layers, signals);
return std::make_pair(ranking, consideredSignals);
}
- DisplayModePtr getBestRefreshRate(const std::vector<LayerRequirement>& layers = {},
- GlobalSignals signals = {}) const {
- return getRankedRefreshRates(layers, signals).ranking.front().modePtr;
+ DisplayModePtr getBestFrameRateMode(const std::vector<LayerRequirement>& layers = {},
+ GlobalSignals signals = {}) const {
+ return getRankedFrameRates(layers, signals).ranking.front().frameRateMode.modePtr;
+ }
+
+ ScoredFrameRate getBestScoredFrameRate(const std::vector<LayerRequirement>& layers = {},
+ GlobalSignals signals = {}) const {
+ return getRankedFrameRates(layers, signals).ranking.front();
}
SetPolicyResult setPolicy(const PolicyVariant& policy) {
@@ -120,9 +130,11 @@
SetPolicyResult setDisplayManagerPolicy(const DisplayManagerPolicy& policy) {
return setPolicy(policy);
}
+
+ const auto& getPrimaryFrameRates() const { return mPrimaryFrameRates; }
};
-class RefreshRateSelectorTest : public testing::Test {
+class RefreshRateSelectorTest : public testing::TestWithParam<Config::FrameRateOverride> {
protected:
using RefreshRateOrder = TestableRefreshRateSelector::RefreshRateOrder;
@@ -140,6 +152,7 @@
static constexpr DisplayModeId kModeId24Frac{8};
static constexpr DisplayModeId kModeId30Frac{9};
static constexpr DisplayModeId kModeId60Frac{10};
+ static constexpr DisplayModeId kModeId35{11};
static inline const DisplayModePtr kMode60 = createDisplayMode(kModeId60, 60_Hz);
static inline const DisplayModePtr kMode60Frac = createDisplayMode(kModeId60Frac, 59.94_Hz);
@@ -156,12 +169,14 @@
static inline const DisplayModePtr kMode30Frac = createDisplayMode(kModeId30Frac, 29.97_Hz);
static inline const DisplayModePtr kMode25 = createDisplayMode(kModeId25, 25_Hz);
static inline const DisplayModePtr kMode25_G1 = createDisplayMode(kModeId25, 25_Hz, 1);
+ static inline const DisplayModePtr kMode35 = createDisplayMode(kModeId35, 35_Hz);
static inline const DisplayModePtr kMode50 = createDisplayMode(kModeId50, 50_Hz);
static inline const DisplayModePtr kMode24 = createDisplayMode(kModeId24, 24_Hz);
static inline const DisplayModePtr kMode24Frac = createDisplayMode(kModeId24Frac, 23.976_Hz);
// Test configurations.
static inline const DisplayModes kModes_60 = makeModes(kMode60);
+ static inline const DisplayModes kModes_35_60_90 = makeModes(kMode35, kMode60, kMode90);
static inline const DisplayModes kModes_60_90 = makeModes(kMode60, kMode90);
static inline const DisplayModes kModes_60_90_G1 = makeModes(kMode60, kMode90_G1);
static inline const DisplayModes kModes_60_90_4K = makeModes(kMode60, kMode90_4K);
@@ -185,6 +200,13 @@
static inline const DisplayModes kModes_24_25_30_50_60_Frac =
makeModes(kMode24, kMode24Frac, kMode25, kMode30, kMode30Frac, kMode50, kMode60,
kMode60Frac);
+
+ static TestableRefreshRateSelector createSelector(DisplayModes modes,
+ DisplayModeId activeModeId,
+ Config config = {}) {
+ config.enableFrameRateOverride = GetParam();
+ return TestableRefreshRateSelector(modes, activeModeId, config);
+ }
};
RefreshRateSelectorTest::RefreshRateSelectorTest() {
@@ -201,13 +223,23 @@
namespace {
-TEST_F(RefreshRateSelectorTest, oneMode_canSwitch) {
- RefreshRateSelector selector(kModes_60, kModeId60);
- EXPECT_FALSE(selector.canSwitch());
+INSTANTIATE_TEST_SUITE_P(PerOverrideConfig, RefreshRateSelectorTest,
+ testing::Values(Config::FrameRateOverride::Disabled,
+ Config::FrameRateOverride::AppOverrideNativeRefreshRates,
+ Config::FrameRateOverride::AppOverride,
+ Config::FrameRateOverride::Enabled));
+
+TEST_P(RefreshRateSelectorTest, oneMode_canSwitch) {
+ auto selector = createSelector(kModes_60, kModeId60);
+ if (GetParam() == Config::FrameRateOverride::Enabled) {
+ EXPECT_TRUE(selector.canSwitch());
+ } else {
+ EXPECT_FALSE(selector.canSwitch());
+ }
}
-TEST_F(RefreshRateSelectorTest, invalidPolicy) {
- TestableRefreshRateSelector selector(kModes_60, kModeId60);
+TEST_P(RefreshRateSelectorTest, invalidPolicy) {
+ auto selector = createSelector(kModes_60, kModeId60);
EXPECT_EQ(SetPolicyResult::Invalid,
selector.setDisplayManagerPolicy({DisplayModeId(10), {60_Hz, 60_Hz}}));
@@ -215,8 +247,8 @@
selector.setDisplayManagerPolicy({kModeId60, {20_Hz, 40_Hz}}));
}
-TEST_F(RefreshRateSelectorTest, unchangedPolicy) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, unchangedPolicy) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
EXPECT_EQ(SetPolicyResult::Changed,
selector.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}));
@@ -236,8 +268,8 @@
selector.setDisplayManagerPolicy({kModeId90, {30_Hz, 90_Hz}}));
}
-TEST_F(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
const auto minRate = selector.getMinSupportedRefreshRate();
const auto performanceRate = selector.getMaxSupportedRefreshRate();
@@ -252,8 +284,8 @@
EXPECT_EQ(performanceRateByPolicy, performanceRate);
}
-TEST_F(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap_differentGroups) {
- TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+TEST_P(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap_differentGroups) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
const auto minRate = selector.getMinRefreshRateByPolicy();
const auto performanceRate = selector.getMaxSupportedRefreshRate();
@@ -276,8 +308,8 @@
EXPECT_EQ(kMode90_G1, performanceRate90);
}
-TEST_F(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap_differentResolutions) {
- TestableRefreshRateSelector selector(kModes_60_90_4K, kModeId60);
+TEST_P(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap_differentResolutions) {
+ auto selector = createSelector(kModes_60_90_4K, kModeId60);
const auto minRate = selector.getMinRefreshRateByPolicy();
const auto performanceRate = selector.getMaxSupportedRefreshRate();
@@ -300,8 +332,8 @@
EXPECT_EQ(kMode90_4K, performanceRate90);
}
-TEST_F(RefreshRateSelectorTest, twoModes_policyChange) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, twoModes_policyChange) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
const auto minRate = selector.getMinRefreshRateByPolicy();
const auto performanceRate = selector.getMaxRefreshRateByPolicy();
@@ -319,8 +351,8 @@
EXPECT_EQ(kMode60, performanceRate60);
}
-TEST_F(RefreshRateSelectorTest, twoModes_getActiveMode) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, twoModes_getActiveMode) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
{
const auto& mode = selector.getActiveMode();
EXPECT_EQ(mode.getId(), kModeId60);
@@ -340,31 +372,31 @@
}
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_noLayers) {
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_noLayers) {
{
- TestableRefreshRateSelector selector(kModes_60_72_90, kModeId72);
+ auto selector = createSelector(kModes_60_72_90, kModeId72);
// If there are no layers we select the default frame rate, which is the max of the primary
// range.
- EXPECT_EQ(kMode90, selector.getBestRefreshRate());
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode());
EXPECT_EQ(SetPolicyResult::Changed,
selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
- EXPECT_EQ(kMode60, selector.getBestRefreshRate());
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode());
}
{
// We select max even when this will cause a non-seamless switch.
- TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
constexpr bool kAllowGroupSwitching = true;
EXPECT_EQ(SetPolicyResult::Changed,
selector.setDisplayManagerPolicy(
{kModeId90, {0_Hz, 90_Hz}, kAllowGroupSwitching}));
- EXPECT_EQ(kMode90_G1, selector.getBestRefreshRate());
+ EXPECT_EQ(kMode90_G1, selector.getBestFrameRateMode());
}
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_exactDontChangeRefreshRateWhenNotInPolicy) {
- TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId72);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_exactDontChangeRefreshRateWhenNotInPolicy) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId72);
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
layers[0].vote = LayerVoteType::ExplicitExact;
@@ -372,188 +404,187 @@
EXPECT_EQ(SetPolicyResult::Changed,
selector.setDisplayManagerPolicy({kModeId72, {0_Hz, 90_Hz}}));
- EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_60_90) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_60_90) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
auto& lr = layers[0];
lr.vote = LayerVoteType::Min;
lr.name = "Min";
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.vote = LayerVoteType::Max;
lr.name = "Max";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 90_Hz;
lr.vote = LayerVoteType::Heuristic;
lr.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 60_Hz;
lr.name = "60Hz Heuristic";
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 45_Hz;
lr.name = "45Hz Heuristic";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 30_Hz;
lr.name = "30Hz Heuristic";
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 24_Hz;
lr.name = "24Hz Heuristic";
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.name = "";
EXPECT_EQ(SetPolicyResult::Changed,
selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
lr.vote = LayerVoteType::Min;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.vote = LayerVoteType::Max;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 90_Hz;
lr.vote = LayerVoteType::Heuristic;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 45_Hz;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 30_Hz;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 24_Hz;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
EXPECT_EQ(SetPolicyResult::Changed,
selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}));
lr.vote = LayerVoteType::Min;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.vote = LayerVoteType::Max;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 90_Hz;
lr.vote = LayerVoteType::Heuristic;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 45_Hz;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 30_Hz;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 24_Hz;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
EXPECT_EQ(SetPolicyResult::Changed,
selector.setDisplayManagerPolicy({kModeId60, {0_Hz, 120_Hz}}));
lr.vote = LayerVoteType::Min;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.vote = LayerVoteType::Max;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 90_Hz;
lr.vote = LayerVoteType::Heuristic;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 45_Hz;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 30_Hz;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 24_Hz;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_multipleThreshold_60_90) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId60,
- {.frameRateMultipleThreshold = 90});
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_multipleThreshold_60_90) {
+ auto selector = createSelector(kModes_60_90, kModeId60, {.frameRateMultipleThreshold = 90});
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
auto& lr = layers[0];
lr.vote = LayerVoteType::Min;
lr.name = "Min";
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.vote = LayerVoteType::Max;
lr.name = "Max";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 90_Hz;
lr.vote = LayerVoteType::Heuristic;
lr.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 60_Hz;
lr.name = "60Hz Heuristic";
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 45_Hz;
lr.name = "45Hz Heuristic";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 30_Hz;
lr.name = "30Hz Heuristic";
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 24_Hz;
lr.name = "24Hz Heuristic";
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_60_72_90) {
- TestableRefreshRateSelector selector(kModes_60_72_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_60_72_90) {
+ auto selector = createSelector(kModes_60_72_90, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
auto& lr = layers[0];
lr.vote = LayerVoteType::Min;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.vote = LayerVoteType::Max;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 90_Hz;
lr.vote = LayerVoteType::Heuristic;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 45_Hz;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 30_Hz;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 24_Hz;
- EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_30_60_72_90_120) {
- TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60_72_90_120) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
auto& lr1 = layers[0];
@@ -563,23 +594,23 @@
lr1.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 60_Hz;
lr2.vote = LayerVoteType::Heuristic;
- EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 48_Hz;
lr2.vote = LayerVoteType::Heuristic;
- EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 48_Hz;
lr2.vote = LayerVoteType::Heuristic;
- EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_30_60_90_120_DifferentTypes) {
- TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60_90_120_DifferentTypes) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
auto& lr1 = layers[0];
@@ -591,7 +622,7 @@
lr2.desiredRefreshRate = 60_Hz;
lr2.vote = LayerVoteType::Heuristic;
lr2.name = "60Hz Heuristic";
- EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -599,7 +630,7 @@
lr2.desiredRefreshRate = 60_Hz;
lr2.vote = LayerVoteType::Heuristic;
lr2.name = "60Hz Heuristic";
- EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -607,7 +638,7 @@
lr2.desiredRefreshRate = 60_Hz;
lr2.vote = LayerVoteType::ExplicitDefault;
lr2.name = "60Hz ExplicitDefault";
- EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -615,7 +646,7 @@
lr2.desiredRefreshRate = 90_Hz;
lr2.vote = LayerVoteType::Heuristic;
lr2.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -623,7 +654,7 @@
lr2.desiredRefreshRate = 90_Hz;
lr2.vote = LayerVoteType::ExplicitDefault;
lr2.name = "90Hz Heuristic";
- EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::ExplicitDefault;
@@ -631,7 +662,7 @@
lr2.desiredRefreshRate = 90_Hz;
lr2.vote = LayerVoteType::Heuristic;
lr2.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::Heuristic;
@@ -639,7 +670,7 @@
lr2.desiredRefreshRate = 90_Hz;
lr2.vote = LayerVoteType::ExplicitDefault;
lr2.name = "90Hz ExplicitDefault";
- EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -647,7 +678,7 @@
lr2.desiredRefreshRate = 90_Hz;
lr2.vote = LayerVoteType::ExplicitDefault;
lr2.name = "90Hz ExplicitDefault";
- EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::ExplicitDefault;
@@ -655,12 +686,13 @@
lr2.desiredRefreshRate = 90_Hz;
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
lr2.name = "90Hz ExplicitExactOrMultiple";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_30_60_90_120_DifferentTypes_multipleThreshold) {
- TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60,
- {.frameRateMultipleThreshold = 120});
+TEST_P(RefreshRateSelectorTest,
+ getBestFrameRateMode_30_60_90_120_DifferentTypes_multipleThreshold) {
+ auto selector =
+ createSelector(kModes_30_60_72_90_120, kModeId60, {.frameRateMultipleThreshold = 120});
std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}, {.weight = 1.f}};
auto& lr1 = layers[0];
@@ -673,7 +705,7 @@
lr2.desiredRefreshRate = 60_Hz;
lr2.vote = LayerVoteType::Heuristic;
lr2.name = "60Hz Heuristic";
- EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -681,7 +713,7 @@
lr2.desiredRefreshRate = 60_Hz;
lr2.vote = LayerVoteType::Heuristic;
lr2.name = "60Hz Heuristic";
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -689,7 +721,7 @@
lr2.desiredRefreshRate = 60_Hz;
lr2.vote = LayerVoteType::ExplicitDefault;
lr2.name = "60Hz ExplicitDefault";
- EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -697,7 +729,7 @@
lr2.desiredRefreshRate = 90_Hz;
lr2.vote = LayerVoteType::Heuristic;
lr2.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -705,7 +737,7 @@
lr2.desiredRefreshRate = 90_Hz;
lr2.vote = LayerVoteType::ExplicitDefault;
lr2.name = "90Hz Heuristic";
- EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::ExplicitDefault;
@@ -713,7 +745,7 @@
lr2.desiredRefreshRate = 90_Hz;
lr2.vote = LayerVoteType::Heuristic;
lr2.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::Heuristic;
@@ -721,7 +753,7 @@
lr2.desiredRefreshRate = 90_Hz;
lr2.vote = LayerVoteType::ExplicitDefault;
lr2.name = "90Hz ExplicitDefault";
- EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -729,7 +761,7 @@
lr2.desiredRefreshRate = 90_Hz;
lr2.vote = LayerVoteType::ExplicitDefault;
lr2.name = "90Hz ExplicitDefault";
- EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::ExplicitDefault;
@@ -737,14 +769,14 @@
lr2.desiredRefreshRate = 90_Hz;
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
lr2.name = "90Hz ExplicitExactOrMultiple";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.name = "24Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::Max;
lr2.name = "Max";
- EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -752,7 +784,7 @@
lr2.desiredRefreshRate = 120_Hz;
lr2.vote = LayerVoteType::ExplicitDefault;
lr2.name = "120Hz ExplicitDefault";
- EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 24_Hz;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -760,7 +792,7 @@
lr2.desiredRefreshRate = 120_Hz;
lr2.vote = LayerVoteType::ExplicitExact;
lr2.name = "120Hz ExplicitExact";
- EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 10_Hz;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -768,7 +800,7 @@
lr2.desiredRefreshRate = 120_Hz;
lr2.vote = LayerVoteType::Heuristic;
lr2.name = "120Hz ExplicitExact";
- EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
lr1.desiredRefreshRate = 30_Hz;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -779,86 +811,86 @@
lr3.vote = LayerVoteType::Heuristic;
lr3.desiredRefreshRate = 120_Hz;
lr3.name = "120Hz Heuristic";
- EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_30_60) {
- TestableRefreshRateSelector selector(kModes_30_60, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60) {
+ auto selector = createSelector(kModes_30_60, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
auto& lr = layers[0];
lr.vote = LayerVoteType::Min;
- EXPECT_EQ(kMode30, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
lr.vote = LayerVoteType::Max;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 90_Hz;
lr.vote = LayerVoteType::Heuristic;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 45_Hz;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 30_Hz;
- EXPECT_EQ(kMode30, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 24_Hz;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_30_60_72_90) {
- TestableRefreshRateSelector selector(kModes_30_60_72_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60_72_90) {
+ auto selector = createSelector(kModes_30_60_72_90, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
auto& lr = layers[0];
lr.vote = LayerVoteType::Min;
lr.name = "Min";
- EXPECT_EQ(kMode30, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
lr.vote = LayerVoteType::Max;
lr.name = "Max";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 90_Hz;
lr.vote = LayerVoteType::Heuristic;
lr.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.desiredRefreshRate = 60_Hz;
lr.name = "60Hz Heuristic";
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true}));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
lr.desiredRefreshRate = 45_Hz;
lr.name = "45Hz Heuristic";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true}));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
lr.desiredRefreshRate = 30_Hz;
lr.name = "30Hz Heuristic";
- EXPECT_EQ(kMode30, selector.getBestRefreshRate(layers));
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true}));
+ EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
lr.desiredRefreshRate = 24_Hz;
lr.name = "24Hz Heuristic";
- EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true}));
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
lr.desiredRefreshRate = 24_Hz;
lr.vote = LayerVoteType::ExplicitExactOrMultiple;
lr.name = "24Hz ExplicitExactOrMultiple";
- EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true}));
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_PriorityTest) {
- TestableRefreshRateSelector selector(kModes_30_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_PriorityTest) {
+ auto selector = createSelector(kModes_30_60_90, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
auto& lr1 = layers[0];
@@ -866,43 +898,43 @@
lr1.vote = LayerVoteType::Min;
lr2.vote = LayerVoteType::Max;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr1.vote = LayerVoteType::Min;
lr2.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 24_Hz;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr1.vote = LayerVoteType::Min;
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
lr2.desiredRefreshRate = 24_Hz;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr1.vote = LayerVoteType::Max;
lr2.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr1.vote = LayerVoteType::Max;
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
lr2.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr1.vote = LayerVoteType::Heuristic;
lr1.desiredRefreshRate = 15_Hz;
lr2.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 45_Hz;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr1.vote = LayerVoteType::Heuristic;
lr1.desiredRefreshRate = 30_Hz;
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
lr2.desiredRefreshRate = 45_Hz;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_24FpsVideo) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_24FpsVideo) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
auto& lr = layers[0];
@@ -910,15 +942,14 @@
lr.vote = LayerVoteType::ExplicitExactOrMultiple;
for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
lr.desiredRefreshRate = Fps::fromValue(fps);
- const auto mode = selector.getBestRefreshRate(layers);
+ const auto mode = selector.getBestFrameRateMode(layers);
EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses "
<< to_string(mode->getFps());
}
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_24FpsVideo_multipleThreshold_60_120) {
- TestableRefreshRateSelector selector(kModes_60_120, kModeId60,
- {.frameRateMultipleThreshold = 120});
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_24FpsVideo_multipleThreshold_60_120) {
+ auto selector = createSelector(kModes_60_120, kModeId60, {.frameRateMultipleThreshold = 120});
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
auto& lr = layers[0];
@@ -926,14 +957,14 @@
lr.vote = LayerVoteType::ExplicitExactOrMultiple;
for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
lr.desiredRefreshRate = Fps::fromValue(fps);
- const auto mode = selector.getBestRefreshRate(layers);
+ const auto mode = selector.getBestFrameRateMode(layers);
EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses "
<< to_string(mode->getFps());
}
}
-TEST_F(RefreshRateSelectorTest, twoModes_getBestRefreshRate_Explicit) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, twoModes_getBestFrameRateMode_Explicit) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
auto& lr1 = layers[0];
@@ -943,23 +974,23 @@
lr1.desiredRefreshRate = 60_Hz;
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
lr2.desiredRefreshRate = 90_Hz;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr1.vote = LayerVoteType::ExplicitDefault;
lr1.desiredRefreshRate = 90_Hz;
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
lr2.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr1.vote = LayerVoteType::Heuristic;
lr1.desiredRefreshRate = 90_Hz;
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
lr2.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_75HzContent) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_75HzContent) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
auto& lr = layers[0];
@@ -967,14 +998,14 @@
lr.vote = LayerVoteType::ExplicitExactOrMultiple;
for (float fps = 75.0f; fps < 100.0f; fps += 0.1f) {
lr.desiredRefreshRate = Fps::fromValue(fps);
- const auto mode = selector.getBestRefreshRate(layers, {});
+ const auto mode = selector.getBestFrameRateMode(layers, {});
EXPECT_EQ(kMode90, mode) << lr.desiredRefreshRate << " chooses "
<< to_string(mode->getFps());
}
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_Multiples) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_Multiples) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
auto& lr1 = layers[0];
@@ -986,7 +1017,7 @@
lr2.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 90_Hz;
lr2.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.desiredRefreshRate = 60_Hz;
@@ -994,14 +1025,14 @@
lr2.vote = LayerVoteType::ExplicitDefault;
lr2.desiredRefreshRate = 90_Hz;
lr2.name = "90Hz ExplicitDefault";
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.desiredRefreshRate = 60_Hz;
lr1.name = "60Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::Max;
lr2.name = "Max";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.desiredRefreshRate = 30_Hz;
@@ -1009,18 +1040,18 @@
lr2.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 90_Hz;
lr2.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.desiredRefreshRate = 30_Hz;
lr1.name = "30Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::Max;
lr2.name = "Max";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
}
-TEST_F(RefreshRateSelectorTest, scrollWhileWatching60fps_60_90) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, scrollWhileWatching60fps_60_90) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
auto& lr1 = layers[0];
@@ -1031,28 +1062,28 @@
lr1.name = "60Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::NoVote;
lr2.name = "NoVote";
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.desiredRefreshRate = 60_Hz;
lr1.name = "60Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::NoVote;
lr2.name = "NoVote";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true}));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.desiredRefreshRate = 60_Hz;
lr1.name = "60Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::Max;
lr2.name = "Max";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true}));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.desiredRefreshRate = 60_Hz;
lr1.name = "60Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::Max;
lr2.name = "Max";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
// The other layer starts to provide buffers
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -1061,49 +1092,70 @@
lr2.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 90_Hz;
lr2.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
}
-TEST_F(RefreshRateSelectorTest, getMaxRefreshRatesByPolicy) {
+TEST_P(RefreshRateSelectorTest, getMaxRefreshRatesByPolicy) {
// The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
// different group.
- TestableRefreshRateSelector selector(kModes_30_60_90, kModeId60);
-
+ auto selector = createSelector(kModes_30_60_90, kModeId60);
const auto refreshRates = selector.rankRefreshRates(selector.getActiveMode().getGroup(),
RefreshRateOrder::Descending);
- const std::array expectedRefreshRates = {kMode90, kMode60, kMode30};
+ const auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{90_Hz, kMode90}, {60_Hz, kMode60}, {30_Hz, kMode30}};
+ case Config::FrameRateOverride::Enabled:
+ return {{90_Hz, kMode90}, {60_Hz, kMode60}, {45_Hz, kMode90}, {30_Hz, kMode30}};
+ }
+ }();
ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
- EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
- << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
- << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+ << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+ << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+ << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+ << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
}
}
-TEST_F(RefreshRateSelectorTest, getMinRefreshRatesByPolicy) {
+TEST_P(RefreshRateSelectorTest, getMinRefreshRatesByPolicy) {
// The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
// different group.
- TestableRefreshRateSelector selector(kModes_30_60_90, kModeId60);
+ auto selector = createSelector(kModes_30_60_90, kModeId60);
const auto refreshRates = selector.rankRefreshRates(selector.getActiveMode().getGroup(),
RefreshRateOrder::Ascending);
- const std::array expectedRefreshRates = {kMode30, kMode60, kMode90};
+ const auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+ case Config::FrameRateOverride::Enabled:
+ return {{30_Hz, kMode30}, {45_Hz, kMode90}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+ }
+ }();
ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
- EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
- << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
- << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+ << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+ << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+ << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+ << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
}
}
-TEST_F(RefreshRateSelectorTest, getMinRefreshRatesByPolicyOutsideTheGroup) {
+TEST_P(RefreshRateSelectorTest, getMinRefreshRatesByPolicyOutsideTheGroup) {
// The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
// different group.
- TestableRefreshRateSelector selector(kModes_30_60_90, kModeId72);
+ auto selector = createSelector(kModes_30_60_90, kModeId72);
EXPECT_EQ(SetPolicyResult::Changed,
selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}}));
@@ -1111,20 +1163,31 @@
const auto refreshRates =
selector.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Ascending);
- const std::array expectedRefreshRates = {kMode30, kMode60, kMode90};
+ const auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+ case Config::FrameRateOverride::Enabled:
+ return {{30_Hz, kMode30}, {45_Hz, kMode90}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+ }
+ }();
ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
- EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
- << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
- << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+ << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+ << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+ << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+ << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
}
}
-TEST_F(RefreshRateSelectorTest, getMaxRefreshRatesByPolicyOutsideTheGroup) {
+TEST_P(RefreshRateSelectorTest, getMaxRefreshRatesByPolicyOutsideTheGroup) {
// The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
// different group.
- TestableRefreshRateSelector selector(kModes_30_60_90, kModeId72);
+ auto selector = createSelector(kModes_30_60_90, kModeId72);
EXPECT_EQ(SetPolicyResult::Changed,
selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}}));
@@ -1132,29 +1195,52 @@
const auto refreshRates = selector.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt,
RefreshRateOrder::Descending);
- const std::array expectedRefreshRates = {kMode90, kMode60, kMode30};
+ const auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{90_Hz, kMode90}, {60_Hz, kMode60}, {30_Hz, kMode30}};
+ case Config::FrameRateOverride::Enabled:
+ return {{90_Hz, kMode90}, {60_Hz, kMode60}, {45_Hz, kMode90}, {30_Hz, kMode30}};
+ }
+ }();
ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
- EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
- << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
- << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+ << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+ << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+ << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+ << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
}
}
-TEST_F(RefreshRateSelectorTest, powerOnImminentConsidered) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
- auto [refreshRates, signals] = selector.getRankedRefreshRates({}, {});
+ auto [refreshRates, signals] = selector.getRankedFrameRates({}, {});
EXPECT_FALSE(signals.powerOnImminent);
- std::array expectedRefreshRates = {kMode90, kMode60};
+ auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{90_Hz, kMode90}, {60_Hz, kMode60}};
+ case Config::FrameRateOverride::Enabled:
+ return {{90_Hz, kMode90}, {60_Hz, kMode60}, {45_Hz, kMode90},
+ {30_Hz, kMode60}, {22.5_Hz, kMode90}, {20_Hz, kMode60}};
+ }
+ }();
ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
- EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
- << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
- << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+ << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+ << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+ << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+ << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
}
std::tie(refreshRates, signals) =
@@ -1164,9 +1250,11 @@
ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
- EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
- << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
- << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+ << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+ << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+ << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+ << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
}
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
@@ -1182,29 +1270,43 @@
ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
- EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
- << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
- << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+ << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+ << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+ << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+ << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
}
std::tie(refreshRates, signals) =
selector.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = false});
EXPECT_FALSE(signals.powerOnImminent);
- expectedRefreshRates = {kMode60, kMode90};
+ expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{60_Hz, kMode60}, {90_Hz, kMode90}};
+ case Config::FrameRateOverride::Enabled:
+ return {{60_Hz, kMode60}, {90_Hz, kMode90}, {45_Hz, kMode90},
+ {30_Hz, kMode60}, {22.5_Hz, kMode90}, {20_Hz, kMode60}};
+ }
+ }();
ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
- EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
- << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
- << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+ << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+ << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+ << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+ << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
}
}
-TEST_F(RefreshRateSelectorTest, touchConsidered) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, touchConsidered) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
- auto [_, signals] = selector.getRankedRefreshRates({}, {});
+ auto [_, signals] = selector.getRankedFrameRates({}, {});
EXPECT_FALSE(signals.touch);
std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair({}, {.touch = true});
@@ -1251,8 +1353,8 @@
EXPECT_FALSE(signals.touch);
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitDefault) {
- TestableRefreshRateSelector selector(kModes_60_90_72_120, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitDefault) {
+ auto selector = createSelector(kModes_60_90_72_120, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
auto& lr = layers[0];
@@ -1284,57 +1386,57 @@
ss << "ExplicitDefault " << desired;
lr.name = ss.str();
- EXPECT_EQ(expected, selector.getBestRefreshRate(layers)->getFps());
+ EXPECT_EQ(expected, selector.getBestFrameRateMode(layers)->getFps());
}
}
-TEST_F(RefreshRateSelectorTest,
- getBestRefreshRate_ExplicitExactOrMultiple_WithFractionalRefreshRates) {
+TEST_P(RefreshRateSelectorTest,
+ getBestFrameRateMode_ExplicitExactOrMultiple_WithFractionalRefreshRates) {
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
auto& lr = layers[0];
// Test that 23.976 will choose 24 if 23.976 is not supported
{
- TestableRefreshRateSelector selector(makeModes(kMode24, kMode25, kMode30, kMode30Frac,
- kMode60, kMode60Frac),
- kModeId60);
+ auto selector = createSelector(makeModes(kMode24, kMode25, kMode30, kMode30Frac, kMode60,
+ kMode60Frac),
+ kModeId60);
lr.vote = LayerVoteType::ExplicitExactOrMultiple;
lr.desiredRefreshRate = 23.976_Hz;
lr.name = "ExplicitExactOrMultiple 23.976 Hz";
- EXPECT_EQ(kModeId24, selector.getBestRefreshRate(layers)->getId());
+ EXPECT_EQ(kModeId24, selector.getBestFrameRateMode(layers)->getId());
}
// Test that 24 will choose 23.976 if 24 is not supported
{
- TestableRefreshRateSelector selector(makeModes(kMode24Frac, kMode25, kMode30, kMode30Frac,
- kMode60, kMode60Frac),
- kModeId60);
+ auto selector = createSelector(makeModes(kMode24Frac, kMode25, kMode30, kMode30Frac,
+ kMode60, kMode60Frac),
+ kModeId60);
lr.desiredRefreshRate = 24_Hz;
lr.name = "ExplicitExactOrMultiple 24 Hz";
- EXPECT_EQ(kModeId24Frac, selector.getBestRefreshRate(layers)->getId());
+ EXPECT_EQ(kModeId24Frac, selector.getBestFrameRateMode(layers)->getId());
}
// Test that 29.97 will prefer 59.94 over 60 and 30
{
- TestableRefreshRateSelector selector(makeModes(kMode24, kMode24Frac, kMode25, kMode30,
- kMode60, kMode60Frac),
- kModeId60);
+ auto selector = createSelector(makeModes(kMode24, kMode24Frac, kMode25, kMode30, kMode60,
+ kMode60Frac),
+ kModeId60);
lr.desiredRefreshRate = 29.97_Hz;
lr.name = "ExplicitExactOrMultiple 29.97 Hz";
- EXPECT_EQ(kModeId60Frac, selector.getBestRefreshRate(layers)->getId());
+ EXPECT_EQ(kModeId60Frac, selector.getBestFrameRateMode(layers)->getId());
}
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExact_WithFractionalRefreshRates) {
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitExact_WithFractionalRefreshRates) {
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
auto& lr = layers[0];
// Test that voting for supported refresh rate will select this refresh rate
{
- TestableRefreshRateSelector selector(kModes_24_25_30_50_60_Frac, kModeId60);
+ auto selector = createSelector(kModes_24_25_30_50_60_Frac, kModeId60);
for (auto desired : {23.976_Hz, 24_Hz, 25_Hz, 29.97_Hz, 30_Hz, 50_Hz, 59.94_Hz, 60_Hz}) {
lr.vote = LayerVoteType::ExplicitExact;
@@ -1343,14 +1445,14 @@
ss << "ExplicitExact " << desired;
lr.name = ss.str();
- EXPECT_EQ(lr.desiredRefreshRate, selector.getBestRefreshRate(layers)->getFps());
+ EXPECT_EQ(lr.desiredRefreshRate, selector.getBestFrameRateMode(layers)->getFps());
}
}
}
-TEST_F(RefreshRateSelectorTest,
- getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresTouchFlag) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId90);
+TEST_P(RefreshRateSelectorTest,
+ getBestFrameRateMode_withDisplayManagerRequestingSingleRate_ignoresTouchFlag) {
+ auto selector = createSelector(kModes_60_90, kModeId90);
constexpr FpsRange k90 = {90_Hz, 90_Hz};
constexpr FpsRange k60_90 = {60_Hz, 90_Hz};
@@ -1365,16 +1467,16 @@
lr.name = "60Hz ExplicitDefault";
lr.focused = true;
- const auto [mode, signals] =
- selector.getRankedRefreshRates(layers, {.touch = true, .idle = true});
+ const auto [rankedFrameRate, signals] =
+ selector.getRankedFrameRates(layers, {.touch = true, .idle = true});
- EXPECT_EQ(mode.begin()->modePtr, kMode60);
+ EXPECT_EQ(rankedFrameRate.begin()->frameRateMode.modePtr, kMode60);
EXPECT_FALSE(signals.touch);
}
-TEST_F(RefreshRateSelectorTest,
- getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresIdleFlag) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest,
+ getBestFrameRateMode_withDisplayManagerRequestingSingleRate_ignoresIdleFlag) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
constexpr FpsRange k60 = {60_Hz, 60_Hz};
constexpr FpsRange k60_90 = {60_Hz, 90_Hz};
@@ -1389,11 +1491,11 @@
lr.desiredRefreshRate = 90_Hz;
lr.name = "90Hz ExplicitDefault";
lr.focused = true;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.idle = true}));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.idle = true}));
}
-TEST_F(RefreshRateSelectorTest, testDisplayModeOrdering) {
- TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60);
+TEST_P(RefreshRateSelectorTest, testDisplayModeOrdering) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 1.f},
{.weight = 1.f},
@@ -1426,15 +1528,31 @@
lr5.name = "30Hz";
lr5.focused = true;
- std::array expectedRanking = {kMode120, kMode90, kMode72, kMode60, kMode30};
- auto actualRanking = selector.getRankedRefreshRates(layers, {}).ranking;
+ auto expectedRanking = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{120_Hz, kMode120},
+ {90_Hz, kMode90},
+ {72_Hz, kMode72},
+ {60_Hz, kMode60},
+ {30_Hz, kMode30}};
+ case Config::FrameRateOverride::Enabled:
+ return {{120_Hz, kMode120}, {90_Hz, kMode90}, {72_Hz, kMode72}, {60_Hz, kMode60},
+ {45_Hz, kMode90}, {40_Hz, kMode120}, {36_Hz, kMode72}, {30_Hz, kMode30}};
+ }
+ }();
+ auto actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
ASSERT_EQ(expectedRanking.size(), actualRanking.size());
for (size_t i = 0; i < expectedRanking.size(); ++i) {
- EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
- << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
- << actualRanking[i].modePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+ << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+ << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+ << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+ << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
}
lr1.vote = LayerVoteType::Max;
@@ -1452,15 +1570,31 @@
lr5.desiredRefreshRate = 120_Hz;
lr5.name = "120Hz";
- expectedRanking = {kMode120, kMode90, kMode72, kMode60, kMode30};
- actualRanking = selector.getRankedRefreshRates(layers, {}).ranking;
+ expectedRanking = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{120_Hz, kMode120},
+ {90_Hz, kMode90},
+ {72_Hz, kMode72},
+ {60_Hz, kMode60},
+ {30_Hz, kMode30}};
+ case Config::FrameRateOverride::Enabled:
+ return {{120_Hz, kMode120}, {90_Hz, kMode90}, {72_Hz, kMode72}, {60_Hz, kMode60},
+ {45_Hz, kMode90}, {40_Hz, kMode120}, {36_Hz, kMode72}, {30_Hz, kMode30}};
+ }
+ }();
+ actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
ASSERT_EQ(expectedRanking.size(), actualRanking.size());
for (size_t i = 0; i < expectedRanking.size(); ++i) {
- EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
- << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
- << actualRanking[i].modePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+ << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+ << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+ << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+ << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
}
lr1.vote = LayerVoteType::Heuristic;
@@ -1476,15 +1610,31 @@
lr5.desiredRefreshRate = 72_Hz;
lr5.name = "72Hz";
- expectedRanking = {kMode30, kMode60, kMode90, kMode120, kMode72};
- actualRanking = selector.getRankedRefreshRates(layers, {}).ranking;
+ expectedRanking = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{30_Hz, kMode30},
+ {60_Hz, kMode60},
+ {90_Hz, kMode90},
+ {120_Hz, kMode120},
+ {72_Hz, kMode72}};
+ case Config::FrameRateOverride::Enabled:
+ return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}, {120_Hz, kMode120},
+ {45_Hz, kMode90}, {40_Hz, kMode120}, {72_Hz, kMode72}, {36_Hz, kMode72}};
+ }
+ }();
+ actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
ASSERT_EQ(expectedRanking.size(), actualRanking.size());
for (size_t i = 0; i < expectedRanking.size(); ++i) {
- EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
- << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
- << actualRanking[i].modePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+ << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+ << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+ << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+ << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
}
lr1.desiredRefreshRate = 120_Hz;
@@ -1503,21 +1653,37 @@
lr5.desiredRefreshRate = 120_Hz;
lr5.name = "120Hz-2";
- expectedRanking = {kMode90, kMode60, kMode120, kMode72, kMode30};
- actualRanking = selector.getRankedRefreshRates(layers, {}).ranking;
+ expectedRanking = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{90_Hz, kMode90},
+ {60_Hz, kMode60},
+ {120_Hz, kMode120},
+ {72_Hz, kMode72},
+ {30_Hz, kMode30}};
+ case Config::FrameRateOverride::Enabled:
+ return {{90_Hz, kMode90}, {60_Hz, kMode60}, {120_Hz, kMode120}, {72_Hz, kMode72},
+ {45_Hz, kMode90}, {40_Hz, kMode120}, {36_Hz, kMode72}, {30_Hz, kMode30}};
+ }
+ }();
+ actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
ASSERT_EQ(expectedRanking.size(), actualRanking.size());
for (size_t i = 0; i < expectedRanking.size(); ++i) {
- EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
- << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
- << actualRanking[i].modePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+ << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+ << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+ << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+ << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
}
}
-TEST_F(RefreshRateSelectorTest,
- getBestRefreshRate_withDisplayManagerRequestingSingleRate_onlySwitchesRatesForExplicitFocusedLayers) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId90);
+TEST_P(RefreshRateSelectorTest,
+ getBestFrameRateMode_withDisplayManagerRequestingSingleRate_onlySwitchesRatesForExplicitFocusedLayers) {
+ auto selector = createSelector(kModes_60_90, kModeId90);
constexpr FpsRange k90 = {90_Hz, 90_Hz};
constexpr FpsRange k60_90 = {60_Hz, 90_Hz};
@@ -1525,8 +1691,8 @@
EXPECT_EQ(SetPolicyResult::Changed,
selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}}));
- const auto [ranking, signals] = selector.getRankedRefreshRates({}, {});
- EXPECT_EQ(ranking.front().modePtr, kMode90);
+ const auto [ranking, signals] = selector.getRankedFrameRates({}, {});
+ EXPECT_EQ(ranking.front().frameRateMode.modePtr, kMode90);
EXPECT_FALSE(signals.touch);
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
@@ -1536,50 +1702,50 @@
lr.desiredRefreshRate = 60_Hz;
lr.name = "60Hz ExplicitExactOrMultiple";
lr.focused = false;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.focused = true;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.vote = LayerVoteType::ExplicitDefault;
lr.desiredRefreshRate = 60_Hz;
lr.name = "60Hz ExplicitDefault";
lr.focused = false;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.focused = true;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
lr.vote = LayerVoteType::Heuristic;
lr.desiredRefreshRate = 60_Hz;
lr.name = "60Hz Heuristic";
lr.focused = false;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.focused = true;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.vote = LayerVoteType::Max;
lr.desiredRefreshRate = 60_Hz;
lr.name = "60Hz Max";
lr.focused = false;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.focused = true;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.vote = LayerVoteType::Min;
lr.desiredRefreshRate = 60_Hz;
lr.name = "60Hz Min";
lr.focused = false;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
lr.focused = true;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
}
-TEST_F(RefreshRateSelectorTest, groupSwitchingNotAllowed) {
- TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+TEST_P(RefreshRateSelectorTest, groupSwitchingNotAllowed) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
// The default policy doesn't allow group switching. Verify that no
// group switches are performed.
@@ -1591,11 +1757,11 @@
layer.name = "90Hz ExplicitDefault";
layer.focused = true;
- EXPECT_EQ(kModeId60, selector.getBestRefreshRate(layers)->getId());
+ EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
}
-TEST_F(RefreshRateSelectorTest, groupSwitchingWithOneLayer) {
- TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayer) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
RefreshRateSelector::DisplayManagerPolicy policy;
policy.defaultMode = selector.getCurrentPolicy().defaultMode;
@@ -1609,11 +1775,11 @@
layer.seamlessness = Seamlessness::SeamedAndSeamless;
layer.name = "90Hz ExplicitDefault";
layer.focused = true;
- EXPECT_EQ(kModeId90, selector.getBestRefreshRate(layers)->getId());
+ EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
}
-TEST_F(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamless) {
- TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamless) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
RefreshRateSelector::DisplayManagerPolicy policy;
policy.defaultMode = selector.getCurrentPolicy().defaultMode;
@@ -1628,11 +1794,11 @@
layer.seamlessness = Seamlessness::OnlySeamless;
layer.name = "90Hz ExplicitDefault";
layer.focused = true;
- EXPECT_EQ(kModeId60, selector.getBestRefreshRate(layers)->getId());
+ EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
}
-TEST_F(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps) {
- TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
RefreshRateSelector::DisplayManagerPolicy policy;
policy.defaultMode = selector.getCurrentPolicy().defaultMode;
@@ -1649,11 +1815,11 @@
layer.seamlessness = Seamlessness::OnlySeamless;
layer.name = "60Hz ExplicitDefault";
layer.focused = true;
- EXPECT_EQ(kModeId90, selector.getBestRefreshRate(layers)->getId());
+ EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
}
-TEST_F(RefreshRateSelectorTest, groupSwitchingWithOneLayerDefaultSeamlessness) {
- TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerDefaultSeamlessness) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
RefreshRateSelector::DisplayManagerPolicy policy;
policy.defaultMode = selector.getCurrentPolicy().defaultMode;
@@ -1673,11 +1839,11 @@
layer.name = "60Hz ExplicitDefault";
layer.focused = true;
- EXPECT_EQ(kModeId60, selector.getBestRefreshRate(layers)->getId());
+ EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
}
-TEST_F(RefreshRateSelectorTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed) {
- TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
RefreshRateSelector::DisplayManagerPolicy policy;
policy.defaultMode = selector.getCurrentPolicy().defaultMode;
@@ -1702,11 +1868,11 @@
layers[1].name = "90Hz ExplicitDefault";
layers[1].focused = false;
- EXPECT_EQ(kModeId90, selector.getBestRefreshRate(layers)->getId());
+ EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
}
-TEST_F(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeamed) {
- TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeamed) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
RefreshRateSelector::DisplayManagerPolicy policy;
policy.defaultMode = selector.getCurrentPolicy().defaultMode;
@@ -1735,11 +1901,11 @@
layers[1].vote = LayerVoteType::ExplicitDefault;
layers[1].name = "90Hz ExplicitDefault";
- EXPECT_EQ(kModeId90, selector.getBestRefreshRate(layers)->getId());
+ EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
}
-TEST_F(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultNotFocusedAndSeamed) {
- TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultNotFocusedAndSeamed) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
RefreshRateSelector::DisplayManagerPolicy policy;
policy.defaultMode = selector.getCurrentPolicy().defaultMode;
@@ -1765,11 +1931,11 @@
layers[1].vote = LayerVoteType::ExplicitDefault;
layers[1].name = "90Hz ExplicitDefault";
- EXPECT_EQ(kModeId60, selector.getBestRefreshRate(layers)->getId());
+ EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
}
-TEST_F(RefreshRateSelectorTest, nonSeamlessVotePrefersSeamlessSwitches) {
- TestableRefreshRateSelector selector(kModes_30_60, kModeId60);
+TEST_P(RefreshRateSelectorTest, nonSeamlessVotePrefersSeamlessSwitches) {
+ auto selector = createSelector(kModes_30_60, kModeId60);
// Allow group switching.
RefreshRateSelector::DisplayManagerPolicy policy;
@@ -1785,14 +1951,14 @@
layer.name = "60Hz ExplicitExactOrMultiple";
layer.focused = true;
- EXPECT_EQ(kModeId60, selector.getBestRefreshRate(layers)->getId());
+ EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
selector.setActiveModeId(kModeId120);
- EXPECT_EQ(kModeId120, selector.getBestRefreshRate(layers)->getId());
+ EXPECT_EQ(kModeId120, selector.getBestFrameRateMode(layers)->getId());
}
-TEST_F(RefreshRateSelectorTest, nonSeamlessExactAndSeamlessMultipleLayers) {
- TestableRefreshRateSelector selector(kModes_25_30_50_60, kModeId60);
+TEST_P(RefreshRateSelectorTest, nonSeamlessExactAndSeamlessMultipleLayers) {
+ auto selector = createSelector(kModes_25_30_50_60, kModeId60);
// Allow group switching.
RefreshRateSelector::DisplayManagerPolicy policy;
@@ -1813,18 +1979,18 @@
.weight = 1.f,
.focused = true}};
- EXPECT_EQ(kModeId50, selector.getBestRefreshRate(layers)->getId());
+ EXPECT_EQ(kModeId50, selector.getBestFrameRateMode(layers)->getId());
auto& seamedLayer = layers[0];
seamedLayer.desiredRefreshRate = 30_Hz;
seamedLayer.name = "30Hz ExplicitDefault";
selector.setActiveModeId(kModeId30);
- EXPECT_EQ(kModeId25, selector.getBestRefreshRate(layers)->getId());
+ EXPECT_EQ(kModeId25, selector.getBestFrameRateMode(layers)->getId());
}
-TEST_F(RefreshRateSelectorTest, minLayersDontTrigerSeamedSwitch) {
- TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId90);
+TEST_P(RefreshRateSelectorTest, minLayersDontTrigerSeamedSwitch) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId90);
// Allow group switching.
RefreshRateSelector::DisplayManagerPolicy policy;
@@ -1835,11 +2001,11 @@
std::vector<LayerRequirement> layers = {
{.name = "Min", .vote = LayerVoteType::Min, .weight = 1.f, .focused = true}};
- EXPECT_EQ(kModeId90, selector.getBestRefreshRate(layers)->getId());
+ EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
}
-TEST_F(RefreshRateSelectorTest, primaryVsAppRequestPolicy) {
- TestableRefreshRateSelector selector(kModes_30_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, primaryVsAppRequestPolicy) {
+ auto selector = createSelector(kModes_30_60_90, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
layers[0].name = "Test layer";
@@ -1849,13 +2015,14 @@
bool focused = true;
};
- // Returns the mode selected by getBestRefreshRate for a single layer with the given arguments.
+ // Returns the mode selected by getBestFrameRateMode for a single layer with the given
+ // arguments.
const auto getFrameRate = [&](LayerVoteType voteType, Fps fps,
Args args = {}) -> DisplayModeId {
layers[0].vote = voteType;
layers[0].desiredRefreshRate = fps;
layers[0].focused = args.focused;
- return selector.getBestRefreshRate(layers, {.touch = args.touch})->getId();
+ return selector.getBestFrameRateMode(layers, {.touch = args.touch})->getId();
};
constexpr FpsRange k30_60 = {30_Hz, 60_Hz};
@@ -1864,7 +2031,7 @@
EXPECT_EQ(SetPolicyResult::Changed,
selector.setDisplayManagerPolicy({kModeId60, {k30_60, k30_60}, {k30_90, k30_90}}));
- EXPECT_EQ(kModeId60, selector.getBestRefreshRate()->getId());
+ EXPECT_EQ(kModeId60, selector.getBestFrameRateMode()->getId());
EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz));
EXPECT_EQ(kModeId30, getFrameRate(LayerVoteType::Min, 90_Hz));
EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz));
@@ -1897,22 +2064,23 @@
EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz));
}
-TEST_F(RefreshRateSelectorTest, idle) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, idle) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
layers[0].name = "Test layer";
- const auto getIdleFrameRate = [&](LayerVoteType voteType, bool touchActive) -> DisplayModeId {
+ const auto getIdleDisplayModeId = [&](LayerVoteType voteType,
+ bool touchActive) -> DisplayModeId {
layers[0].vote = voteType;
layers[0].desiredRefreshRate = 90_Hz;
const auto [ranking, signals] =
- selector.getRankedRefreshRates(layers, {.touch = touchActive, .idle = true});
+ selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true});
// Refresh rate will be chosen by either touch state or idle state.
EXPECT_EQ(!touchActive, signals.idle);
- return ranking.front().modePtr->getId();
+ return ranking.front().frameRateMode.modePtr->getId();
};
EXPECT_EQ(SetPolicyResult::Changed,
@@ -1921,38 +2089,38 @@
// Idle should be lower priority than touch boost.
{
constexpr bool kTouchActive = true;
- EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::NoVote, kTouchActive));
- EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::Min, kTouchActive));
- EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::Max, kTouchActive));
- EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::Heuristic, kTouchActive));
- EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::ExplicitDefault, kTouchActive));
+ EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::NoVote, kTouchActive));
+ EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::Min, kTouchActive));
+ EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::Max, kTouchActive));
+ EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::Heuristic, kTouchActive));
+ EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::ExplicitDefault, kTouchActive));
EXPECT_EQ(kModeId90,
- getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
+ getIdleDisplayModeId(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
}
// With no layers, idle should still be lower priority than touch boost.
- EXPECT_EQ(kModeId90, selector.getBestRefreshRate({}, {.touch = true, .idle = true})->getId());
+ EXPECT_EQ(kModeId90, selector.getBestFrameRateMode({}, {.touch = true, .idle = true})->getId());
// Idle should be higher precedence than other layer frame rate considerations.
selector.setActiveModeId(kModeId90);
{
constexpr bool kTouchActive = false;
- EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::NoVote, kTouchActive));
- EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::Min, kTouchActive));
- EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::Max, kTouchActive));
- EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::Heuristic, kTouchActive));
- EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::ExplicitDefault, kTouchActive));
+ EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::NoVote, kTouchActive));
+ EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::Min, kTouchActive));
+ EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::Max, kTouchActive));
+ EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::Heuristic, kTouchActive));
+ EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::ExplicitDefault, kTouchActive));
EXPECT_EQ(kModeId60,
- getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
+ getIdleDisplayModeId(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
}
// Idle should be applied rather than the active mode when there are no layers.
- EXPECT_EQ(kModeId60, selector.getBestRefreshRate({}, {.idle = true})->getId());
+ EXPECT_EQ(kModeId60, selector.getBestFrameRateMode({}, {.idle = true})->getId());
}
-TEST_F(RefreshRateSelectorTest, findClosestKnownFrameRate) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, findClosestKnownFrameRate) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
for (float fps = 1.0f; fps <= 120.0f; fps += 0.1f) {
const auto knownFrameRate = selector.findClosestKnownFrameRate(Fps::fromValue(fps));
@@ -1969,8 +2137,8 @@
}
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_KnownFrameRate) {
- TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_KnownFrameRate) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
struct Expectation {
Fps fps;
@@ -1997,101 +2165,80 @@
for (const auto& [fps, mode] : knownFrameRatesExpectations) {
layer.desiredRefreshRate = fps;
- EXPECT_EQ(mode, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(mode, selector.getBestFrameRateMode(layers));
}
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExact) {
- TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitExact) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
auto& explicitExactLayer = layers[0];
auto& explicitExactOrMultipleLayer = layers[1];
- explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
- explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
- explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
-
explicitExactLayer.vote = LayerVoteType::ExplicitExact;
explicitExactLayer.name = "ExplicitExact";
explicitExactLayer.desiredRefreshRate = 30_Hz;
- EXPECT_EQ(kMode30, selector.getBestRefreshRate(layers));
- EXPECT_EQ(kMode30, selector.getBestRefreshRate(layers, {.touch = true}));
-
- explicitExactOrMultipleLayer.desiredRefreshRate = 120_Hz;
- explicitExactLayer.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
-
- explicitExactLayer.desiredRefreshRate = 72_Hz;
- EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
-
- explicitExactLayer.desiredRefreshRate = 90_Hz;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
-
- explicitExactLayer.desiredRefreshRate = 120_Hz;
- EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExactEnableFrameRateOverride) {
- TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60,
- {.enableFrameRateOverride =
- Config::FrameRateOverride::Enabled});
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
- auto& explicitExactLayer = layers[0];
- auto& explicitExactOrMultipleLayer = layers[1];
-
explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
- explicitExactLayer.vote = LayerVoteType::ExplicitExact;
- explicitExactLayer.name = "ExplicitExact";
- explicitExactLayer.desiredRefreshRate = 30_Hz;
+ if (GetParam() == Config::FrameRateOverride::Disabled) {
+ EXPECT_SCORED_FRAME_RATE(kMode30, 30_Hz, selector.getBestScoredFrameRate(layers));
+ EXPECT_SCORED_FRAME_RATE(kMode30, 30_Hz,
+ selector.getBestScoredFrameRate(layers, {.touch = true}));
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
- EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers, {.touch = true}));
+ } else {
+ EXPECT_SCORED_FRAME_RATE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers));
+ EXPECT_SCORED_FRAME_RATE(kMode120, 120_Hz,
+ selector.getBestScoredFrameRate(layers, {.touch = true}));
+ }
explicitExactOrMultipleLayer.desiredRefreshRate = 120_Hz;
explicitExactLayer.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+
+ if (GetParam() == Config::FrameRateOverride::Disabled) {
+ EXPECT_SCORED_FRAME_RATE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers));
+ } else {
+ EXPECT_SCORED_FRAME_RATE(kMode120, 120_Hz, selector.getBestScoredFrameRate(layers));
+ }
explicitExactLayer.desiredRefreshRate = 72_Hz;
- EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+ EXPECT_SCORED_FRAME_RATE(kMode72, 72_Hz, selector.getBestScoredFrameRate(layers));
explicitExactLayer.desiredRefreshRate = 90_Hz;
- EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+ EXPECT_SCORED_FRAME_RATE(kMode90, 90_Hz, selector.getBestScoredFrameRate(layers));
explicitExactLayer.desiredRefreshRate = 120_Hz;
- EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+ EXPECT_SCORED_FRAME_RATE(kMode120, 120_Hz, selector.getBestScoredFrameRate(layers));
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ReadsCache) {
- TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ReadsCache) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
using GlobalSignals = RefreshRateSelector::GlobalSignals;
const auto args = std::make_pair(std::vector<LayerRequirement>{},
GlobalSignals{.touch = true, .idle = true});
- const RefreshRateSelector::RankedRefreshRates result = {{RefreshRateSelector::ScoredRefreshRate{
- kMode90}},
- {.touch = true}};
+ const RefreshRateSelector::RankedFrameRates result = {{RefreshRateSelector::ScoredFrameRate{
+ {90_Hz, kMode90}}},
+ GlobalSignals{.touch = true}};
selector.mutableGetRankedRefreshRatesCache() = {args, result};
- EXPECT_EQ(result, selector.getRankedRefreshRates(args.first, args.second));
+ EXPECT_EQ(result, selector.getRankedFrameRates(args.first, args.second));
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_WritesCache) {
- TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_WritesCache) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
EXPECT_FALSE(selector.mutableGetRankedRefreshRatesCache());
std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true};
- const auto result = selector.getRankedRefreshRates(layers, globalSignals);
+ const auto result = selector.getRankedFrameRates(layers, globalSignals);
const auto& cache = selector.mutableGetRankedRefreshRatesCache();
ASSERT_TRUE(cache);
@@ -2100,10 +2247,8 @@
EXPECT_EQ(cache->result, result);
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExactTouchBoost) {
- TestableRefreshRateSelector selector(kModes_60_120, kModeId60,
- {.enableFrameRateOverride =
- Config::FrameRateOverride::Enabled});
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitExactTouchBoost) {
+ auto selector = createSelector(kModes_60_120, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
auto& explicitExactLayer = layers[0];
@@ -2117,19 +2262,21 @@
explicitExactLayer.name = "ExplicitExact";
explicitExactLayer.desiredRefreshRate = 30_Hz;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
- EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers, {.touch = true}));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+ if (GetParam() == Config::FrameRateOverride::Disabled) {
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers, {.touch = true}));
+ } else {
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers, {.touch = true}));
+ }
explicitExactOrMultipleLayer.vote = LayerVoteType::NoVote;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers, {.touch = true}));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers, {.touch = true}));
}
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_FractionalRefreshRates_ExactAndDefault) {
- TestableRefreshRateSelector selector(kModes_24_25_30_50_60_Frac, kModeId60,
- {.enableFrameRateOverride =
- Config::FrameRateOverride::Enabled});
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_FractionalRefreshRates_ExactAndDefault) {
+ auto selector = createSelector(kModes_24_25_30_50_60_Frac, kModeId60);
std::vector<LayerRequirement> layers = {{.weight = 0.5f}, {.weight = 0.5f}};
auto& explicitDefaultLayer = layers[0];
@@ -2143,12 +2290,12 @@
explicitDefaultLayer.name = "ExplicitDefault";
explicitDefaultLayer.desiredRefreshRate = 59.94_Hz;
- EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
}
// b/190578904
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_withCloseRefreshRates) {
- constexpr int kMinRefreshRate = 10;
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withCloseRefreshRates) {
+ const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue();
constexpr int kMaxRefreshRate = 240;
DisplayModes displayModes;
@@ -2159,14 +2306,13 @@
Fps::fromValue(static_cast<float>(fps))));
}
- const TestableRefreshRateSelector selector(std::move(displayModes),
- DisplayModeId(kMinRefreshRate));
+ const auto selector = createSelector(std::move(displayModes), DisplayModeId(kMinRefreshRate));
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) {
layers[0].desiredRefreshRate = fps;
layers[0].vote = vote;
- EXPECT_EQ(fps.getIntValue(), selector.getBestRefreshRate(layers)->getFps().getIntValue())
+ EXPECT_EQ(fps.getIntValue(), selector.getBestFrameRateMode(layers)->getFps().getIntValue())
<< "Failed for " << ftl::enum_string(vote);
};
@@ -2180,7 +2326,7 @@
}
// b/190578904
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_conflictingVotes) {
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_conflictingVotes) {
constexpr DisplayModeId kActiveModeId{0};
DisplayModes displayModes = makeModes(createDisplayMode(kActiveModeId, 43_Hz),
createDisplayMode(DisplayModeId(1), 53_Hz),
@@ -2188,7 +2334,7 @@
createDisplayMode(DisplayModeId(3), 60_Hz));
const RefreshRateSelector::GlobalSignals globalSignals = {.touch = false, .idle = false};
- const TestableRefreshRateSelector selector(std::move(displayModes), kActiveModeId);
+ const auto selector = createSelector(std::move(displayModes), kActiveModeId);
const std::vector<LayerRequirement> layers = {
{
@@ -2205,19 +2351,19 @@
},
};
- EXPECT_EQ(53_Hz, selector.getBestRefreshRate(layers, globalSignals)->getFps());
+ EXPECT_EQ(53_Hz, selector.getBestFrameRateMode(layers, globalSignals)->getFps());
}
-TEST_F(RefreshRateSelectorTest, modeComparison) {
+TEST_P(RefreshRateSelectorTest, modeComparison) {
EXPECT_LT(kMode60->getFps(), kMode90->getFps());
EXPECT_GE(kMode60->getFps(), kMode60->getFps());
EXPECT_GE(kMode90->getFps(), kMode90->getFps());
}
-TEST_F(RefreshRateSelectorTest, testKernelIdleTimerAction) {
+TEST_P(RefreshRateSelectorTest, testKernelIdleTimerAction) {
using KernelIdleTimerAction = RefreshRateSelector::KernelIdleTimerAction;
- TestableRefreshRateSelector selector(kModes_60_90, kModeId90);
+ auto selector = createSelector(kModes_60_90, kModeId90);
EXPECT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
@@ -2234,10 +2380,10 @@
EXPECT_EQ(KernelIdleTimerAction::TurnOff, selector.getIdleTimerAction());
}
-TEST_F(RefreshRateSelectorTest, testKernelIdleTimerActionFor120Hz) {
+TEST_P(RefreshRateSelectorTest, testKernelIdleTimerActionFor120Hz) {
using KernelIdleTimerAction = RefreshRateSelector::KernelIdleTimerAction;
- TestableRefreshRateSelector selector(kModes_60_120, kModeId120);
+ auto selector = createSelector(kModes_60_120, kModeId120);
EXPECT_EQ(SetPolicyResult::Changed,
selector.setDisplayManagerPolicy({kModeId60, {0_Hz, 60_Hz}}));
@@ -2256,8 +2402,8 @@
EXPECT_EQ(KernelIdleTimerAction::TurnOff, selector.getIdleTimerAction());
}
-TEST_F(RefreshRateSelectorTest, getFrameRateDivisor) {
- TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId30);
+TEST_P(RefreshRateSelectorTest, getFrameRateDivisor) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId30);
const auto frameRate = 30_Hz;
Fps displayRefreshRate = selector.getActiveMode().getFps();
@@ -2289,7 +2435,7 @@
EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(60_Hz, 59.94_Hz));
}
-TEST_F(RefreshRateSelectorTest, isFractionalPairOrMultiple) {
+TEST_P(RefreshRateSelectorTest, isFractionalPairOrMultiple) {
EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(23.976_Hz, 24_Hz));
EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(24_Hz, 23.976_Hz));
@@ -2315,22 +2461,72 @@
EXPECT_FALSE(RefreshRateSelector::isFractionalPairOrMultiple(29.97_Hz, 59.94_Hz));
}
-TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_noLayers) {
- RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120);
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_noLayers) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
EXPECT_TRUE(selector.getFrameRateOverrides({}, 120_Hz, {}).empty());
}
-TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_60on120) {
- RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120,
- {.enableFrameRateOverride = Config::FrameRateOverride::Enabled});
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_NonExplicit) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
layers[0].name = "Test layer";
layers[0].ownerUid = 1234;
layers[0].desiredRefreshRate = 60_Hz;
- layers[0].vote = LayerVoteType::ExplicitDefault;
+ layers[0].vote = LayerVoteType::NoVote;
+ EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+ layers[0].vote = LayerVoteType::Min;
+ EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+ layers[0].vote = LayerVoteType::Max;
+ EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+ layers[0].vote = LayerVoteType::Heuristic;
+ EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_Disabled) {
+ if (GetParam() != Config::FrameRateOverride::Disabled) {
+ return;
+ }
+
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "Test layer";
+ layers[0].ownerUid = 1234;
+ layers[0].desiredRefreshRate = 60_Hz;
+
+ layers[0].vote = LayerVoteType::ExplicitDefault;
+ EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+ layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+ EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+ layers[0].vote = LayerVoteType::ExplicitExact;
+ EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_60on120) {
+ if (GetParam() == Config::FrameRateOverride::Disabled) {
+ return;
+ }
+
+ ASSERT_TRUE(GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ||
+ GetParam() == Config::FrameRateOverride::AppOverride ||
+ GetParam() == Config::FrameRateOverride::Enabled);
+
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "Test layer";
+ layers[0].ownerUid = 1234;
+ layers[0].desiredRefreshRate = 60_Hz;
+
+ layers[0].vote = LayerVoteType::ExplicitDefault;
auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
EXPECT_EQ(1u, frameRateOverrides.size());
ASSERT_EQ(1u, frameRateOverrides.count(1234));
@@ -2342,26 +2538,23 @@
ASSERT_EQ(1u, frameRateOverrides.count(1234));
EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
- layers[0].vote = LayerVoteType::NoVote;
+ layers[0].vote = LayerVoteType::ExplicitExact;
frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_TRUE(frameRateOverrides.empty());
-
- layers[0].vote = LayerVoteType::Min;
- frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_TRUE(frameRateOverrides.empty());
-
- layers[0].vote = LayerVoteType::Max;
- frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_TRUE(frameRateOverrides.empty());
-
- layers[0].vote = LayerVoteType::Heuristic;
- frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_TRUE(frameRateOverrides.empty());
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
}
-TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_twoUids) {
- RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120,
- {.enableFrameRateOverride = Config::FrameRateOverride::Enabled});
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_twoUids) {
+ if (GetParam() == Config::FrameRateOverride::Disabled) {
+ return;
+ }
+
+ ASSERT_TRUE(GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ||
+ GetParam() == Config::FrameRateOverride::AppOverride ||
+ GetParam() == Config::FrameRateOverride::Enabled);
+
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f},
{.ownerUid = 5678, .weight = 1.f}};
@@ -2392,9 +2585,16 @@
EXPECT_TRUE(frameRateOverrides.empty());
}
-TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_touch) {
- RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120,
- {.enableFrameRateOverride = Config::FrameRateOverride::Enabled});
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_touch) {
+ if (GetParam() == Config::FrameRateOverride::Disabled) {
+ return;
+ }
+
+ ASSERT_TRUE(GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ||
+ GetParam() == Config::FrameRateOverride::AppOverride ||
+ GetParam() == Config::FrameRateOverride::Enabled);
+
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f}};
layers[0].name = "Test layer";
@@ -2432,88 +2632,87 @@
EXPECT_TRUE(frameRateOverrides.empty());
}
-TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_DivisorIsNotDisplayRefreshRate_Enabled) {
- RefreshRateSelector selector(kModes_60_120, kModeId120,
- {.enableFrameRateOverride = Config::FrameRateOverride::Enabled});
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_DivisorIsNotDisplayRefreshRate) {
+ if (GetParam() == Config::FrameRateOverride::Disabled) {
+ return;
+ }
+
+ ASSERT_TRUE(GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ||
+ GetParam() == Config::FrameRateOverride::AppOverride ||
+ GetParam() == Config::FrameRateOverride::Enabled);
+
+ auto selector = createSelector(kModes_60_120, kModeId120);
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
layers[0].name = "Test layer";
layers[0].ownerUid = 1234;
layers[0].desiredRefreshRate = 30_Hz;
- layers[0].vote = LayerVoteType::ExplicitDefault;
+ const auto expetedFps =
+ GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ? 60_Hz : 30_Hz;
+ layers[0].vote = LayerVoteType::ExplicitDefault;
auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
EXPECT_EQ(1u, frameRateOverrides.size());
ASSERT_EQ(1u, frameRateOverrides.count(1234));
- EXPECT_EQ(30_Hz, frameRateOverrides.at(1234));
+ EXPECT_EQ(expetedFps, frameRateOverrides.at(1234));
layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
EXPECT_EQ(1u, frameRateOverrides.size());
ASSERT_EQ(1u, frameRateOverrides.count(1234));
- EXPECT_EQ(30_Hz, frameRateOverrides.at(1234));
+ EXPECT_EQ(expetedFps, frameRateOverrides.at(1234));
- layers[0].vote = LayerVoteType::NoVote;
- frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_TRUE(frameRateOverrides.empty());
-
- layers[0].vote = LayerVoteType::Min;
- frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_TRUE(frameRateOverrides.empty());
-
- layers[0].vote = LayerVoteType::Max;
- frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_TRUE(frameRateOverrides.empty());
-
- layers[0].vote = LayerVoteType::Heuristic;
- frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_TRUE(frameRateOverrides.empty());
-}
-
-TEST_F(RefreshRateSelectorTest,
- getFrameRateOverrides_DivisorIsNotDisplayRefreshRate_EnabledForNativeRefreshRates) {
- RefreshRateSelector selector(kModes_60_120, kModeId120,
- {.enableFrameRateOverride =
- Config::FrameRateOverride::EnabledForNativeRefreshRates});
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- layers[0].name = "Test layer";
- layers[0].ownerUid = 1234;
- layers[0].desiredRefreshRate = 30_Hz;
- layers[0].vote = LayerVoteType::ExplicitDefault;
-
- auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_EQ(1u, frameRateOverrides.size());
- ASSERT_EQ(1u, frameRateOverrides.count(1234));
- EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
- layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+ layers[0].vote = LayerVoteType::ExplicitExact;
frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
EXPECT_EQ(1u, frameRateOverrides.size());
ASSERT_EQ(1u, frameRateOverrides.count(1234));
- EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
- layers[0].vote = LayerVoteType::NoVote;
- frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_TRUE(frameRateOverrides.empty());
-
- layers[0].vote = LayerVoteType::Min;
- frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_TRUE(frameRateOverrides.empty());
-
- layers[0].vote = LayerVoteType::Max;
- frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_TRUE(frameRateOverrides.empty());
-
- layers[0].vote = LayerVoteType::Heuristic;
- frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_TRUE(frameRateOverrides.empty());
+ EXPECT_EQ(expetedFps, frameRateOverrides.at(1234));
}
-TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_InPolicy) {
- TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120,
- {.enableFrameRateOverride =
- Config::FrameRateOverride::Enabled});
+TEST_P(RefreshRateSelectorTest, renderFrameRateInvalidPolicy) {
+ auto selector = createSelector(kModes_60_120, kModeId120);
+
+ // The render frame rate cannot be greater than the physical refresh rate
+ {
+ const FpsRange physical = {60_Hz, 60_Hz};
+ const FpsRange render = {60_Hz, 120_Hz};
+ EXPECT_EQ(SetPolicyResult::Invalid,
+ selector.setDisplayManagerPolicy(
+ {kModeId60, {physical, render}, {physical, render}}));
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, renderFrameRateRestrictsPhysicalRefreshRate) {
+ auto selector = createSelector(kModes_60_120, kModeId120);
+
+ {
+ const FpsRange physical = {0_Hz, 120_Hz};
+ const FpsRange render = {0_Hz, 60_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId60, {physical, render}, {physical, render}}));
+ const auto expectedMaxMode =
+ GetParam() == Config::FrameRateOverride::Enabled ? kMode120 : kMode60;
+ EXPECT_EQ(expectedMaxMode, selector.getMaxRefreshRateByPolicy());
+ EXPECT_EQ(kMode60, selector.getMinRefreshRateByPolicy());
+ }
+
+ {
+ const FpsRange physical = {0_Hz, 120_Hz};
+ const FpsRange render = {120_Hz, 120_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId60, {physical, render}, {physical, render}}));
+ EXPECT_EQ(kMode120, selector.getMaxRefreshRateByPolicy());
+ EXPECT_EQ(kMode120, selector.getMinRefreshRateByPolicy());
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_InPolicy) {
+ if (GetParam() != Config::FrameRateOverride::Enabled) {
+ return;
+ }
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
{
@@ -2566,5 +2765,153 @@
EXPECT_EQ(30_Hz, frameRateOverrides.at(1234));
}
+TEST_P(RefreshRateSelectorTest, renderFrameRates) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+ // [renderRate, refreshRate]
+ const auto expected = []() -> std::vector<std::pair<Fps, Fps>> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{30_Hz, 30_Hz},
+ {60_Hz, 60_Hz},
+ {72_Hz, 72_Hz},
+ {90_Hz, 90_Hz},
+ {120_Hz, 120_Hz}};
+ case Config::FrameRateOverride::Enabled:
+ return {{30_Hz, 30_Hz}, {36_Hz, 72_Hz}, {40_Hz, 120_Hz}, {45_Hz, 90_Hz},
+ {60_Hz, 60_Hz}, {72_Hz, 72_Hz}, {90_Hz, 90_Hz}, {120_Hz, 120_Hz}};
+ }
+ }();
+
+ const auto& primaryRefreshRates = selector.getPrimaryFrameRates();
+ ASSERT_EQ(expected.size(), primaryRefreshRates.size());
+
+ for (size_t i = 0; i < expected.size(); i++) {
+ const auto [expectedRenderRate, expectedRefreshRate] = expected[i];
+ EXPECT_EQ(expectedRenderRate, primaryRefreshRates[i].fps);
+ EXPECT_EQ(expectedRefreshRate, primaryRefreshRates[i].modePtr->getFps());
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, refreshRateIsCappedWithRenderFrameRate) {
+ if (GetParam() != Config::FrameRateOverride::Enabled) {
+ return;
+ }
+
+ auto selector = createSelector(kModes_60_120, kModeId60);
+
+ constexpr FpsRange k0_120Hz = {0_Hz, 120_Hz};
+ constexpr FpsRange k0_60Hz = {0_Hz, 60_Hz};
+
+ constexpr FpsRanges kAppRequest = {/*physical*/ k0_120Hz,
+ /*render*/ k0_120Hz};
+
+ EXPECT_SCORED_FRAME_RATE(kMode120, 120_Hz, selector.getBestScoredFrameRate());
+ {
+ constexpr FpsRanges kPrimary = {/*physical*/ k0_120Hz,
+ /*render*/ k0_120Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({/*defaultMode*/ kModeId60,
+ /*primaryRanges*/
+ kPrimary,
+ /*appRequestRanges*/
+ kAppRequest}));
+ }
+ EXPECT_SCORED_FRAME_RATE(kMode120, 120_Hz, selector.getBestScoredFrameRate());
+
+ {
+ constexpr FpsRanges kPrimary = {/*physical*/ k0_60Hz,
+ /*render*/ k0_60Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({/*defaultMode*/ kModeId60,
+ /*primaryRanges*/
+ kPrimary,
+ /*appRequestRanges*/
+ kAppRequest}));
+ }
+ EXPECT_SCORED_FRAME_RATE(kMode60, 60_Hz, selector.getBestScoredFrameRate());
+
+ {
+ constexpr FpsRanges kPrimary = {/*physical*/ k0_120Hz,
+ /*render*/ k0_60Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({/*defaultMode*/ kModeId60,
+ /*primaryRanges*/
+ kPrimary,
+ /*appRequestRanges*/
+ kAppRequest}));
+ }
+ EXPECT_SCORED_FRAME_RATE(kMode60, 60_Hz, selector.getBestScoredFrameRate());
+}
+
+TEST_P(RefreshRateSelectorTest, renderFrameRates_60_120) {
+ auto selector = createSelector(kModes_60_120, kModeId120);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& layer = layers[0];
+
+ const auto expectedRenderRate =
+ GetParam() == Config::FrameRateOverride::Enabled ? 30_Hz : 60_Hz;
+
+ layer.name = "30Hz ExplicitDefault";
+ layer.desiredRefreshRate = 30_Hz;
+ layer.vote = LayerVoteType::ExplicitDefault;
+ EXPECT_SCORED_FRAME_RATE(kMode60, expectedRenderRate, selector.getBestScoredFrameRate(layers));
+
+ layer.name = "30Hz Heuristic";
+ layer.desiredRefreshRate = 30_Hz;
+ layer.vote = LayerVoteType::Heuristic;
+ EXPECT_SCORED_FRAME_RATE(kMode60, expectedRenderRate, selector.getBestScoredFrameRate(layers));
+
+ layer.name = "30Hz ExplicitExactOrMultiple";
+ layer.desiredRefreshRate = 30_Hz;
+ layer.vote = LayerVoteType::ExplicitExactOrMultiple;
+ EXPECT_SCORED_FRAME_RATE(kMode60, expectedRenderRate, selector.getBestScoredFrameRate(layers));
+}
+
+TEST_P(RefreshRateSelectorTest, idleWhenLowestRefreshRateIsNotDivisor) {
+ auto selector = createSelector(kModes_35_60_90, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "Test layer";
+
+ const auto getIdleDisplayModeId = [&](LayerVoteType voteType,
+ bool touchActive) -> DisplayModeId {
+ layers[0].vote = voteType;
+ layers[0].desiredRefreshRate = 90_Hz;
+
+ const auto [ranking, signals] =
+ selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true});
+
+ // Refresh rate will be chosen by either touch state or idle state.
+ EXPECT_EQ(!touchActive, signals.idle);
+ return ranking.front().frameRateMode.modePtr->getId();
+ };
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId60, {0_Hz, 90_Hz}}));
+
+ // With no layers, idle should still be lower priority than touch boost.
+ EXPECT_EQ(kModeId90, selector.getBestFrameRateMode({}, {.touch = true, .idle = true})->getId());
+
+ // Idle should be higher precedence than other layer frame rate considerations.
+ selector.setActiveModeId(kModeId90);
+ {
+ constexpr bool kTouchActive = false;
+ EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::NoVote, kTouchActive));
+ EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::Min, kTouchActive));
+ EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::Max, kTouchActive));
+ EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::Heuristic, kTouchActive));
+ EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::ExplicitDefault, kTouchActive));
+ EXPECT_EQ(kModeId35,
+ getIdleDisplayModeId(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
+ }
+
+ // Idle should be applied rather than the active mode when there are no layers.
+ EXPECT_EQ(kModeId35, selector.getBestFrameRateMode({}, {.idle = true})->getId());
+}
+
} // namespace
} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
index 6ffc039..1dd4f25 100644
--- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -1320,6 +1320,7 @@
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 3, 3000000, {}, GameMode::Performance);
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 4, 4000000, {}, GameMode::Battery);
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 5, 4000000, {}, GameMode::Battery);
+ insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 6, 5000000, {}, GameMode::Custom);
std::string pulledData;
EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData));
@@ -1327,9 +1328,9 @@
SurfaceflingerStatsLayerInfoWrapper atomList;
ASSERT_TRUE(atomList.ParseFromString(pulledData));
// The first time record is never uploaded to stats.
- ASSERT_EQ(atomList.atom_size(), 3);
+ ASSERT_EQ(atomList.atom_size(), 4);
// Layers are ordered based on the hash in LayerStatsKey. For this test, the order happens to
- // be: 0 - Battery 1 - Performance 2 - Standard
+ // be: 0 - Battery 1 - Custom 2 - Performance 3 - Standard
const SurfaceflingerStatsLayerInfo& atom0 = atomList.atom(0);
EXPECT_EQ(atom0.layer_name(), genLayerName(LAYER_ID_0));
@@ -1364,7 +1365,7 @@
EXPECT_EQ(atom1.uid(), UID_0);
EXPECT_EQ(atom1.display_refresh_rate_bucket(), REFRESH_RATE_BUCKET_0);
EXPECT_EQ(atom1.render_rate_bucket(), RENDER_RATE_BUCKET_0);
- EXPECT_EQ(atom1.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_PERFORMANCE);
+ EXPECT_EQ(atom1.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_CUSTOM);
const SurfaceflingerStatsLayerInfo& atom2 = atomList.atom(2);
@@ -1377,12 +1378,30 @@
EXPECT_THAT(atom2.latch_to_present(), HistogramEq(buildExpectedHistogram({2}, {1})));
EXPECT_THAT(atom2.desired_to_present(), HistogramEq(buildExpectedHistogram({1}, {1})));
EXPECT_THAT(atom2.post_to_acquire(), HistogramEq(buildExpectedHistogram({1}, {1})));
- EXPECT_EQ(atom2.late_acquire_frames(), LATE_ACQUIRE_FRAMES);
- EXPECT_EQ(atom2.bad_desired_present_frames(), BAD_DESIRED_PRESENT_FRAMES);
+ EXPECT_EQ(atom2.late_acquire_frames(), 0);
+ EXPECT_EQ(atom2.bad_desired_present_frames(), 0);
EXPECT_EQ(atom2.uid(), UID_0);
EXPECT_EQ(atom2.display_refresh_rate_bucket(), REFRESH_RATE_BUCKET_0);
EXPECT_EQ(atom2.render_rate_bucket(), RENDER_RATE_BUCKET_0);
- EXPECT_EQ(atom2.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_STANDARD);
+ EXPECT_EQ(atom2.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_PERFORMANCE);
+
+ const SurfaceflingerStatsLayerInfo& atom3 = atomList.atom(3);
+
+ EXPECT_EQ(atom3.layer_name(), genLayerName(LAYER_ID_0));
+ EXPECT_EQ(atom3.total_frames(), 1);
+ EXPECT_EQ(atom3.dropped_frames(), 0);
+ EXPECT_THAT(atom3.present_to_present(), HistogramEq(buildExpectedHistogram({1}, {1})));
+ EXPECT_THAT(atom3.post_to_present(), HistogramEq(buildExpectedHistogram({4}, {1})));
+ EXPECT_THAT(atom3.acquire_to_present(), HistogramEq(buildExpectedHistogram({3}, {1})));
+ EXPECT_THAT(atom3.latch_to_present(), HistogramEq(buildExpectedHistogram({2}, {1})));
+ EXPECT_THAT(atom3.desired_to_present(), HistogramEq(buildExpectedHistogram({1}, {1})));
+ EXPECT_THAT(atom3.post_to_acquire(), HistogramEq(buildExpectedHistogram({1}, {1})));
+ EXPECT_EQ(atom3.late_acquire_frames(), LATE_ACQUIRE_FRAMES);
+ EXPECT_EQ(atom3.bad_desired_present_frames(), BAD_DESIRED_PRESENT_FRAMES);
+ EXPECT_EQ(atom3.uid(), UID_0);
+ EXPECT_EQ(atom3.display_refresh_rate_bucket(), REFRESH_RATE_BUCKET_0);
+ EXPECT_EQ(atom3.render_rate_bucket(), RENDER_RATE_BUCKET_0);
+ EXPECT_EQ(atom3.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_STANDARD);
}
TEST_F(TimeStatsTest, layerStatsCallback_pullsMultipleLayers) {
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index 488d4a9..d84698f 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -42,6 +42,8 @@
using testing::Return;
using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+using frontend::TransactionHandler;
+
constexpr nsecs_t TRANSACTION_TIMEOUT = s2ns(5);
class TransactionApplicationTest : public testing::Test {
public: