Merge "Add getHdrOutputConversionSupport to SurfaceComposerClient."
diff --git a/TEST_MAPPING b/TEST_MAPPING
index f54f132..cd8f3cd 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -4,58 +4,7 @@
"name": "SurfaceFlinger_test",
"options": [
{
- "include-filter": "*CredentialsTest.*"
- },
- {
- "include-filter": "*SurfaceFlingerStress.*"
- },
- {
- "include-filter": "*SurfaceInterceptorTest.*"
- },
- {
- "include-filter": "*LayerTransactionTest.*"
- },
- {
- "include-filter": "*LayerTypeTransactionTest.*"
- },
- {
- "include-filter": "*LayerUpdateTest.*"
- },
- {
- "include-filter": "*GeometryLatchingTest.*"
- },
- {
- "include-filter": "*CropLatchingTest.*"
- },
- {
- "include-filter": "*ChildLayerTest.*"
- },
- {
- "include-filter": "*ScreenCaptureTest.*"
- },
- {
- "include-filter": "*ScreenCaptureChildOnlyTest.*"
- },
- {
- "include-filter": "*DereferenceSurfaceControlTest.*"
- },
- {
- "include-filter": "*BoundlessLayerTest.*"
- },
- {
- "include-filter": "*MultiDisplayLayerBoundsTest.*"
- },
- {
- "include-filter": "*InvalidHandleTest.*"
- },
- {
- "include-filter": "*VirtualDisplayTest.*"
- },
- {
- "include-filter": "*RelativeZTest.*"
- },
- {
- "include-filter": "*RefreshRateOverlayTest.*"
+ "include-filter": "*"
},
{
"exclude-filter": "*ChildLayerTest#ChildrenSurviveParentDestruction"
@@ -76,58 +25,7 @@
"name": "SurfaceFlinger_test",
"options": [
{
- "include-filter": "*CredentialsTest.*"
- },
- {
- "include-filter": "*SurfaceFlingerStress.*"
- },
- {
- "include-filter": "*SurfaceInterceptorTest.*"
- },
- {
- "include-filter": "*LayerTransactionTest.*"
- },
- {
- "include-filter": "*LayerTypeTransactionTest.*"
- },
- {
- "include-filter": "*LayerUpdateTest.*"
- },
- {
- "include-filter": "*GeometryLatchingTest.*"
- },
- {
- "include-filter": "*CropLatchingTest.*"
- },
- {
- "include-filter": "*ChildLayerTest.*"
- },
- {
- "include-filter": "*ScreenCaptureTest.*"
- },
- {
- "include-filter": "*ScreenCaptureChildOnlyTest.*"
- },
- {
- "include-filter": "*DereferenceSurfaceControlTest.*"
- },
- {
- "include-filter": "*BoundlessLayerTest.*"
- },
- {
- "include-filter": "*MultiDisplayLayerBoundsTest.*"
- },
- {
- "include-filter": "*InvalidHandleTest.*"
- },
- {
- "include-filter": "*VirtualDisplayTest.*"
- },
- {
- "include-filter": "*RelativeZTest.*"
- },
- {
- "include-filter": "*RefreshRateOverlayTest.*"
+ "include-filter": "*"
}
]
}
diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp
index 42e9e0f..a7bc018 100644
--- a/cmds/dumpstate/DumpstateService.cpp
+++ b/cmds/dumpstate/DumpstateService.cpp
@@ -58,6 +58,13 @@
exit(0);
}
+[[noreturn]] static void* dumpstate_thread_retrieve(void* data) {
+ std::unique_ptr<DumpstateInfo> ds_info(static_cast<DumpstateInfo*>(data));
+ ds_info->ds->Retrieve(ds_info->calling_uid, ds_info->calling_package);
+ MYLOGD("Finished retrieving a bugreport. Exiting.\n");
+ exit(0);
+}
+
[[noreturn]] static void signalErrorAndExit(sp<IDumpstateListener> listener, int error_code) {
listener->onError(error_code);
exit(0);
@@ -192,6 +199,41 @@
return binder::Status::ok();
}
+binder::Status DumpstateService::retrieveBugreport(
+ int32_t calling_uid, const std::string& calling_package,
+ android::base::unique_fd bugreport_fd,
+ const std::string& bugreport_file,
+ const sp<IDumpstateListener>& listener) {
+
+ ds_ = &(Dumpstate::GetInstance());
+ DumpstateInfo* ds_info = new DumpstateInfo();
+ ds_info->ds = ds_;
+ ds_info->calling_uid = calling_uid;
+ ds_info->calling_package = calling_package;
+ ds_->listener_ = listener;
+ std::unique_ptr<Dumpstate::DumpOptions> options = std::make_unique<Dumpstate::DumpOptions>();
+ // Use a /dev/null FD when initializing options since none is provided.
+ android::base::unique_fd devnull_fd(
+ TEMP_FAILURE_RETRY(open("/dev/null", O_WRONLY | O_CLOEXEC)));
+
+ options->Initialize(Dumpstate::BugreportMode::BUGREPORT_DEFAULT,
+ 0, bugreport_fd, devnull_fd, false);
+
+ if (bugreport_fd.get() == -1) {
+ MYLOGE("Invalid filedescriptor");
+ signalErrorAndExit(listener, IDumpstateListener::BUGREPORT_ERROR_INVALID_INPUT);
+ }
+ ds_->SetOptions(std::move(options));
+ ds_->path_ = bugreport_file;
+ pthread_t thread;
+ status_t err = pthread_create(&thread, nullptr, dumpstate_thread_retrieve, ds_info);
+ if (err != 0) {
+ MYLOGE("Could not create a thread");
+ signalErrorAndExit(listener, IDumpstateListener::BUGREPORT_ERROR_RUNTIME_ERROR);
+ }
+ return binder::Status::ok();
+}
+
status_t DumpstateService::dump(int fd, const Vector<String16>&) {
std::lock_guard<std::mutex> lock(lock_);
if (ds_ == nullptr) {
diff --git a/cmds/dumpstate/DumpstateService.h b/cmds/dumpstate/DumpstateService.h
index 997999c..dd73319 100644
--- a/cmds/dumpstate/DumpstateService.h
+++ b/cmds/dumpstate/DumpstateService.h
@@ -46,6 +46,13 @@
int bugreport_flags, const sp<IDumpstateListener>& listener,
bool is_screenshot_requested) override;
+ binder::Status retrieveBugreport(int32_t calling_uid,
+ const std::string& calling_package,
+ android::base::unique_fd bugreport_fd,
+ const std::string& bugreport_file,
+ const sp<IDumpstateListener>& listener)
+ override;
+
binder::Status cancelBugreport(int32_t calling_uid,
const std::string& calling_package) override;
diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
index d4323af..0dc8f5a 100644
--- a/cmds/dumpstate/binder/android/os/IDumpstate.aidl
+++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
@@ -50,7 +50,10 @@
const int BUGREPORT_MODE_DEFAULT = 6;
// Use pre-dumped data.
- const int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 1;
+ const int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 0x1;
+
+ // Defer user consent.
+ const int BUGREPORT_FLAG_DEFER_CONSENT = 0x2;
/**
* Speculatively pre-dumps UI data for a bugreport request that might come later.
@@ -100,4 +103,22 @@
* @param callingPackage package of the original application that requested the cancellation.
*/
void cancelBugreport(int callingUid, @utf8InCpp String callingPackage);
+
+ /**
+ * Retrieves a previously generated bugreport.
+ *
+ * <p>The caller must have previously generated a bugreport using
+ * {@link #startBugreport} with the {@link BUGREPORT_FLAG_DEFER_CONSENT}
+ * flag set.
+ *
+ * @param callingUid UID of the original application that requested the report.
+ * @param callingPackage package of the original application that requested the report.
+ * @param bugreportFd the file to which the zipped bugreport should be written
+ * @param bugreportFile the path of the bugreport file
+ * @param listener callback for updates; optional
+ */
+ void retrieveBugreport(int callingUid, @utf8InCpp String callingPackage,
+ FileDescriptor bugreportFd,
+ @utf8InCpp String bugreportFile,
+ IDumpstateListener listener);
}
diff --git a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl
index 50c1624..e8891d3 100644
--- a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl
+++ b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl
@@ -50,6 +50,9 @@
/* There is currently a bugreport running. The caller should try again later. */
const int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS = 5;
+ /* There is no bugreport to retrieve for the given caller. */
+ const int BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE = 6;
+
/**
* Called on an error condition with one of the error codes listed above.
*/
@@ -57,8 +60,10 @@
/**
* Called when taking bugreport finishes successfully.
+ *
+ * @param bugreportFile The location of the bugreport file
*/
- oneway void onFinished();
+ oneway void onFinished(@utf8InCpp String bugreportFile);
/**
* Called when screenshot is taken.
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index d77b458..a48313a 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -186,6 +186,7 @@
#define SYSTEM_TRACE_SNAPSHOT "/data/misc/perfetto-traces/bugreport/systrace.pftrace"
#define CGROUPFS_DIR "/sys/fs/cgroup"
#define SDK_EXT_INFO "/apex/com.android.sdkext/bin/derive_sdk"
+#define DROPBOX_DIR "/data/system/dropbox"
// TODO(narayan): Since this information has to be kept in sync
// with tombstoned, we should just put it in a common header.
@@ -526,6 +527,15 @@
return strcmp(path + len - sizeof(stat) + 1, stat); /* .../stat? */
}
+static bool skip_wtf_strictmode(const char *path) {
+ if (strstr(path, "_wtf")) {
+ return true;
+ } else if (strstr(path, "_strictmode")) {
+ return true;
+ }
+ return false;
+}
+
static bool skip_none(const char* path __attribute__((unused))) {
return false;
}
@@ -1895,6 +1905,11 @@
DumpIpTablesAsRoot();
DumpDynamicPartitionInfo();
ds.AddDir(OTA_METADATA_DIR, true);
+ if (!PropertiesHelper::IsUserBuild()) {
+ // Include dropbox entry files inside ZIP, but exclude
+ // noisy WTF and StrictMode entries
+ dump_files("", DROPBOX_DIR, skip_wtf_strictmode, _add_file_from_fd);
+ }
// Capture any IPSec policies in play. No keys are exposed here.
RunCommand("IP XFRM POLICY", {"ip", "xfrm", "policy"}, CommandOptions::WithTimeout(10).Build());
@@ -2825,6 +2840,7 @@
const android::base::unique_fd& screenshot_fd_in,
bool is_screenshot_requested) {
this->use_predumped_ui_data = bugreport_flags & BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA;
+ this->is_consent_deferred = bugreport_flags & BugreportFlag::BUGREPORT_FLAG_DEFER_CONSENT;
// Duplicate the fds because the passed in fds don't outlive the binder transaction.
bugreport_fd.reset(fcntl(bugreport_fd_in.get(), F_DUPFD_CLOEXEC, 0));
screenshot_fd.reset(fcntl(screenshot_fd_in.get(), F_DUPFD_CLOEXEC, 0));
@@ -2907,10 +2923,64 @@
Dumpstate::RunStatus Dumpstate::Run(int32_t calling_uid, const std::string& calling_package) {
Dumpstate::RunStatus status = RunInternal(calling_uid, calling_package);
- if (listener_ != nullptr) {
+ HandleRunStatus(status);
+ return status;
+}
+
+Dumpstate::RunStatus Dumpstate::Retrieve(int32_t calling_uid, const std::string& calling_package) {
+ Dumpstate::RunStatus status = RetrieveInternal(calling_uid, calling_package);
+ HandleRunStatus(status);
+ return status;
+}
+
+Dumpstate::RunStatus Dumpstate::RetrieveInternal(int32_t calling_uid,
+ const std::string& calling_package) {
+ consent_callback_ = new ConsentCallback();
+ const String16 incidentcompanion("incidentcompanion");
+ sp<android::IBinder> ics(
+ defaultServiceManager()->checkService(incidentcompanion));
+ android::String16 package(calling_package.c_str());
+ if (ics != nullptr) {
+ MYLOGD("Checking user consent via incidentcompanion service\n");
+ android::interface_cast<android::os::IIncidentCompanion>(ics)->authorizeReport(
+ calling_uid, package, String16(), String16(),
+ 0x1 /* FLAG_CONFIRMATION_DIALOG */, consent_callback_.get());
+ } else {
+ MYLOGD(
+ "Unable to check user consent; incidentcompanion service unavailable\n");
+ return RunStatus::USER_CONSENT_TIMED_OUT;
+ }
+ UserConsentResult consent_result = consent_callback_->getResult();
+ int timeout_ms = 30 * 1000;
+ while (consent_result == UserConsentResult::UNAVAILABLE &&
+ consent_callback_->getElapsedTimeMs() < timeout_ms) {
+ sleep(1);
+ consent_result = consent_callback_->getResult();
+ }
+ if (consent_result == UserConsentResult::DENIED) {
+ return RunStatus::USER_CONSENT_DENIED;
+ }
+ if (consent_result == UserConsentResult::UNAVAILABLE) {
+ MYLOGD("Canceling user consent request via incidentcompanion service\n");
+ android::interface_cast<android::os::IIncidentCompanion>(ics)->cancelAuthorization(
+ consent_callback_.get());
+ return RunStatus::USER_CONSENT_TIMED_OUT;
+ }
+
+ bool copy_succeeded =
+ android::os::CopyFileToFd(path_, options_->bugreport_fd.get());
+ if (copy_succeeded) {
+ android::os::UnlinkAndLogOnError(path_);
+ }
+ return copy_succeeded ? Dumpstate::RunStatus::OK
+ : Dumpstate::RunStatus::ERROR;
+}
+
+void Dumpstate::HandleRunStatus(Dumpstate::RunStatus status) {
+ if (listener_ != nullptr) {
switch (status) {
case Dumpstate::RunStatus::OK:
- listener_->onFinished();
+ listener_->onFinished(path_.c_str());
break;
case Dumpstate::RunStatus::HELP:
break;
@@ -2928,9 +2998,7 @@
break;
}
}
- return status;
}
-
void Dumpstate::Cancel() {
CleanupTmpFiles();
android::os::UnlinkAndLogOnError(log_path_);
@@ -3181,7 +3249,7 @@
// Share the final file with the caller if the user has consented or Shell is the caller.
Dumpstate::RunStatus status = Dumpstate::RunStatus::OK;
- if (CalledByApi()) {
+ if (CalledByApi() && !options_->is_consent_deferred) {
status = CopyBugreportIfUserConsented(calling_uid);
if (status != Dumpstate::RunStatus::OK &&
status != Dumpstate::RunStatus::USER_CONSENT_TIMED_OUT) {
@@ -3270,6 +3338,15 @@
return;
}
+ // Include the proto logging from WMShell.
+ RunCommand(
+ // Empty name because it's not intended to be classified as a bugreport section.
+ // Actual logging files can be found as "/data/misc/wmtrace/shell_log.winscope"
+ // in the bugreport.
+ "", {"dumpsys", "activity", "service", "SystemUIService",
+ "WMShell", "protolog", "save-for-bugreport"},
+ CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build());
+
// Currently WindowManagerService and InputMethodManagerSerivice support WinScope protocol.
for (const auto& service : {"input_method", "window"}) {
RunCommand(
@@ -3326,9 +3403,11 @@
}
void Dumpstate::MaybeCheckUserConsent(int32_t calling_uid, const std::string& calling_package) {
- if (multiuser_get_app_id(calling_uid) == AID_SHELL || !CalledByApi()) {
- // No need to get consent for shell triggered dumpstates, or not through
- // bugreporting API (i.e. no fd to copy back).
+ if (multiuser_get_app_id(calling_uid) == AID_SHELL ||
+ !CalledByApi() || options_->is_consent_deferred) {
+ // No need to get consent for shell triggered dumpstates, or not
+ // through bugreporting API (i.e. no fd to copy back), or when consent
+ // is deferred.
return;
}
consent_callback_ = new ConsentCallback();
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 9f894b5..8a31c31 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -207,7 +207,9 @@
// The flags used to customize bugreport requests.
enum BugreportFlag {
BUGREPORT_USE_PREDUMPED_UI_DATA =
- android::os::IDumpstate::BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA
+ android::os::IDumpstate::BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA,
+ BUGREPORT_FLAG_DEFER_CONSENT =
+ android::os::IDumpstate::BUGREPORT_FLAG_DEFER_CONSENT
};
static android::os::dumpstate::CommandOptions DEFAULT_DUMPSYS;
@@ -353,6 +355,15 @@
*/
RunStatus Run(int32_t calling_uid, const std::string& calling_package);
+ /*
+ * Entry point for retrieving a previous-generated bugreport.
+ *
+ * Initialize() dumpstate before calling this method.
+ */
+ RunStatus Retrieve(int32_t calling_uid, const std::string& calling_package);
+
+
+
RunStatus ParseCommandlineAndRun(int argc, char* argv[]);
/* Deletes in-progress files */
@@ -396,6 +407,7 @@
bool progress_updates_to_socket = false;
bool do_screenshot = false;
bool is_screenshot_copied = false;
+ bool is_consent_deferred = false;
bool is_remote_mode = false;
bool show_header_only = false;
bool telephony_only = false;
@@ -548,6 +560,7 @@
private:
RunStatus RunInternal(int32_t calling_uid, const std::string& calling_package);
+ RunStatus RetrieveInternal(int32_t calling_uid, const std::string& calling_package);
RunStatus DumpstateDefaultAfterCritical();
RunStatus dumpstate();
@@ -572,6 +585,8 @@
RunStatus HandleUserConsentDenied();
+ void HandleRunStatus(RunStatus status);
+
// Copies bugreport artifacts over to the caller's directories provided there is user consent or
// called by Shell.
RunStatus CopyBugreportIfUserConsented(int32_t calling_uid);
diff --git a/cmds/dumpstate/dumpstate.rc b/cmds/dumpstate/dumpstate.rc
index 12a7cff..a80da4e 100644
--- a/cmds/dumpstate/dumpstate.rc
+++ b/cmds/dumpstate/dumpstate.rc
@@ -8,7 +8,6 @@
socket dumpstate stream 0660 shell log
disabled
oneshot
- capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER FSETID KILL NET_ADMIN NET_RAW SETGID SETUID SYS_PTRACE SYS_RESOURCE BLOCK_SUSPEND SYSLOG
# dumpstatez generates a zipped bugreport but also uses a socket to print the file location once
# it is finished.
@@ -17,11 +16,9 @@
class main
disabled
oneshot
- capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER FSETID KILL NET_ADMIN NET_RAW SETGID SETUID SYS_PTRACE SYS_RESOURCE BLOCK_SUSPEND SYSLOG
# bugreportd starts dumpstate binder service and makes it wait for a listener to connect.
service bugreportd /system/bin/dumpstate -w
class main
disabled
oneshot
- capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER FSETID KILL NET_ADMIN NET_RAW SETGID SETUID SYS_PTRACE SYS_RESOURCE BLOCK_SUSPEND SYSLOG
diff --git a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
index b091c8e..ccf64fe 100644
--- a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
@@ -160,7 +160,7 @@
return binder::Status::ok();
}
- binder::Status onFinished() override {
+ binder::Status onFinished([[maybe_unused]] const std::string& bugreport_file) override {
std::lock_guard<std::mutex> lock(lock_);
is_finished_ = true;
dprintf(out_fd_, "\rFinished");
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
index 1ffcafa..aa5219b 100644
--- a/cmds/dumpstate/tests/dumpstate_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -71,7 +71,7 @@
public:
MOCK_METHOD1(onProgress, binder::Status(int32_t progress));
MOCK_METHOD1(onError, binder::Status(int32_t error_code));
- MOCK_METHOD0(onFinished, binder::Status());
+ MOCK_METHOD1(onFinished, binder::Status(const std::string& bugreport_file));
MOCK_METHOD1(onScreenshotTaken, binder::Status(bool success));
MOCK_METHOD0(onUiIntensiveBugreportDumpsFinished, binder::Status());
@@ -486,6 +486,20 @@
EXPECT_TRUE(options_.ValidateOptions());
}
+TEST_F(DumpOptionsTest, InitializeBugreportFlags) {
+ int flags = Dumpstate::BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA |
+ Dumpstate::BugreportFlag::BUGREPORT_FLAG_DEFER_CONSENT;
+ options_.Initialize(
+ Dumpstate::BugreportMode::BUGREPORT_FULL, flags, fd, fd, true);
+ EXPECT_TRUE(options_.is_consent_deferred);
+ EXPECT_TRUE(options_.use_predumped_ui_data);
+
+ options_.Initialize(
+ Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true);
+ EXPECT_FALSE(options_.is_consent_deferred);
+ EXPECT_FALSE(options_.use_predumped_ui_data);
+}
+
class DumpstateTest : public DumpstateBaseTest {
public:
void SetUp() {
@@ -1037,7 +1051,8 @@
};
// Generate a quick LimitedOnly report redirected to a file, open it and verify entry exist.
-TEST_F(ZippedBugReportStreamTest, StreamLimitedOnlyReport) {
+// TODO: broken test tracked in b/249983726
+TEST_F(ZippedBugReportStreamTest, DISABLED_StreamLimitedOnlyReport) {
std::string out_path = kTestDataPath + "StreamLimitedOnlyReportOut.zip";
android::base::unique_fd out_fd;
CreateFd(out_path, &out_fd);
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index cc038ae..07809e2 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -39,6 +39,11 @@
namespace android {
+bool is_multiuser_uid_isolated(uid_t uid) {
+ uid_t appid = multiuser_get_app_id(uid);
+ return appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END;
+}
+
#ifndef VENDORSERVICEMANAGER
struct ManifestWithDescription {
@@ -222,6 +227,13 @@
}
#endif // !VENDORSERVICEMANAGER
+ServiceManager::Service::~Service() {
+ if (!hasClients) {
+ // only expected to happen on process death
+ LOG(WARNING) << "a service was removed when there are clients";
+ }
+}
+
ServiceManager::ServiceManager(std::unique_ptr<Access>&& access) : mAccess(std::move(access)) {
// TODO(b/151696835): reenable performance hack when we solve bug, since with
// this hack and other fixes, it is unlikely we will see even an ephemeral
@@ -273,13 +285,8 @@
if (auto it = mNameToService.find(name); it != mNameToService.end()) {
service = &(it->second);
- if (!service->allowIsolated) {
- uid_t appid = multiuser_get_app_id(ctx.uid);
- bool isIsolated = appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END;
-
- if (isIsolated) {
- return nullptr;
- }
+ if (!service->allowIsolated && is_multiuser_uid_isolated(ctx.uid)) {
+ return nullptr;
}
out = service->binder;
}
@@ -293,8 +300,13 @@
}
if (out) {
- // Setting this guarantee each time we hand out a binder ensures that the client-checking
- // loop knows about the event even if the client immediately drops the service
+ // Force onClients to get sent, and then make sure the timerfd won't clear it
+ // by setting guaranteeClient again. This logic could be simplified by using
+ // a time-based guarantee. However, forcing onClients(true) to get sent
+ // right here is always going to be important for processes serving multiple
+ // lazy interfaces.
+ service->guaranteeClient = true;
+ CHECK(handleServiceClientCallback(2 /* sm + transaction */, name, false));
service->guaranteeClient = true;
}
@@ -384,8 +396,13 @@
};
if (auto it = mNameToRegistrationCallback.find(name); it != mNameToRegistrationCallback.end()) {
+ // See also getService - handles case where client never gets the service,
+ // we want the service to quit.
+ mNameToService[name].guaranteeClient = true;
+ CHECK(handleServiceClientCallback(2 /* sm + transaction */, name, false));
+ mNameToService[name].guaranteeClient = true;
+
for (const sp<IServiceCallback>& cb : it->second) {
- mNameToService[name].guaranteeClient = true;
// permission checked in registerForNotifications
cb->onRegistration(name, binder);
}
@@ -425,7 +442,17 @@
auto ctx = mAccess->getCallingContext();
if (!mAccess->canFind(ctx, name)) {
- return Status::fromExceptionCode(Status::EX_SECURITY);
+ return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux");
+ }
+
+ // note - we could allow isolated apps to get notifications if we
+ // keep track of isolated callbacks and non-isolated callbacks, but
+ // this is done since isolated apps shouldn't access lazy services
+ // so we should be able to use different APIs to keep things simple.
+ // Here, we disallow everything, because the service might not be
+ // registered yet.
+ if (is_multiuser_uid_isolated(ctx.uid)) {
+ return Status::fromExceptionCode(Status::EX_SECURITY, "isolated app");
}
if (!isValidServiceName(name)) {
@@ -696,67 +723,74 @@
void ServiceManager::handleClientCallbacks() {
for (const auto& [name, service] : mNameToService) {
- handleServiceClientCallback(name, true);
+ handleServiceClientCallback(1 /* sm has one refcount */, name, true);
}
}
-ssize_t ServiceManager::handleServiceClientCallback(const std::string& serviceName,
- bool isCalledOnInterval) {
+bool ServiceManager::handleServiceClientCallback(size_t knownClients,
+ const std::string& serviceName,
+ bool isCalledOnInterval) {
auto serviceIt = mNameToService.find(serviceName);
if (serviceIt == mNameToService.end() || mNameToClientCallback.count(serviceName) < 1) {
- return -1;
+ return true; // return we do have clients a.k.a. DON'T DO ANYTHING
}
Service& service = serviceIt->second;
ssize_t count = service.getNodeStrongRefCount();
- // binder driver doesn't support this feature
- if (count == -1) return count;
+ // binder driver doesn't support this feature, consider we have clients
+ if (count == -1) return true;
- bool hasClients = count > 1; // this process holds a strong count
+ bool hasKernelReportedClients = static_cast<size_t>(count) > knownClients;
if (service.guaranteeClient) {
- // we have no record of this client
- if (!service.hasClients && !hasClients) {
- sendClientCallbackNotifications(serviceName, true);
+ if (!service.hasClients && !hasKernelReportedClients) {
+ sendClientCallbackNotifications(serviceName, true,
+ "service is guaranteed to be in use");
}
// guarantee is temporary
service.guaranteeClient = false;
}
- // only send notifications if this was called via the interval checking workflow
- if (isCalledOnInterval) {
- if (hasClients && !service.hasClients) {
- // client was retrieved in some other way
- sendClientCallbackNotifications(serviceName, true);
- }
+ // Regardless of this situation, we want to give this notification as soon as possible.
+ // This way, we have a chance of preventing further thrashing.
+ if (hasKernelReportedClients && !service.hasClients) {
+ sendClientCallbackNotifications(serviceName, true, "we now have a record of a client");
+ }
- // there are no more clients, but the callback has not been called yet
- if (!hasClients && service.hasClients) {
- sendClientCallbackNotifications(serviceName, false);
+ // But limit rate of shutting down service.
+ if (isCalledOnInterval) {
+ if (!hasKernelReportedClients && service.hasClients) {
+ sendClientCallbackNotifications(serviceName, false,
+ "we now have no record of a client");
}
}
- return count;
+ // May be different than 'hasKernelReportedClients'. We intentionally delay
+ // information about clients going away to reduce thrashing.
+ return service.hasClients;
}
-void ServiceManager::sendClientCallbackNotifications(const std::string& serviceName, bool hasClients) {
+void ServiceManager::sendClientCallbackNotifications(const std::string& serviceName,
+ bool hasClients, const char* context) {
auto serviceIt = mNameToService.find(serviceName);
if (serviceIt == mNameToService.end()) {
- ALOGW("sendClientCallbackNotifications could not find service %s", serviceName.c_str());
+ ALOGW("sendClientCallbackNotifications could not find service %s when %s",
+ serviceName.c_str(), context);
return;
}
Service& service = serviceIt->second;
- CHECK(hasClients != service.hasClients) << "Record shows: " << service.hasClients
- << " so we can't tell clients again that we have client: " << hasClients;
+ CHECK_NE(hasClients, service.hasClients) << context;
- ALOGI("Notifying %s they have clients: %d", serviceName.c_str(), hasClients);
+ ALOGI("Notifying %s they %s (previously: %s) have clients when %s", serviceName.c_str(),
+ hasClients ? "do" : "don't", service.hasClients ? "do" : "don't", context);
auto ccIt = mNameToClientCallback.find(serviceName);
CHECK(ccIt != mNameToClientCallback.end())
- << "sendClientCallbackNotifications could not find callbacks for service ";
+ << "sendClientCallbackNotifications could not find callbacks for service when "
+ << context;
for (const auto& callback : ccIt->second) {
callback->onClients(service.binder, hasClients);
@@ -795,26 +829,29 @@
return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
}
+ // important because we don't have timer-based guarantees, we don't want to clear
+ // this
if (serviceIt->second.guaranteeClient) {
ALOGI("Tried to unregister %s, but there is about to be a client.", name.c_str());
return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
}
- int clients = handleServiceClientCallback(name, false);
-
- // clients < 0: feature not implemented or other error. Assume clients.
- // Otherwise:
// - kernel driver will hold onto one refcount (during this transaction)
// - servicemanager has a refcount (guaranteed by this transaction)
- // So, if clients > 2, then at least one other service on the system must hold a refcount.
- if (clients < 0 || clients > 2) {
- // client callbacks are either disabled or there are other clients
- ALOGI("Tried to unregister %s, but there are clients: %d", name.c_str(), clients);
- // Set this flag to ensure the clients are acknowledged in the next callback
+ constexpr size_t kKnownClients = 2;
+
+ if (handleServiceClientCallback(kKnownClients, name, false)) {
+ ALOGI("Tried to unregister %s, but there are clients.", name.c_str());
+
+ // Since we had a failed registration attempt, and the HIDL implementation of
+ // delaying service shutdown for multiple periods wasn't ported here... this may
+ // help reduce thrashing, but we should be able to remove it.
serviceIt->second.guaranteeClient = true;
+
return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
}
+ ALOGI("Unregistering %s", name.c_str());
mNameToService.erase(name);
return Status::ok();
diff --git a/cmds/servicemanager/ServiceManager.h b/cmds/servicemanager/ServiceManager.h
index b24c11c..3aa6731 100644
--- a/cmds/servicemanager/ServiceManager.h
+++ b/cmds/servicemanager/ServiceManager.h
@@ -80,6 +80,8 @@
// the number of clients of the service, including servicemanager itself
ssize_t getNodeStrongRefCount();
+
+ ~Service();
};
using ServiceCallbackMap = std::map<std::string, std::vector<sp<IServiceCallback>>>;
@@ -91,9 +93,12 @@
void removeRegistrationCallback(const wp<IBinder>& who,
ServiceCallbackMap::iterator* it,
bool* found);
- ssize_t handleServiceClientCallback(const std::string& serviceName, bool isCalledOnInterval);
- // Also updates mHasClients (of what the last callback was)
- void sendClientCallbackNotifications(const std::string& serviceName, bool hasClients);
+ // returns whether there are known clients in addition to the count provided
+ bool handleServiceClientCallback(size_t knownClients, const std::string& serviceName,
+ bool isCalledOnInterval);
+ // Also updates mHasClients (of what the last callback was)
+ void sendClientCallbackNotifications(const std::string& serviceName, bool hasClients,
+ const char* context);
// removes a callback from mNameToClientCallback, deleting the entry if the vector is empty
// this updates the iterator to the next location
void removeClientCallback(const wp<IBinder>& who, ClientCallbackMap::iterator* it);
diff --git a/cmds/servicemanager/servicemanager.rc b/cmds/servicemanager/servicemanager.rc
index 3bd6db5..4f92b3a 100644
--- a/cmds/servicemanager/servicemanager.rc
+++ b/cmds/servicemanager/servicemanager.rc
@@ -5,7 +5,7 @@
critical
file /dev/kmsg w
onrestart setprop servicemanager.ready false
- onrestart restart apexd
+ onrestart restart --only-if-running apexd
onrestart restart audioserver
onrestart restart gatekeeperd
onrestart class_restart --only-enabled main
diff --git a/cmds/servicemanager/test_sm.cpp b/cmds/servicemanager/test_sm.cpp
index 0fd8d8e..cae32e3 100644
--- a/cmds/servicemanager/test_sm.cpp
+++ b/cmds/servicemanager/test_sm.cpp
@@ -383,6 +383,22 @@
sp<CallbackHistorian> cb = sp<CallbackHistorian>::make();
+ EXPECT_EQ(sm->registerForNotifications("foofoo", cb).exceptionCode(), Status::EX_SECURITY);
+}
+
+TEST(GetService, IsolatedCantRegister) {
+ std::unique_ptr<MockAccess> access = std::make_unique<NiceMock<MockAccess>>();
+
+ EXPECT_CALL(*access, getCallingContext())
+ .WillOnce(Return(Access::CallingContext{
+ .uid = AID_ISOLATED_START,
+ }));
+ EXPECT_CALL(*access, canFind(_, _)).WillOnce(Return(true));
+
+ sp<ServiceManager> sm = sp<ServiceManager>::make(std::move(access));
+
+ sp<CallbackHistorian> cb = sp<CallbackHistorian>::make();
+
EXPECT_EQ(sm->registerForNotifications("foofoo", cb).exceptionCode(),
Status::EX_SECURITY);
}
diff --git a/data/etc/input/Android.bp b/data/etc/input/Android.bp
new file mode 100644
index 0000000..90f3c6b
--- /dev/null
+++ b/data/etc/input/Android.bp
@@ -0,0 +1,14 @@
+package {
+ default_applicable_licenses: ["frameworks_native_license"],
+}
+
+filegroup {
+ name: "motion_predictor_model.fb",
+ srcs: ["motion_predictor_model.fb"],
+}
+
+prebuilt_etc {
+ name: "motion_predictor_model_prebuilt",
+ filename_from_src: true,
+ src: "motion_predictor_model.fb",
+}
diff --git a/data/etc/input/motion_predictor_model.fb b/data/etc/input/motion_predictor_model.fb
new file mode 100644
index 0000000..10b3c8b
--- /dev/null
+++ b/data/etc/input/motion_predictor_model.fb
Binary files differ
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index 4a5bd5e..b494f89 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -37,6 +37,7 @@
#include <android/api-level.h>
#include <stdint.h>
+#include <unistd.h>
__BEGIN_DECLS
@@ -173,7 +174,7 @@
*/
int APerformanceHint_setThreads(
APerformanceHintSession* session,
- const int32_t* threadIds,
+ const pid_t* threadIds,
size_t size) __INTRODUCED_IN(__ANDROID_API_U__);
__END_DECLS
diff --git a/include/input/Input.h b/include/input/Input.h
index 7573282..608519b 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -1113,6 +1113,7 @@
enum class PointerIconStyle : int32_t {
TYPE_CUSTOM = -1,
TYPE_NULL = 0,
+ TYPE_NOT_SPECIFIED = 1,
TYPE_ARROW = 1000,
TYPE_CONTEXT_MENU = 1001,
TYPE_HAND = 1002,
diff --git a/include/input/MotionPredictor.h b/include/input/MotionPredictor.h
index 045e61b..68ebf75 100644
--- a/include/input/MotionPredictor.h
+++ b/include/input/MotionPredictor.h
@@ -16,9 +16,16 @@
#pragma once
+#include <cstdint>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+
#include <android-base/thread_annotations.h>
#include <android/sysprop/InputProperties.sysprop.h>
#include <input/Input.h>
+#include <input/TfLiteMotionPredictor.h>
namespace android {
@@ -28,48 +35,52 @@
/**
* Given a set of MotionEvents for the current gesture, predict the motion. The returned MotionEvent
- * contains a set of samples in the future, up to "presentation time + offset".
+ * contains a set of samples in the future.
*
* The typical usage is like this:
*
* MotionPredictor predictor(offset = MY_OFFSET);
- * predictor.setExpectedPresentationTimeNanos(NEXT_PRESENT_TIME);
* predictor.record(DOWN_MOTION_EVENT);
* predictor.record(MOVE_MOTION_EVENT);
- * prediction = predictor.predict();
+ * prediction = predictor.predict(futureTime);
*
- * The presentation time should be set some time before calling .predict(). It could be set before
- * or after the recorded motion events. Must be done on every frame.
- *
- * The resulting motion event will have eventTime <= (NEXT_PRESENT_TIME + MY_OFFSET). It might
- * contain historical data, which are additional samples from the latest recorded MotionEvent's
- * eventTime to the NEXT_PRESENT_TIME + MY_OFFSET.
+ * The resulting motion event will have eventTime <= (futureTime + MY_OFFSET). It might contain
+ * historical data, which are additional samples from the latest recorded MotionEvent's eventTime
+ * to the futureTime + MY_OFFSET.
*
* The offset is used to provide additional flexibility to the caller, in case the default present
* time (typically provided by the choreographer) does not account for some delays, or to simply
- * reduce the aggressiveness of the prediction. Offset can be both positive and negative.
+ * reduce the aggressiveness of the prediction. Offset can be positive or negative.
*/
class MotionPredictor {
public:
/**
* Parameters:
* predictionTimestampOffsetNanos: additional, constant shift to apply to the target
- * presentation time. The prediction will target the time t=(presentationTime +
+ * prediction time. The prediction will target the time t=(prediction time +
* predictionTimestampOffsetNanos).
*
+ * modelPath: filesystem path to a TfLiteMotionPredictorModel flatbuffer, or nullptr to use the
+ * default model path.
+ *
* checkEnableMotionPredition: the function to check whether the prediction should run. Used to
* provide an additional way of turning prediction on and off. Can be toggled at runtime.
*/
- MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
+ MotionPredictor(nsecs_t predictionTimestampOffsetNanos, const char* modelPath = nullptr,
std::function<bool()> checkEnableMotionPrediction = isMotionPredictionEnabled);
void record(const MotionEvent& event);
std::vector<std::unique_ptr<MotionEvent>> predict(nsecs_t timestamp);
bool isPredictionAvailable(int32_t deviceId, int32_t source);
private:
- std::vector<MotionEvent> mEvents;
const nsecs_t mPredictionTimestampOffsetNanos;
+ const std::string mModelPath;
const std::function<bool()> mCheckMotionPredictionEnabled;
+
+ std::unique_ptr<TfLiteMotionPredictorModel> mModel;
+ // Buffers/events for each device seen by record().
+ std::unordered_map</*deviceId*/ int32_t, TfLiteMotionPredictorBuffers> mDeviceBuffers;
+ std::unordered_map</*deviceId*/ int32_t, MotionEvent> mLastEvents;
};
} // namespace android
diff --git a/include/input/RingBuffer.h b/include/input/RingBuffer.h
new file mode 100644
index 0000000..67984b7
--- /dev/null
+++ b/include/input/RingBuffer.h
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <compare>
+#include <cstddef>
+#include <iterator>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+namespace android {
+
+// A fixed-size ring buffer of elements.
+//
+// Elements can only be removed from the front/back or added to the front/back, but with O(1)
+// performance. Elements from the opposing side are evicted when new elements are pushed onto a full
+// buffer.
+template <typename T>
+class RingBuffer {
+public:
+ using value_type = T;
+ using size_type = size_t;
+ using difference_type = ptrdiff_t;
+ using reference = value_type&;
+ using const_reference = const value_type&;
+ using pointer = value_type*;
+ using const_pointer = const value_type*;
+
+ template <typename U>
+ class Iterator;
+ using iterator = Iterator<T>;
+ using const_iterator = Iterator<const T>;
+
+ // Creates an empty ring buffer that can hold some capacity.
+ explicit RingBuffer(size_type capacity)
+ : mBuffer(std::allocator<value_type>().allocate(capacity)), mCapacity(capacity) {}
+
+ // Creates a full ring buffer holding a fixed number of elements initialised to some value.
+ explicit RingBuffer(size_type count, const_reference value) : RingBuffer(count) {
+ while (count) {
+ pushBack(value);
+ --count;
+ }
+ }
+
+ RingBuffer(const RingBuffer& other) : RingBuffer(other.capacity()) {
+ for (const auto& element : other) {
+ pushBack(element);
+ }
+ }
+
+ RingBuffer(RingBuffer&& other) noexcept { *this = std::move(other); }
+
+ ~RingBuffer() {
+ if (mBuffer) {
+ clear();
+ std::allocator<value_type>().deallocate(mBuffer, mCapacity);
+ }
+ }
+
+ RingBuffer& operator=(const RingBuffer& other) { return *this = RingBuffer(other); }
+
+ RingBuffer& operator=(RingBuffer&& other) noexcept {
+ if (this == &other) {
+ return *this;
+ }
+ if (mBuffer) {
+ clear();
+ std::allocator<value_type>().deallocate(mBuffer, mCapacity);
+ }
+ mBuffer = std::move(other.mBuffer);
+ mCapacity = other.mCapacity;
+ mBegin = other.mBegin;
+ mSize = other.mSize;
+ other.mBuffer = nullptr;
+ other.mCapacity = 0;
+ other.mBegin = 0;
+ other.mSize = 0;
+ return *this;
+ }
+
+ iterator begin() { return {*this, 0}; }
+ const_iterator begin() const { return {*this, 0}; }
+ iterator end() { return {*this, mSize}; }
+ const_iterator end() const { return {*this, mSize}; }
+
+ reference operator[](size_type i) { return mBuffer[bufferIndex(i)]; }
+ const_reference operator[](size_type i) const { return mBuffer[bufferIndex(i)]; }
+
+ // Removes all elements from the buffer.
+ void clear() {
+ std::destroy(begin(), end());
+ mSize = 0;
+ }
+
+ // Removes and returns the first element from the buffer.
+ value_type popFront() {
+ value_type element = mBuffer[mBegin];
+ std::destroy_at(std::addressof(mBuffer[mBegin]));
+ mBegin = next(mBegin);
+ --mSize;
+ return element;
+ }
+
+ // Removes and returns the last element from the buffer.
+ value_type popBack() {
+ size_type backIndex = bufferIndex(mSize - 1);
+ value_type element = mBuffer[backIndex];
+ std::destroy_at(std::addressof(mBuffer[backIndex]));
+ --mSize;
+ return element;
+ }
+
+ // Adds an element to the front of the buffer.
+ void pushFront(const value_type& element) { pushFront(value_type(element)); }
+ void pushFront(value_type&& element) {
+ mBegin = previous(mBegin);
+ if (size() == capacity()) {
+ mBuffer[mBegin] = std::forward<value_type>(element);
+ } else {
+ // The space at mBuffer[mBegin] is uninitialised.
+ // TODO: Use std::construct_at when it becomes available.
+ new (std::addressof(mBuffer[mBegin])) value_type(std::forward<value_type>(element));
+ ++mSize;
+ }
+ }
+
+ // Adds an element to the back of the buffer.
+ void pushBack(const value_type& element) { pushBack(value_type(element)); }
+ void pushBack(value_type&& element) {
+ if (size() == capacity()) {
+ mBuffer[mBegin] = std::forward<value_type>(element);
+ mBegin = next(mBegin);
+ } else {
+ // The space at mBuffer[...] is uninitialised.
+ // TODO: Use std::construct_at when it becomes available.
+ new (std::addressof(mBuffer[bufferIndex(mSize)]))
+ value_type(std::forward<value_type>(element));
+ ++mSize;
+ }
+ }
+
+ bool empty() const { return mSize == 0; }
+ size_type capacity() const { return mCapacity; }
+ size_type size() const { return mSize; }
+
+ void swap(RingBuffer& other) noexcept {
+ using std::swap;
+ swap(mBuffer, other.mBuffer);
+ swap(mCapacity, other.mCapacity);
+ swap(mBegin, other.mBegin);
+ swap(mSize, other.mSize);
+ }
+
+ friend void swap(RingBuffer& lhs, RingBuffer& rhs) noexcept { lhs.swap(rhs); }
+
+ template <typename U>
+ class Iterator {
+ private:
+ using ContainerType = std::conditional_t<std::is_const_v<U>, const RingBuffer, RingBuffer>;
+
+ public:
+ using iterator_category = std::random_access_iterator_tag;
+ using size_type = ContainerType::size_type;
+ using difference_type = ContainerType::difference_type;
+ using value_type = std::remove_cv_t<U>;
+ using pointer = U*;
+ using reference = U&;
+
+ Iterator(ContainerType& container, size_type index)
+ : mContainer(container), mIndex(index) {}
+
+ Iterator(const Iterator&) = default;
+ Iterator& operator=(const Iterator&) = default;
+
+ Iterator& operator++() {
+ ++mIndex;
+ return *this;
+ }
+
+ Iterator operator++(int) {
+ Iterator iterator(*this);
+ ++(*this);
+ return iterator;
+ }
+
+ Iterator& operator--() {
+ --mIndex;
+ return *this;
+ }
+
+ Iterator operator--(int) {
+ Iterator iterator(*this);
+ --(*this);
+ return iterator;
+ }
+
+ Iterator& operator+=(difference_type n) {
+ mIndex += n;
+ return *this;
+ }
+
+ Iterator operator+(difference_type n) {
+ Iterator iterator(*this);
+ return iterator += n;
+ }
+
+ Iterator& operator-=(difference_type n) { return *this += -n; }
+
+ Iterator operator-(difference_type n) {
+ Iterator iterator(*this);
+ return iterator -= n;
+ }
+
+ difference_type operator-(const Iterator& other) { return mIndex - other.mIndex; }
+
+ bool operator==(const Iterator& rhs) const { return mIndex == rhs.mIndex; }
+
+ bool operator!=(const Iterator& rhs) const { return !(*this == rhs); }
+
+ friend auto operator<=>(const Iterator& lhs, const Iterator& rhs) {
+ return lhs.mIndex <=> rhs.mIndex;
+ }
+
+ reference operator[](difference_type n) { return *(*this + n); }
+
+ reference operator*() const { return mContainer[mIndex]; }
+ pointer operator->() const { return std::addressof(mContainer[mIndex]); }
+
+ private:
+ ContainerType& mContainer;
+ size_type mIndex = 0;
+ };
+
+private:
+ // Returns the index of the next element in mBuffer.
+ size_type next(size_type index) const {
+ if (index == capacity() - 1) {
+ return 0;
+ } else {
+ return index + 1;
+ }
+ }
+
+ // Returns the index of the previous element in mBuffer.
+ size_type previous(size_type index) const {
+ if (index == 0) {
+ return capacity() - 1;
+ } else {
+ return index - 1;
+ }
+ }
+
+ // Converts the index of an element in [0, size()] to its corresponding index in mBuffer.
+ size_type bufferIndex(size_type elementIndex) const {
+ CHECK_LE(elementIndex, size());
+ size_type index = mBegin + elementIndex;
+ if (index >= capacity()) {
+ index -= capacity();
+ }
+ CHECK_LT(index, capacity())
+ << android::base::StringPrintf("Invalid index calculated for element (%zu) "
+ "in buffer of size %zu",
+ elementIndex, size());
+ return index;
+ }
+
+ pointer mBuffer = nullptr;
+ size_type mCapacity = 0; // Total capacity of mBuffer.
+ size_type mBegin = 0; // Index of the first initialised element in mBuffer.
+ size_type mSize = 0; // Total number of initialised elements.
+};
+
+} // namespace android
diff --git a/include/input/TfLiteMotionPredictor.h b/include/input/TfLiteMotionPredictor.h
new file mode 100644
index 0000000..54e2851
--- /dev/null
+++ b/include/input/TfLiteMotionPredictor.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <span>
+
+#include <android-base/mapped_file.h>
+#include <input/RingBuffer.h>
+
+#include <tensorflow/lite/core/api/error_reporter.h>
+#include <tensorflow/lite/interpreter.h>
+#include <tensorflow/lite/model.h>
+#include <tensorflow/lite/signature_runner.h>
+
+namespace android {
+
+struct TfLiteMotionPredictorSample {
+ // The untransformed AMOTION_EVENT_AXIS_X and AMOTION_EVENT_AXIS_Y of the sample.
+ struct Point {
+ float x;
+ float y;
+ } position;
+ // The AMOTION_EVENT_AXIS_PRESSURE, _TILT, and _ORIENTATION.
+ float pressure;
+ float tilt;
+ float orientation;
+};
+
+inline TfLiteMotionPredictorSample::Point operator-(const TfLiteMotionPredictorSample::Point& lhs,
+ const TfLiteMotionPredictorSample::Point& rhs) {
+ return {.x = lhs.x - rhs.x, .y = lhs.y - rhs.y};
+}
+
+class TfLiteMotionPredictorModel;
+
+// Buffer storage for a TfLiteMotionPredictorModel.
+class TfLiteMotionPredictorBuffers {
+public:
+ // Creates buffer storage for a model with the given input length.
+ TfLiteMotionPredictorBuffers(size_t inputLength);
+
+ // Adds a motion sample to the buffers.
+ void pushSample(int64_t timestamp, TfLiteMotionPredictorSample sample);
+
+ // Returns true if the buffers are complete enough to generate a prediction.
+ bool isReady() const {
+ // Predictions can't be applied unless there are at least two points to determine
+ // the direction to apply them in.
+ return mAxisFrom && mAxisTo;
+ }
+
+ // Resets all buffers to their initial state.
+ void reset();
+
+ // Copies the buffers to those of a model for prediction.
+ void copyTo(TfLiteMotionPredictorModel& model) const;
+
+ // Returns the current axis of the buffer's samples. Only valid if isReady().
+ TfLiteMotionPredictorSample axisFrom() const { return *mAxisFrom; }
+ TfLiteMotionPredictorSample axisTo() const { return *mAxisTo; }
+
+ // Returns the timestamp of the last sample.
+ int64_t lastTimestamp() const { return mTimestamp; }
+
+private:
+ int64_t mTimestamp = 0;
+
+ RingBuffer<float> mInputR;
+ RingBuffer<float> mInputPhi;
+ RingBuffer<float> mInputPressure;
+ RingBuffer<float> mInputTilt;
+ RingBuffer<float> mInputOrientation;
+
+ // The samples defining the current polar axis.
+ std::optional<TfLiteMotionPredictorSample> mAxisFrom;
+ std::optional<TfLiteMotionPredictorSample> mAxisTo;
+};
+
+// A TFLite model for generating motion predictions.
+class TfLiteMotionPredictorModel {
+public:
+ // Creates a model from an encoded Flatbuffer model.
+ static std::unique_ptr<TfLiteMotionPredictorModel> create(const char* modelPath);
+
+ ~TfLiteMotionPredictorModel();
+
+ // Returns the length of the model's input buffers.
+ size_t inputLength() const;
+
+ // Executes the model.
+ // Returns true if the model successfully executed and the output tensors can be read.
+ bool invoke();
+
+ // Returns mutable buffers to the input tensors of inputLength() elements.
+ std::span<float> inputR();
+ std::span<float> inputPhi();
+ std::span<float> inputPressure();
+ std::span<float> inputOrientation();
+ std::span<float> inputTilt();
+
+ // Returns immutable buffers to the output tensors of identical length. Only valid after a
+ // successful call to invoke().
+ std::span<const float> outputR() const;
+ std::span<const float> outputPhi() const;
+ std::span<const float> outputPressure() const;
+
+private:
+ explicit TfLiteMotionPredictorModel(std::unique_ptr<android::base::MappedFile> model);
+
+ void allocateTensors();
+ void attachInputTensors();
+ void attachOutputTensors();
+
+ TfLiteTensor* mInputR = nullptr;
+ TfLiteTensor* mInputPhi = nullptr;
+ TfLiteTensor* mInputPressure = nullptr;
+ TfLiteTensor* mInputTilt = nullptr;
+ TfLiteTensor* mInputOrientation = nullptr;
+
+ const TfLiteTensor* mOutputR = nullptr;
+ const TfLiteTensor* mOutputPhi = nullptr;
+ const TfLiteTensor* mOutputPressure = nullptr;
+
+ std::unique_ptr<android::base::MappedFile> mFlatBuffer;
+ std::unique_ptr<tflite::ErrorReporter> mErrorReporter;
+ std::unique_ptr<tflite::FlatBufferModel> mModel;
+ std::unique_ptr<tflite::Interpreter> mInterpreter;
+ tflite::SignatureRunner* mRunner = nullptr;
+};
+
+} // namespace android
diff --git a/include/private/surface_control_private.h b/include/private/surface_control_private.h
index 7e6c515..138926e 100644
--- a/include/private/surface_control_private.h
+++ b/include/private/surface_control_private.h
@@ -19,6 +19,8 @@
#include <stdint.h>
+#include <android/choreographer.h>
+
__BEGIN_DECLS
struct ASurfaceControl;
@@ -56,6 +58,13 @@
ASurfaceControl_SurfaceStatsListener func);
/**
+ * Gets the attached AChoreographer instance from the given \c surfaceControl. If there is no
+ * choreographer associated with the surface control, then a new instance of choreographer is
+ * created. The new choreographer is associated with the current thread's Looper.
+ */
+AChoreographer* ASurfaceControl_getChoreographer(ASurfaceControl* surfaceControl);
+
+/**
* Returns the timestamp of when the buffer was acquired for a specific frame with frame number
* obtained from ASurfaceControlStats_getFrameNumber.
*/
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 7e7bba3..c4c8ffb 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -215,6 +215,8 @@
cc_defaults {
name: "trusty_mock_defaults",
+ vendor_available: true,
+ host_supported: true,
header_libs: [
"trusty_mock_headers",
@@ -287,6 +289,14 @@
cflags: [
"-DBINDER_WITH_KERNEL_IPC",
],
+ arch: {
+ // TODO(b/254713216): undefined symbol in BufferedTextOutput::getBuffer
+ riscv64: {
+ lto: {
+ thin: false,
+ },
+ },
+ },
}
cc_library {
@@ -508,6 +518,10 @@
enabled: false,
},
},
+ visibility: [
+ ":__subpackages__",
+ "//system/tools/aidl:__subpackages__",
+ ],
}
// TODO(b/184872979): remove once the Rust API is created.
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index d03326e..53852d8 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -388,7 +388,8 @@
{
if (isRpcBinder()) {
if (rpcSession()->getMaxIncomingThreads() < 1) {
- ALOGE("Cannot register a DeathRecipient without any incoming connections.");
+ ALOGE("Cannot register a DeathRecipient without any incoming threads. Need to set max "
+ "incoming threads to a value greater than 0 before calling linkToDeath.");
return INVALID_OPERATION;
}
} else if constexpr (!kEnableKernelIpc) {
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index 04cb61f..1488400 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -86,6 +86,12 @@
"name": "binderRpcTest"
},
{
+ "name": "CtsRootRollbackManagerHostTestCases"
+ },
+ {
+ "name": "StagedRollbackTest"
+ },
+ {
"name": "binderRpcTestNoKernel"
},
{
diff --git a/libs/binder/include/binder/ProcessState.h b/libs/binder/include/binder/ProcessState.h
index bad8cb1..471c994 100644
--- a/libs/binder/include/binder/ProcessState.h
+++ b/libs/binder/include/binder/ProcessState.h
@@ -64,6 +64,11 @@
// For main functions - dangerous for libraries to use
status_t setThreadPoolMaxThreadCount(size_t maxThreads);
status_t enableOnewaySpamDetection(bool enable);
+
+ // Set the name of the current thread to look like a threadpool
+ // thread. Typically this is called before joinThreadPool.
+ //
+ // TODO: remove this API, and automatically set it intelligently.
void giveThreadPoolName();
String8 getDriverName();
diff --git a/libs/binder/ndk/include_cpp/android/binder_to_string.h b/libs/binder/ndk/include_cpp/android/binder_to_string.h
index 9b0d222..b0c7f6d 100644
--- a/libs/binder/ndk/include_cpp/android/binder_to_string.h
+++ b/libs/binder/ndk/include_cpp/android/binder_to_string.h
@@ -139,30 +139,6 @@
};
template <typename _T>
-class ToEmptyString {
- template <typename _U>
- static std::enable_if_t<false
-#ifdef HAS_NDK_INTERFACE
- || std::is_base_of_v<::ndk::ICInterface, _U>
-#if __ANDROID_API__ >= 31
- || std::is_same_v<::ndk::AParcelableHolder, _U>
-#endif
-#endif // HAS_NDK_INTERFACE
-#ifdef HAS_CPP_INTERFACE
- || std::is_base_of_v<IInterface, _U> ||
- std::is_same_v<IBinder, _U>
-#endif
- ,
- std::true_type>
- _test(int);
- template <typename _U>
- static std::false_type _test(...);
-
- public:
- enum { value = decltype(_test<_T>(0))::value };
-};
-
-template <typename _T>
struct TypeDependentFalse {
enum { value = false };
};
@@ -171,9 +147,7 @@
template <typename _T>
std::string ToString(const _T& t) {
- if constexpr (details::ToEmptyString<_T>::value) {
- return "<unimplemented>";
- } else if constexpr (std::is_same_v<bool, _T>) {
+ if constexpr (std::is_same_v<bool, _T>) {
return t ? "true" : "false";
} else if constexpr (std::is_same_v<char16_t, _T>) {
// TODO(b/244494451): codecvt is deprecated in C++17 -- suppress the
@@ -193,6 +167,24 @@
return ss.str();
} else if constexpr (std::is_same_v<::ndk::ScopedFileDescriptor, _T>) {
return "fd:" + std::to_string(t.get());
+ } else if constexpr (std::is_base_of_v<::ndk::ICInterface, _T>) {
+ // TODO(b/266248339): this format is to make it easy to handle resolv_integration_test
+ // freezing the output format. We would like to print more info.
+ return "<interface>";
+#if __ANDROID_API__ >= 31
+ } else if constexpr (std::is_same_v<::ndk::AParcelableHolder, _T>) {
+ return "AParcelableHolder";
+#endif
+#endif // HAS_NDK_INTERFACE
+#ifdef HAS_CPP_INTERFACE
+ } else if constexpr (std::is_base_of_v<IInterface, _T>) {
+ std::stringstream ss;
+ ss << "interface:" << std::hex << &t;
+ return ss.str();
+ } else if constexpr (std::is_same_v<IBinder, _T>) {
+ std::stringstream ss;
+ ss << "binder:" << std::hex << &t;
+ return ss.str();
#endif
#ifdef HAS_STRING16
} else if constexpr (std::is_same_v<String16, _T>) {
diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h
index ad4188f..86d5ed2 100644
--- a/libs/binder/ndk/include_platform/android/binder_manager.h
+++ b/libs/binder/ndk/include_platform/android/binder_manager.h
@@ -38,6 +38,22 @@
AIBinder* binder, const char* instance) __INTRODUCED_IN(29);
/**
+ * This registers the service with the default service manager under this instance name. This does
+ * not take ownership of binder.
+ *
+ * WARNING: when using this API across an APEX boundary, do not use with unstable
+ * AIDL services. TODO(b/139325195)
+ *
+ * \param binder object to register globally with the service manager.
+ * \param instance identifier of the service. This will be used to lookup the service.
+ * \param allowIsolated allows if this service can be isolated.
+ *
+ * \return EX_NONE on success.
+ */
+__attribute__((warn_unused_result)) binder_exception_t AServiceManager_addServiceWithAllowIsolated(
+ AIBinder* binder, const char* instance, bool allowIsolated) __INTRODUCED_IN(34);
+
+/**
* Gets a binder object with this specific instance name. Will return nullptr immediately if the
* service is not available This also implicitly calls AIBinder_incStrong (so the caller of this
* function is responsible for calling AIBinder_decStrong).
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 54e4628..5f2f617 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -163,6 +163,7 @@
LIBBINDER_NDK_PLATFORM {
global:
AParcel_getAllowFds;
+ AServiceManager_addServiceWithAllowIsolated;
extern "C++" {
AIBinder_fromPlatformBinder*;
AIBinder_toPlatformBinder*;
diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp
index e107c83..2763ddb 100644
--- a/libs/binder/ndk/service_manager.cpp
+++ b/libs/binder/ndk/service_manager.cpp
@@ -41,6 +41,19 @@
status_t exception = sm->addService(String16(instance), binder->getBinder());
return PruneException(exception);
}
+
+binder_exception_t AServiceManager_addServiceWithAllowIsolated(AIBinder* binder,
+ const char* instance,
+ bool allowIsolated) {
+ if (binder == nullptr || instance == nullptr) {
+ return EX_ILLEGAL_ARGUMENT;
+ }
+
+ sp<IServiceManager> sm = defaultServiceManager();
+ status_t exception = sm->addService(String16(instance), binder->getBinder(), allowIsolated);
+ return PruneException(exception);
+}
+
AIBinder* AServiceManager_checkService(const char* instance) {
if (instance == nullptr) {
return nullptr;
diff --git a/libs/binder/rust/rpcbinder/Android.bp b/libs/binder/rust/rpcbinder/Android.bp
index afb73e9..38dd4fe 100644
--- a/libs/binder/rust/rpcbinder/Android.bp
+++ b/libs/binder/rust/rpcbinder/Android.bp
@@ -23,7 +23,13 @@
"liblibc",
"liblog_rust",
],
+ visibility: [
+ "//device/google/cuttlefish/shared/minidroid/sample",
+ "//packages/modules/Uwb",
+ "//packages/modules/Virtualization:__subpackages__",
+ ],
apex_available: [
+ "//apex_available:platform",
"com.android.compos",
"com.android.uwb",
"com.android.virt",
@@ -51,6 +57,7 @@
"libutils",
],
apex_available: [
+ "//apex_available:platform",
"com.android.compos",
"com.android.uwb",
"com.android.virt",
@@ -84,6 +91,7 @@
"libutils",
],
apex_available: [
+ "//apex_available:platform",
"com.android.compos",
"com.android.uwb",
"com.android.virt",
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index 7006f87..e609987 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -370,6 +370,31 @@
],
}
+cc_binary {
+ name: "binderRpcTest_on_trusty_mock",
+ defaults: [
+ "trusty_mock_defaults",
+ ],
+
+ srcs: [
+ "binderRpcUniversalTests.cpp",
+ "binderRpcTestCommon.cpp",
+ "binderRpcTestTrusty.cpp",
+ ],
+
+ shared_libs: [
+ "libbinder_on_trusty_mock",
+ "libbase",
+ "libutils",
+ "libcutils",
+ ],
+
+ static_libs: [
+ "binderRpcTestIface-cpp",
+ "libgtest",
+ ],
+}
+
cc_test {
name: "binderRpcTest",
defaults: [
@@ -382,6 +407,7 @@
required: [
"libbinder_on_trusty_mock",
"binderRpcTestService_on_trusty_mock",
+ "binderRpcTest_on_trusty_mock",
],
}
diff --git a/libs/binder/tests/binderRpcTestTrusty.cpp b/libs/binder/tests/binderRpcTestTrusty.cpp
new file mode 100644
index 0000000..b3bb5eb
--- /dev/null
+++ b/libs/binder/tests/binderRpcTestTrusty.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "binderRpcTest"
+
+#include <android-base/stringprintf.h>
+#include <binder/RpcTransportTipcTrusty.h>
+#include <trusty-gtest.h>
+#include <trusty_ipc.h>
+
+#include "binderRpcTestFixture.h"
+
+namespace android {
+
+// Destructors need to be defined, even if pure virtual
+ProcessSession::~ProcessSession() {}
+
+class TrustyProcessSession : public ProcessSession {
+public:
+ ~TrustyProcessSession() override {}
+
+ void setCustomExitStatusCheck(std::function<void(int wstatus)> /*f*/) override {
+ LOG_ALWAYS_FATAL("setCustomExitStatusCheck() not supported");
+ }
+
+ void terminate() override { LOG_ALWAYS_FATAL("terminate() not supported"); }
+};
+
+std::string BinderRpc::PrintParamInfo(const testing::TestParamInfo<ParamType>& info) {
+ auto [type, security, clientVersion, serverVersion, singleThreaded, noKernel] = info.param;
+ auto ret = PrintToString(type) + "_clientV" + std::to_string(clientVersion) + "_serverV" +
+ std::to_string(serverVersion);
+ if (singleThreaded) {
+ ret += "_single_threaded";
+ }
+ if (noKernel) {
+ ret += "_no_kernel";
+ }
+ return ret;
+}
+
+// This creates a new process serving an interface on a certain number of
+// threads.
+std::unique_ptr<ProcessSession> BinderRpc::createRpcTestSocketServerProcessEtc(
+ const BinderRpcOptions& options) {
+ LOG_ALWAYS_FATAL_IF(options.numIncomingConnections != 0,
+ "Non-zero incoming connections %zu on Trusty",
+ options.numIncomingConnections);
+
+ uint32_t clientVersion = std::get<2>(GetParam());
+ uint32_t serverVersion = std::get<3>(GetParam());
+
+ auto ret = std::make_unique<TrustyProcessSession>();
+
+ status_t status;
+ for (size_t i = 0; i < options.numSessions; i++) {
+ auto factory = android::RpcTransportCtxFactoryTipcTrusty::make();
+ auto session = android::RpcSession::make(std::move(factory));
+
+ EXPECT_TRUE(session->setProtocolVersion(clientVersion));
+ session->setMaxOutgoingThreads(options.numOutgoingConnections);
+ session->setFileDescriptorTransportMode(options.clientFileDescriptorTransportMode);
+
+ status = session->setupPreconnectedClient({}, [&]() {
+ auto port = trustyIpcPort(serverVersion);
+ int rc = connect(port.c_str(), IPC_CONNECT_WAIT_FOR_PORT);
+ LOG_ALWAYS_FATAL_IF(rc < 0, "Failed to connect to service: %d", rc);
+ return base::unique_fd(rc);
+ });
+ if (options.allowConnectFailure && status != OK) {
+ ret->sessions.clear();
+ break;
+ }
+ LOG_ALWAYS_FATAL_IF(status != OK, "Failed to connect to service: %s",
+ statusToString(status).c_str());
+ ret->sessions.push_back({session, session->getRootObject()});
+ }
+
+ return ret;
+}
+
+INSTANTIATE_TEST_CASE_P(Trusty, BinderRpc,
+ ::testing::Combine(::testing::Values(SocketType::TIPC),
+ ::testing::Values(RpcSecurity::RAW),
+ ::testing::ValuesIn(testVersions()),
+ ::testing::ValuesIn(testVersions()),
+ ::testing::Values(false), ::testing::Values(true)),
+ BinderRpc::PrintParamInfo);
+
+} // namespace android
+
+PORT_GTEST(BinderRpcTest, "com.android.trusty.binderRpcTest");
diff --git a/libs/binder/tests/binderRpcUniversalTests.cpp b/libs/binder/tests/binderRpcUniversalTests.cpp
index 2249e5c..11a22b0 100644
--- a/libs/binder/tests/binderRpcUniversalTests.cpp
+++ b/libs/binder/tests/binderRpcUniversalTests.cpp
@@ -386,11 +386,11 @@
EXPECT_EQ(b, weak.promote());
}
-#define expectSessions(expected, iface) \
+#define EXPECT_SESSIONS(expected, iface) \
do { \
int session; \
EXPECT_OK((iface)->getNumOpenSessions(&session)); \
- EXPECT_EQ(expected, session); \
+ EXPECT_EQ(static_cast<int>(expected), session); \
} while (false)
TEST_P(BinderRpc, SingleSession) {
@@ -402,9 +402,9 @@
EXPECT_OK(session->getName(&out));
EXPECT_EQ("aoeu", out);
- expectSessions(1, proc.rootIface);
+ EXPECT_SESSIONS(1, proc.rootIface);
session = nullptr;
- expectSessions(0, proc.rootIface);
+ EXPECT_SESSIONS(0, proc.rootIface);
}
TEST_P(BinderRpc, ManySessions) {
@@ -413,24 +413,24 @@
std::vector<sp<IBinderRpcSession>> sessions;
for (size_t i = 0; i < 15; i++) {
- expectSessions(i, proc.rootIface);
+ EXPECT_SESSIONS(i, proc.rootIface);
sp<IBinderRpcSession> session;
EXPECT_OK(proc.rootIface->openSession(std::to_string(i), &session));
sessions.push_back(session);
}
- expectSessions(sessions.size(), proc.rootIface);
+ EXPECT_SESSIONS(sessions.size(), proc.rootIface);
for (size_t i = 0; i < sessions.size(); i++) {
std::string out;
EXPECT_OK(sessions.at(i)->getName(&out));
EXPECT_EQ(std::to_string(i), out);
}
- expectSessions(sessions.size(), proc.rootIface);
+ EXPECT_SESSIONS(sessions.size(), proc.rootIface);
while (!sessions.empty()) {
sessions.pop_back();
- expectSessions(sessions.size(), proc.rootIface);
+ EXPECT_SESSIONS(sessions.size(), proc.rootIface);
}
- expectSessions(0, proc.rootIface);
+ EXPECT_SESSIONS(0, proc.rootIface);
}
TEST_P(BinderRpc, OnewayCallDoesNotWait) {
@@ -483,7 +483,7 @@
cb->mCv.wait_for(_l, 1s, [&] { return !cb->mValues.empty(); });
}
- EXPECT_EQ(cb->mValues.size(), 1)
+ EXPECT_EQ(cb->mValues.size(), 1UL)
<< "callIsOneway: " << callIsOneway
<< " callbackIsOneway: " << callbackIsOneway << " delayed: " << delayed;
if (cb->mValues.empty()) continue;
diff --git a/libs/binder/trusty/RpcServerTrusty.cpp b/libs/binder/trusty/RpcServerTrusty.cpp
index 18ce316..109da75 100644
--- a/libs/binder/trusty/RpcServerTrusty.cpp
+++ b/libs/binder/trusty/RpcServerTrusty.cpp
@@ -117,7 +117,15 @@
*ctx_p = channelContext;
};
- base::unique_fd clientFd(chan);
+ // We need to duplicate the channel handle here because the tipc library
+ // owns the original handle and closes is automatically on channel cleanup.
+ // We use dup() because Trusty does not have fcntl().
+ // NOLINTNEXTLINE(android-cloexec-dup)
+ handle_t chanDup = dup(chan);
+ if (chanDup < 0) {
+ return chanDup;
+ }
+ base::unique_fd clientFd(chanDup);
android::RpcTransportFd transportFd(std::move(clientFd));
std::array<uint8_t, RpcServer::kRpcAddressSize> addr;
diff --git a/libs/binder/trusty/binderRpcTest/manifest.json b/libs/binder/trusty/binderRpcTest/manifest.json
new file mode 100644
index 0000000..d8b080f
--- /dev/null
+++ b/libs/binder/trusty/binderRpcTest/manifest.json
@@ -0,0 +1,6 @@
+{
+ "uuid": "9dbe9fb8-60fd-4bdd-af86-03e95d7ad78b",
+ "app_name": "binderRpcTest",
+ "min_heap": 163840,
+ "min_stack": 16384
+}
diff --git a/libs/binder/trusty/binderRpcTest/rules.mk b/libs/binder/trusty/binderRpcTest/rules.mk
new file mode 100644
index 0000000..ae39492
--- /dev/null
+++ b/libs/binder/trusty/binderRpcTest/rules.mk
@@ -0,0 +1,35 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_TESTS_DIR := frameworks/native/libs/binder/tests
+
+MODULE := $(LOCAL_DIR)
+
+MANIFEST := $(LOCAL_DIR)/manifest.json
+
+MODULE_SRCS += \
+ $(LIBBINDER_TESTS_DIR)/binderRpcUniversalTests.cpp \
+ $(LIBBINDER_TESTS_DIR)/binderRpcTestCommon.cpp \
+ $(LIBBINDER_TESTS_DIR)/binderRpcTestTrusty.cpp \
+
+MODULE_LIBRARY_DEPS += \
+ $(LOCAL_DIR)/aidl \
+ frameworks/native/libs/binder/trusty \
+ frameworks/native/libs/binder/trusty/ndk \
+ trusty/user/base/lib/googletest \
+ trusty/user/base/lib/libstdc++-trusty \
+
+include make/trusted_app.mk
diff --git a/libs/binder/trusty/build-config-usertests b/libs/binder/trusty/build-config-usertests
new file mode 100644
index 0000000..d0a1fbc
--- /dev/null
+++ b/libs/binder/trusty/build-config-usertests
@@ -0,0 +1,19 @@
+# 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.
+
+# This file lists userspace tests
+
+[
+ porttest("com.android.trusty.binderRpcTest"),
+]
diff --git a/libs/binder/trusty/include_mock/lib/tipc/tipc.h b/libs/binder/trusty/include_mock/lib/tipc/tipc.h
index f295be4..ead9f9c 100644
--- a/libs/binder/trusty/include_mock/lib/tipc/tipc.h
+++ b/libs/binder/trusty/include_mock/lib/tipc/tipc.h
@@ -15,7 +15,9 @@
*/
#pragma once
-__BEGIN_DECLS
+#if defined(__cplusplus)
+extern "C" {
+#endif
struct tipc_hset;
@@ -26,4 +28,6 @@
return 0;
}
-__END_DECLS
+#if defined(__cplusplus)
+}
+#endif
diff --git a/libs/binder/trusty/include_mock/trusty-gtest.h b/libs/binder/trusty/include_mock/trusty-gtest.h
new file mode 100644
index 0000000..046b403
--- /dev/null
+++ b/libs/binder/trusty/include_mock/trusty-gtest.h
@@ -0,0 +1,21 @@
+/*
+ * 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
+
+#define PORT_GTEST(suite, port) \
+ int main(void) { \
+ return 0; \
+ }
diff --git a/libs/binder/trusty/include_mock/trusty_ipc.h b/libs/binder/trusty/include_mock/trusty_ipc.h
index 43ab84a..db044c2 100644
--- a/libs/binder/trusty/include_mock/trusty_ipc.h
+++ b/libs/binder/trusty/include_mock/trusty_ipc.h
@@ -27,6 +27,8 @@
#define IPC_PORT_ALLOW_TA_CONNECT 0x1
#define IPC_PORT_ALLOW_NS_CONNECT 0x2
+#define IPC_CONNECT_WAIT_FOR_PORT 0x1
+
#define IPC_HANDLE_POLL_HUP 0x1
#define IPC_HANDLE_POLL_MSG 0x2
#define IPC_HANDLE_POLL_SEND_UNBLOCKED 0x4
diff --git a/libs/binder/trusty/usertests-inc.mk b/libs/binder/trusty/usertests-inc.mk
index 2f5a7f4..1300121 100644
--- a/libs/binder/trusty/usertests-inc.mk
+++ b/libs/binder/trusty/usertests-inc.mk
@@ -14,4 +14,6 @@
#
TRUSTY_USER_TESTS += \
+ frameworks/native/libs/binder/trusty/binderRpcTest \
frameworks/native/libs/binder/trusty/binderRpcTest/service \
+
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 6c9c28a..21900a0 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -254,6 +254,10 @@
lto: {
thin: true,
},
+
+ cflags: [
+ "-Wthread-safety",
+ ],
}
// Used by media codec services exclusively as a static lib for
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 60603ba..9d82c14 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -20,6 +20,7 @@
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
//#define LOG_NDEBUG 0
+#include <cutils/atomic.h>
#include <gui/BLASTBufferQueue.h>
#include <gui/BufferItemConsumer.h>
#include <gui/BufferQueueConsumer.h>
@@ -35,6 +36,7 @@
#include <private/gui/ComposerService.h>
#include <private/gui/ComposerServiceAIDL.h>
+#include <android-base/thread_annotations.h>
#include <chrono>
using namespace std::chrono_literals;
@@ -63,6 +65,10 @@
ATRACE_FORMAT("%s - %s(f:%u,a:%u)" x, __FUNCTION__, mName.c_str(), mNumFrameAvailable, \
mNumAcquired, ##__VA_ARGS__)
+#define UNIQUE_LOCK_WITH_ASSERTION(mutex) \
+ std::unique_lock _lock{mutex}; \
+ base::ScopedLockAssertion assumeLocked(mutex);
+
void BLASTBufferItemConsumer::onDisconnect() {
Mutex::Autolock lock(mMutex);
mPreviouslyConnected = mCurrentlyConnected;
@@ -152,11 +158,11 @@
GraphicBuffer::USAGE_HW_COMPOSER |
GraphicBuffer::USAGE_HW_TEXTURE,
1, false, this);
- static int32_t id = 0;
- mName = name + "#" + std::to_string(id);
- auto consumerName = mName + "(BLAST Consumer)" + std::to_string(id);
- mQueuedBufferTrace = "QueuedBuffer - " + mName + "BLAST#" + std::to_string(id);
- id++;
+ static std::atomic<uint32_t> nextId = 0;
+ mProducerId = nextId++;
+ mName = name + "#" + std::to_string(mProducerId);
+ auto consumerName = mName + "(BLAST Consumer)" + std::to_string(mProducerId);
+ mQueuedBufferTrace = "QueuedBuffer - " + mName + "BLAST#" + std::to_string(mProducerId);
mBufferItemConsumer->setName(String8(consumerName.c_str()));
mBufferItemConsumer->setFrameAvailableListener(this);
@@ -207,7 +213,7 @@
int32_t format) {
LOG_ALWAYS_FATAL_IF(surface == nullptr, "BLASTBufferQueue: mSurfaceControl must not be NULL");
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
if (mFormat != format) {
mFormat = format;
mBufferItemConsumer->setDefaultBufferFormat(convertBufferFormat(format));
@@ -277,7 +283,7 @@
const sp<Fence>& /*presentFence*/,
const std::vector<SurfaceControlStats>& stats) {
{
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
BBQ_TRACE();
BQA_LOGV("transactionCommittedCallback");
if (!mSurfaceControlsWithPendingCallback.empty()) {
@@ -325,7 +331,7 @@
void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp<Fence>& /*presentFence*/,
const std::vector<SurfaceControlStats>& stats) {
{
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
BBQ_TRACE();
BQA_LOGV("transactionCallback");
@@ -406,9 +412,8 @@
void BLASTBufferQueue::releaseBufferCallback(
const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
std::optional<uint32_t> currentMaxAcquiredBufferCount) {
+ std::lock_guard _lock{mMutex};
BBQ_TRACE();
-
- std::unique_lock _lock{mMutex};
releaseBufferCallbackLocked(id, releaseFence, currentMaxAcquiredBufferCount,
false /* fakeRelease */);
}
@@ -423,17 +428,15 @@
// to the buffer queue. This will prevent higher latency when we are running
// on a lower refresh rate than the max supported. We only do that for EGL
// clients as others don't care about latency
- const bool isEGL = [&] {
- const auto it = mSubmitted.find(id);
- return it != mSubmitted.end() && it->second.mApi == NATIVE_WINDOW_API_EGL;
- }();
+ const auto it = mSubmitted.find(id);
+ const bool isEGL = it != mSubmitted.end() && it->second.mApi == NATIVE_WINDOW_API_EGL;
if (currentMaxAcquiredBufferCount) {
mCurrentMaxAcquiredBufferCount = *currentMaxAcquiredBufferCount;
}
- const auto numPendingBuffersToHold =
- isEGL ? std::max(0u, mMaxAcquiredBuffers - mCurrentMaxAcquiredBufferCount) : 0;
+ const uint32_t numPendingBuffersToHold =
+ isEGL ? std::max(0, mMaxAcquiredBuffers - (int32_t)mCurrentMaxAcquiredBufferCount) : 0;
auto rb = ReleasedBuffer{id, releaseFence};
if (std::find(mPendingRelease.begin(), mPendingRelease.end(), rb) == mPendingRelease.end()) {
@@ -485,11 +488,19 @@
status_t BLASTBufferQueue::acquireNextBufferLocked(
const std::optional<SurfaceComposerClient::Transaction*> transaction) {
- // If the next transaction is set, we want to guarantee the our acquire will not fail, so don't
- // include the extra buffer when checking if we can acquire the next buffer.
+ // Check if we have frames available and we have not acquired the maximum number of buffers.
+ // Even with this check, the consumer can fail to acquire an additional buffer if the consumer
+ // has already acquired (mMaxAcquiredBuffers + 1) and the new buffer is not droppable. In this
+ // case mBufferItemConsumer->acquireBuffer will return with NO_BUFFER_AVAILABLE.
if (mNumFrameAvailable == 0) {
- BQA_LOGV("Can't process next buffer. No available frames");
- return NOT_ENOUGH_DATA;
+ BQA_LOGV("Can't acquire next buffer. No available frames");
+ return BufferQueue::NO_BUFFER_AVAILABLE;
+ }
+
+ if (mNumAcquired >= (mMaxAcquiredBuffers + 2)) {
+ BQA_LOGV("Can't acquire next buffer. Already acquired max frames %d max:%d + 2",
+ mNumAcquired, mMaxAcquiredBuffers);
+ return BufferQueue::NO_BUFFER_AVAILABLE;
}
if (mSurfaceControl == nullptr) {
@@ -562,7 +573,8 @@
std::bind(releaseBufferCallbackThunk, wp<BLASTBufferQueue>(this) /* callbackContext */,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
sp<Fence> fence = bufferItem.mFence ? new Fence(bufferItem.mFence->dup()) : Fence::NO_FENCE;
- t->setBuffer(mSurfaceControl, buffer, fence, bufferItem.mFrameNumber, releaseBufferCallback);
+ t->setBuffer(mSurfaceControl, buffer, fence, bufferItem.mFrameNumber, mProducerId,
+ releaseBufferCallback);
t->setDataspace(mSurfaceControl, static_cast<ui::Dataspace>(bufferItem.mDataSpace));
t->setHdrMetadata(mSurfaceControl, bufferItem.mHdrMetadata);
t->setSurfaceDamageRegion(mSurfaceControl, bufferItem.mSurfaceDamage);
@@ -607,7 +619,7 @@
}
{
- std::unique_lock _lock{mTimestampMutex};
+ std::lock_guard _lock{mTimestampMutex};
auto dequeueTime = mDequeueTimestamps.find(buffer->getId());
if (dequeueTime != mDequeueTimestamps.end()) {
Parcel p;
@@ -662,11 +674,11 @@
void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) {
std::function<void(SurfaceComposerClient::Transaction*)> prevCallback = nullptr;
SurfaceComposerClient::Transaction* prevTransaction = nullptr;
- bool waitForTransactionCallback = !mSyncedFrameNumbers.empty();
{
- std::unique_lock _lock{mMutex};
+ UNIQUE_LOCK_WITH_ASSERTION(mMutex);
BBQ_TRACE();
+ bool waitForTransactionCallback = !mSyncedFrameNumbers.empty();
const bool syncTransactionSet = mTransactionReadyCallback != nullptr;
BQA_LOGV("onFrameAvailable-start syncTransactionSet=%s", boolToString(syncTransactionSet));
@@ -696,6 +708,15 @@
// flush out the shadow queue
acquireAndReleaseBuffer();
}
+ } else {
+ // Make sure the frame available count is 0 before proceeding with a sync to ensure
+ // the correct frame is used for the sync. The only way mNumFrameAvailable would be
+ // greater than 0 is if we already ran out of buffers previously. This means we
+ // need to flush the buffers before proceeding with the sync.
+ while (mNumFrameAvailable > 0) {
+ BQA_LOGD("waiting until no queued buffers");
+ mCallbackCV.wait(_lock);
+ }
}
}
@@ -711,6 +732,11 @@
item.mFrameNumber, boolToString(syncTransactionSet));
if (syncTransactionSet) {
+ // Add to mSyncedFrameNumbers before waiting in case any buffers are released
+ // while waiting for a free buffer. The release and commit callback will try to
+ // acquire buffers if there are any available, but we don't want it to acquire
+ // in the case where a sync transaction wants the buffer.
+ mSyncedFrameNumbers.emplace(item.mFrameNumber);
// If there's no available buffer and we're in a sync transaction, we need to wait
// instead of returning since we guarantee a buffer will be acquired for the sync.
while (acquireNextBufferLocked(mSyncTransaction) == BufferQueue::NO_BUFFER_AVAILABLE) {
@@ -723,7 +749,6 @@
incStrong((void*)transactionCommittedCallbackThunk);
mSyncTransaction->addTransactionCommittedCallback(transactionCommittedCallbackThunk,
static_cast<void*>(this));
- mSyncedFrameNumbers.emplace(item.mFrameNumber);
if (mAcquireSingleBuffer) {
prevCallback = mTransactionReadyCallback;
prevTransaction = mSyncTransaction;
@@ -745,25 +770,24 @@
}
void BLASTBufferQueue::onFrameDequeued(const uint64_t bufferId) {
- std::unique_lock _lock{mTimestampMutex};
+ std::lock_guard _lock{mTimestampMutex};
mDequeueTimestamps[bufferId] = systemTime();
};
void BLASTBufferQueue::onFrameCancelled(const uint64_t bufferId) {
- std::unique_lock _lock{mTimestampMutex};
+ std::lock_guard _lock{mTimestampMutex};
mDequeueTimestamps.erase(bufferId);
};
void BLASTBufferQueue::syncNextTransaction(
std::function<void(SurfaceComposerClient::Transaction*)> callback,
bool acquireSingleBuffer) {
- BBQ_TRACE();
-
std::function<void(SurfaceComposerClient::Transaction*)> prevCallback = nullptr;
SurfaceComposerClient::Transaction* prevTransaction = nullptr;
{
std::lock_guard _lock{mMutex};
+ BBQ_TRACE();
// We're about to overwrite the previous call so we should invoke that callback
// immediately.
if (mTransactionReadyCallback) {
@@ -829,8 +853,8 @@
class BBQSurface : public Surface {
private:
std::mutex mMutex;
- sp<BLASTBufferQueue> mBbq;
- bool mDestroyed = false;
+ sp<BLASTBufferQueue> mBbq GUARDED_BY(mMutex);
+ bool mDestroyed GUARDED_BY(mMutex) = false;
public:
BBQSurface(const sp<IGraphicBufferProducer>& igbp, bool controlledByApp,
@@ -851,7 +875,7 @@
status_t setFrameRate(float frameRate, int8_t compatibility,
int8_t changeFrameRateStrategy) override {
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
if (mDestroyed) {
return DEAD_OBJECT;
}
@@ -864,7 +888,7 @@
status_t setFrameTimelineInfo(uint64_t frameNumber,
const FrameTimelineInfo& frameTimelineInfo) override {
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
if (mDestroyed) {
return DEAD_OBJECT;
}
@@ -874,7 +898,7 @@
void destroy() override {
Surface::destroy();
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
mDestroyed = true;
mBbq = nullptr;
}
@@ -884,7 +908,7 @@
// no timing issues.
status_t BLASTBufferQueue::setFrameRate(float frameRate, int8_t compatibility,
bool shouldBeSeamless) {
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
SurfaceComposerClient::Transaction t;
return t.setFrameRate(mSurfaceControl, frameRate, compatibility, shouldBeSeamless).apply();
@@ -894,20 +918,20 @@
const FrameTimelineInfo& frameTimelineInfo) {
ATRACE_FORMAT("%s(%s) frameNumber: %" PRIu64 " vsyncId: %" PRId64, __func__, mName.c_str(),
frameNumber, frameTimelineInfo.vsyncId);
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
mPendingFrameTimelines.push({frameNumber, frameTimelineInfo});
return OK;
}
void BLASTBufferQueue::setSidebandStream(const sp<NativeHandle>& stream) {
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
SurfaceComposerClient::Transaction t;
t.setSidebandStream(mSurfaceControl, stream).apply();
}
sp<Surface> BLASTBufferQueue::getSurface(bool includeSurfaceControlHandle) {
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
sp<IBinder> scHandle = nullptr;
if (includeSurfaceControlHandle && mSurfaceControl) {
scHandle = mSurfaceControl->getHandle();
@@ -1098,6 +1122,7 @@
}
uint32_t BLASTBufferQueue::getLastTransformHint() const {
+ std::lock_guard _lock{mMutex};
if (mSurfaceControl != nullptr) {
return mSurfaceControl->getTransformHint();
} else {
@@ -1106,18 +1131,18 @@
}
uint64_t BLASTBufferQueue::getLastAcquiredFrameNum() {
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
return mLastAcquiredFrameNumber;
}
bool BLASTBufferQueue::isSameSurfaceControl(const sp<SurfaceControl>& surfaceControl) const {
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
return SurfaceControl::isSameSurface(mSurfaceControl, surfaceControl);
}
void BLASTBufferQueue::setTransactionHangCallback(
std::function<void(const std::string&)> callback) {
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
mTransactionHangCallback = callback;
}
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index 6b25b26..99bf6ba 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -101,8 +101,9 @@
return gChoreographer;
}
-Choreographer::Choreographer(const sp<Looper>& looper)
- : DisplayEventDispatcher(looper, gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp),
+Choreographer::Choreographer(const sp<Looper>& looper, const sp<IBinder>& layerHandle)
+ : DisplayEventDispatcher(looper, gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp, {},
+ layerHandle),
mLooper(looper),
mThreadId(std::this_thread::get_id()) {
std::lock_guard<std::mutex> _l(gChoreographers.lock);
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
index 501e69a..8a88377 100644
--- a/libs/gui/DisplayEventDispatcher.cpp
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -37,9 +37,10 @@
DisplayEventDispatcher::DisplayEventDispatcher(const sp<Looper>& looper,
gui::ISurfaceComposer::VsyncSource vsyncSource,
- EventRegistrationFlags eventRegistration)
+ EventRegistrationFlags eventRegistration,
+ const sp<IBinder>& layerHandle)
: mLooper(looper),
- mReceiver(vsyncSource, eventRegistration),
+ mReceiver(vsyncSource, eventRegistration, layerHandle),
mWaitingForVsync(false),
mLastVsyncCount(0),
mLastScheduleVsyncTime(0) {
diff --git a/libs/gui/DisplayEventReceiver.cpp b/libs/gui/DisplayEventReceiver.cpp
index c52fb6b..6849a95 100644
--- a/libs/gui/DisplayEventReceiver.cpp
+++ b/libs/gui/DisplayEventReceiver.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#define LOG_TAG "DisplayEventReceiver"
+
#include <string.h>
#include <utils/Errors.h>
@@ -32,7 +34,8 @@
// ---------------------------------------------------------------------------
DisplayEventReceiver::DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource vsyncSource,
- EventRegistrationFlags eventRegistration) {
+ EventRegistrationFlags eventRegistration,
+ const sp<IBinder>& layerHandle) {
sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
if (sf != nullptr) {
mEventConnection = nullptr;
@@ -41,8 +44,8 @@
static_cast<
gui::ISurfaceComposer::EventRegistration>(
eventRegistration.get()),
- &mEventConnection);
- if (mEventConnection != nullptr) {
+ layerHandle, &mEventConnection);
+ if (status.isOk() && mEventConnection != nullptr) {
mDataChannel = std::make_unique<gui::BitTube>();
status = mEventConnection->stealReceiveChannel(mDataChannel.get());
if (!status.isOk()) {
@@ -51,6 +54,8 @@
mDataChannel.reset();
mEventConnection.clear();
}
+ } else {
+ ALOGE("DisplayEventConnection creation failed: status=%s", status.toString8().c_str());
}
}
}
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index cefb9a7..a77ca04 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -63,8 +63,7 @@
Vector<ComposerState>& state, const Vector<DisplayState>& displays,
uint32_t flags, const sp<IBinder>& applyToken,
const InputWindowCommands& commands, int64_t desiredPresentTime,
- bool isAutoTimestamp,
- const std::vector<client_cache_t>& uncacheBuffers,
+ bool isAutoTimestamp, const client_cache_t& uncacheBuffer,
bool hasListenerCallbacks,
const std::vector<ListenerCallbacks>& listenerCallbacks,
uint64_t transactionId) override {
@@ -88,11 +87,8 @@
SAFE_PARCEL(commands.write, data);
SAFE_PARCEL(data.writeInt64, desiredPresentTime);
SAFE_PARCEL(data.writeBool, isAutoTimestamp);
- SAFE_PARCEL(data.writeUint32, static_cast<uint32_t>(uncacheBuffers.size()));
- for (const client_cache_t& uncacheBuffer : uncacheBuffers) {
- SAFE_PARCEL(data.writeStrongBinder, uncacheBuffer.token.promote());
- SAFE_PARCEL(data.writeUint64, uncacheBuffer.id);
- }
+ SAFE_PARCEL(data.writeStrongBinder, uncacheBuffer.token.promote());
+ SAFE_PARCEL(data.writeUint64, uncacheBuffer.id);
SAFE_PARCEL(data.writeBool, hasListenerCallbacks);
SAFE_PARCEL(data.writeVectorSize, listenerCallbacks);
@@ -162,14 +158,11 @@
SAFE_PARCEL(data.readInt64, &desiredPresentTime);
SAFE_PARCEL(data.readBool, &isAutoTimestamp);
- SAFE_PARCEL_READ_SIZE(data.readUint32, &count, data.dataSize());
- std::vector<client_cache_t> uncacheBuffers(count);
+ client_cache_t uncachedBuffer;
sp<IBinder> tmpBinder;
- for (size_t i = 0; i < count; i++) {
- SAFE_PARCEL(data.readNullableStrongBinder, &tmpBinder);
- uncacheBuffers[i].token = tmpBinder;
- SAFE_PARCEL(data.readUint64, &uncacheBuffers[i].id);
- }
+ SAFE_PARCEL(data.readNullableStrongBinder, &tmpBinder);
+ uncachedBuffer.token = tmpBinder;
+ SAFE_PARCEL(data.readUint64, &uncachedBuffer.id);
bool hasListenerCallbacks = false;
SAFE_PARCEL(data.readBool, &hasListenerCallbacks);
@@ -189,7 +182,7 @@
return setTransactionState(frameTimelineInfo, state, displays, stateFlags, applyToken,
inputWindowCommands, desiredPresentTime, isAutoTimestamp,
- uncacheBuffers, hasListenerCallbacks, listenerCallbacks,
+ uncachedBuffer, hasListenerCallbacks, listenerCallbacks,
transactionId);
}
default: {
diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp
index 985c549..ffe79a3 100644
--- a/libs/gui/ITransactionCompletedListener.cpp
+++ b/libs/gui/ITransactionCompletedListener.cpp
@@ -39,6 +39,12 @@
} // Anonymous namespace
+namespace { // Anonymous
+
+constexpr int32_t kSerializedCallbackTypeOnCompelteWithJankData = 2;
+
+} // Anonymous namespace
+
status_t FrameEventHistoryStats::writeToParcel(Parcel* output) const {
status_t err = output->writeUint64(frameNumber);
if (err != NO_ERROR) return err;
@@ -349,7 +355,11 @@
status_t CallbackId::writeToParcel(Parcel* output) const {
SAFE_PARCEL(output->writeInt64, id);
- SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(type));
+ if (type == Type::ON_COMPLETE && includeJankData) {
+ SAFE_PARCEL(output->writeInt32, kSerializedCallbackTypeOnCompelteWithJankData);
+ } else {
+ SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(type));
+ }
return NO_ERROR;
}
@@ -357,7 +367,13 @@
SAFE_PARCEL(input->readInt64, &id);
int32_t typeAsInt;
SAFE_PARCEL(input->readInt32, &typeAsInt);
- type = static_cast<CallbackId::Type>(typeAsInt);
+ if (typeAsInt == kSerializedCallbackTypeOnCompelteWithJankData) {
+ type = Type::ON_COMPLETE;
+ includeJankData = true;
+ } else {
+ type = static_cast<CallbackId::Type>(typeAsInt);
+ includeJankData = false;
+ }
return NO_ERROR;
}
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 43acb16..a6276e5 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -187,6 +187,8 @@
}
SAFE_PARCEL(output.writeParcelable, trustedPresentationThresholds);
SAFE_PARCEL(output.writeParcelable, trustedPresentationListener);
+ SAFE_PARCEL(output.writeFloat, currentSdrHdrRatio);
+ SAFE_PARCEL(output.writeFloat, desiredSdrHdrRatio);
return NO_ERROR;
}
@@ -321,6 +323,11 @@
SAFE_PARCEL(input.readParcelable, &trustedPresentationThresholds);
SAFE_PARCEL(input.readParcelable, &trustedPresentationListener);
+ SAFE_PARCEL(input.readFloat, &tmpFloat);
+ currentSdrHdrRatio = tmpFloat;
+ SAFE_PARCEL(input.readFloat, &tmpFloat);
+ desiredSdrHdrRatio = tmpFloat;
+
return NO_ERROR;
}
@@ -568,6 +575,11 @@
what |= eDataspaceChanged;
dataspace = other.dataspace;
}
+ if (other.what & eExtendedRangeBrightnessChanged) {
+ what |= eExtendedRangeBrightnessChanged;
+ desiredSdrHdrRatio = other.desiredSdrHdrRatio;
+ currentSdrHdrRatio = other.currentSdrHdrRatio;
+ }
if (other.what & eHdrMetadataChanged) {
what |= eHdrMetadataChanged;
hdrMetadata = other.hdrMetadata;
@@ -672,6 +684,9 @@
what |= eDimmingEnabledChanged;
dimmingEnabled = other.dimmingEnabled;
}
+ if (other.what & eFlushJankData) {
+ what |= eFlushJankData;
+ }
if ((other.what & what) != other.what) {
ALOGE("Unmerged SurfaceComposer Transaction properties. LayerState::merge needs updating? "
"other.what=0x%" PRIX64 " what=0x%" PRIX64 " unmerged flags=0x%" PRIX64,
@@ -714,6 +729,8 @@
CHECK_DIFF(diff, eCropChanged, other, crop);
if (other.what & eBufferChanged) diff |= eBufferChanged;
CHECK_DIFF(diff, eDataspaceChanged, other, dataspace);
+ CHECK_DIFF2(diff, eExtendedRangeBrightnessChanged, other, currentSdrHdrRatio,
+ desiredSdrHdrRatio);
CHECK_DIFF(diff, eHdrMetadataChanged, other, hdrMetadata);
if (other.what & eSurfaceDamageRegionChanged &&
(!surfaceDamageRegion.hasSameRects(other.surfaceDamageRegion))) {
@@ -967,6 +984,7 @@
SAFE_PARCEL(output->writeUint64, cachedBuffer.id);
SAFE_PARCEL(output->writeBool, hasBarrier);
SAFE_PARCEL(output->writeUint64, barrierFrameNumber);
+ SAFE_PARCEL(output->writeUint32, producerId);
return NO_ERROR;
}
@@ -1005,6 +1023,7 @@
SAFE_PARCEL(input->readBool, &hasBarrier);
SAFE_PARCEL(input->readUint64, &barrierFrameNumber);
+ SAFE_PARCEL(input->readUint32, &producerId);
return NO_ERROR;
}
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 47339fe..f51c402 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -53,6 +53,7 @@
#include <ui/DisplayState.h>
#include <ui/DynamicDisplayInfo.h>
+#include <android-base/thread_annotations.h>
#include <private/gui/ComposerService.h>
#include <private/gui/ComposerServiceAIDL.h>
@@ -81,6 +82,8 @@
int64_t generateId() {
return (((int64_t)getpid()) << 32) | ++idCounter;
}
+
+void emptyCallback(nsecs_t, const sp<Fence>&, const std::vector<SurfaceControlStats>&) {}
} // namespace
ComposerService::ComposerService()
@@ -248,6 +251,14 @@
surfaceControls,
CallbackId::Type callbackType) {
std::lock_guard<std::mutex> lock(mMutex);
+ return addCallbackFunctionLocked(callbackFunction, surfaceControls, callbackType);
+}
+
+CallbackId TransactionCompletedListener::addCallbackFunctionLocked(
+ const TransactionCompletedCallback& callbackFunction,
+ const std::unordered_set<sp<SurfaceControl>, SurfaceComposerClient::SCHash>&
+ surfaceControls,
+ CallbackId::Type callbackType) {
startListeningLocked();
CallbackId callbackId(getNextIdLocked(), callbackType);
@@ -256,6 +267,11 @@
for (const auto& surfaceControl : surfaceControls) {
callbackSurfaceControls[surfaceControl->getHandle()] = surfaceControl;
+
+ if (callbackType == CallbackId::Type::ON_COMPLETE &&
+ mJankListeners.count(surfaceControl->getLayerId()) != 0) {
+ callbackId.includeJankData = true;
+ }
}
return callbackId;
@@ -304,15 +320,26 @@
}
void TransactionCompletedListener::addSurfaceControlToCallbacks(
- const sp<SurfaceControl>& surfaceControl,
- const std::unordered_set<CallbackId, CallbackIdHash>& callbackIds) {
+ SurfaceComposerClient::CallbackInfo& callbackInfo,
+ const sp<SurfaceControl>& surfaceControl) {
std::lock_guard<std::mutex> lock(mMutex);
- for (auto callbackId : callbackIds) {
+ bool includingJankData = false;
+ for (auto callbackId : callbackInfo.callbackIds) {
mCallbacks[callbackId].surfaceControls.emplace(std::piecewise_construct,
std::forward_as_tuple(
surfaceControl->getHandle()),
std::forward_as_tuple(surfaceControl));
+ includingJankData = includingJankData || callbackId.includeJankData;
+ }
+
+ // If no registered callback is requesting jank data, but there is a jank listener registered
+ // on the new surface control, add a synthetic callback that requests the jank data.
+ if (!includingJankData && mJankListeners.count(surfaceControl->getLayerId()) != 0) {
+ CallbackId callbackId =
+ addCallbackFunctionLocked(&emptyCallback, callbackInfo.surfaceControls,
+ CallbackId::Type::ON_COMPLETE);
+ callbackInfo.callbackIds.emplace(callbackId);
}
}
@@ -606,13 +633,11 @@
return NO_ERROR;
}
- uint64_t cache(const sp<GraphicBuffer>& buffer,
- std::optional<client_cache_t>& outUncacheBuffer) {
+ uint64_t cache(const sp<GraphicBuffer>& buffer) {
std::lock_guard<std::mutex> lock(mMutex);
if (mBuffers.size() >= BUFFER_CACHE_MAX_SIZE) {
- outUncacheBuffer = findLeastRecentlyUsedBuffer();
- mBuffers.erase(outUncacheBuffer->id);
+ evictLeastRecentlyUsedBuffer();
}
buffer->addDeathCallback(removeDeadBufferCallback, nullptr);
@@ -623,13 +648,16 @@
void uncache(uint64_t cacheId) {
std::lock_guard<std::mutex> lock(mMutex);
- if (mBuffers.erase(cacheId)) {
- SurfaceComposerClient::doUncacheBufferTransaction(cacheId);
- }
+ uncacheLocked(cacheId);
+ }
+
+ void uncacheLocked(uint64_t cacheId) REQUIRES(mMutex) {
+ mBuffers.erase(cacheId);
+ SurfaceComposerClient::doUncacheBufferTransaction(cacheId);
}
private:
- client_cache_t findLeastRecentlyUsedBuffer() REQUIRES(mMutex) {
+ void evictLeastRecentlyUsedBuffer() REQUIRES(mMutex) {
auto itr = mBuffers.begin();
uint64_t minCounter = itr->second;
auto minBuffer = itr;
@@ -643,8 +671,7 @@
}
itr++;
}
-
- return {.token = getToken(), .id = minBuffer->first};
+ uncacheLocked(minBuffer->first);
}
uint64_t getCounter() REQUIRES(mMutex) {
@@ -782,18 +809,6 @@
InputWindowCommands inputWindowCommands;
inputWindowCommands.read(*parcel);
- count = static_cast<size_t>(parcel->readUint32());
- if (count > parcel->dataSize()) {
- return BAD_VALUE;
- }
- std::vector<client_cache_t> uncacheBuffers(count);
- for (size_t i = 0; i < count; i++) {
- sp<IBinder> tmpBinder;
- SAFE_PARCEL(parcel->readStrongBinder, &tmpBinder);
- uncacheBuffers[i].token = tmpBinder;
- SAFE_PARCEL(parcel->readUint64, &uncacheBuffers[i].id);
- }
-
// Parsing was successful. Update the object.
mId = transactionId;
mTransactionNestCount = transactionNestCount;
@@ -808,7 +823,6 @@
mComposerStates = composerStates;
mInputWindowCommands = inputWindowCommands;
mApplyToken = applyToken;
- mUncacheBuffers = std::move(uncacheBuffers);
return NO_ERROR;
}
@@ -860,13 +874,6 @@
}
mInputWindowCommands.write(*parcel);
-
- SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(mUncacheBuffers.size()));
- for (const client_cache_t& uncacheBuffer : mUncacheBuffers) {
- SAFE_PARCEL(parcel->writeStrongBinder, uncacheBuffer.token.promote());
- SAFE_PARCEL(parcel->writeUint64, uncacheBuffer.id);
- }
-
return NO_ERROR;
}
@@ -929,15 +936,10 @@
// register all surface controls for all callbackIds for this listener that is merging
for (const auto& surfaceControl : currentProcessCallbackInfo.surfaceControls) {
TransactionCompletedListener::getInstance()
- ->addSurfaceControlToCallbacks(surfaceControl,
- currentProcessCallbackInfo.callbackIds);
+ ->addSurfaceControlToCallbacks(currentProcessCallbackInfo, surfaceControl);
}
}
- for (const auto& cacheId : other.mUncacheBuffers) {
- mUncacheBuffers.push_back(cacheId);
- }
-
mInputWindowCommands.merge(other.mInputWindowCommands);
mMayContainBuffer |= other.mMayContainBuffer;
@@ -956,7 +958,6 @@
mDisplayStates.clear();
mListenerCallbacks.clear();
mInputWindowCommands.clear();
- mUncacheBuffers.clear();
mMayContainBuffer = false;
mTransactionNestCount = 0;
mAnimation = false;
@@ -979,10 +980,10 @@
uncacheBuffer.token = BufferCache::getInstance().getToken();
uncacheBuffer.id = cacheId;
Vector<ComposerState> composerStates;
- status_t status = sf->setTransactionState(FrameTimelineInfo{}, composerStates, {},
- ISurfaceComposer::eOneWay,
- Transaction::getDefaultApplyToken(), {}, systemTime(),
- true, {uncacheBuffer}, false, {}, generateId());
+ status_t status =
+ sf->setTransactionState(FrameTimelineInfo{}, composerStates, {},
+ ISurfaceComposer::eOneWay, Transaction::getDefaultApplyToken(),
+ {}, systemTime(), true, uncacheBuffer, false, {}, generateId());
if (status != NO_ERROR) {
ALOGE_AND_TRACE("SurfaceComposerClient::doUncacheBufferTransaction - %s",
strerror(-status));
@@ -1020,11 +1021,7 @@
s->bufferData->buffer = nullptr;
} else {
// Cache-miss. Include the buffer and send the new cacheId.
- std::optional<client_cache_t> uncacheBuffer;
- cacheId = BufferCache::getInstance().cache(s->bufferData->buffer, uncacheBuffer);
- if (uncacheBuffer) {
- mUncacheBuffers.push_back(*uncacheBuffer);
- }
+ cacheId = BufferCache::getInstance().cache(s->bufferData->buffer);
}
s->bufferData->flags |= BufferData::BufferDataChange::cachedBufferChanged;
s->bufferData->cachedBuffer.token = BufferCache::getInstance().getToken();
@@ -1157,7 +1154,8 @@
sp<ISurfaceComposer> sf(ComposerService::getComposerService());
sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken,
mInputWindowCommands, mDesiredPresentTime, mIsAutoTimestamp,
- mUncacheBuffers, hasListenerCallbacks, listenerCallbacks, mId);
+ {} /*uncacheBuffer - only set in doUncacheBufferTransaction*/,
+ hasListenerCallbacks, listenerCallbacks, mId);
mId = generateId();
// Clear the current states and flags
@@ -1180,14 +1178,29 @@
void SurfaceComposerClient::Transaction::setDefaultApplyToken(sp<IBinder> applyToken) {
sApplyToken = applyToken;
}
+
+status_t SurfaceComposerClient::Transaction::sendSurfaceFlushJankDataTransaction(
+ const sp<SurfaceControl>& sc) {
+ Transaction t;
+ layer_state_t* s = t.getLayerState(sc);
+ if (!s) {
+ return BAD_INDEX;
+ }
+
+ s->what |= layer_state_t::eFlushJankData;
+ t.registerSurfaceControlForCallback(sc);
+ return t.apply(/*sync=*/false, /* oneWay=*/true);
+}
// ---------------------------------------------------------------------------
-sp<IBinder> SurfaceComposerClient::createDisplay(const String8& displayName, bool secure) {
+sp<IBinder> SurfaceComposerClient::createDisplay(const String8& displayName, bool secure,
+ float requestedRefereshRate) {
sp<IBinder> display = nullptr;
binder::Status status =
ComposerServiceAIDL::getComposerService()->createDisplay(std::string(
displayName.string()),
- secure, &display);
+ secure, requestedRefereshRate,
+ &display);
return status.isOk() ? display : nullptr;
}
@@ -1251,8 +1264,7 @@
auto& callbackInfo = mListenerCallbacks[TransactionCompletedListener::getIInstance()];
callbackInfo.surfaceControls.insert(sc);
- TransactionCompletedListener::getInstance()
- ->addSurfaceControlToCallbacks(sc, callbackInfo.callbackIds);
+ TransactionCompletedListener::getInstance()->addSurfaceControlToCallbacks(callbackInfo, sc);
}
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setPosition(
@@ -1592,7 +1604,7 @@
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffer(
const sp<SurfaceControl>& sc, const sp<GraphicBuffer>& buffer,
const std::optional<sp<Fence>>& fence, const std::optional<uint64_t>& optFrameNumber,
- ReleaseBufferCallback callback) {
+ uint32_t producerId, ReleaseBufferCallback callback) {
layer_state_t* s = getLayerState(sc);
if (!s) {
mStatus = BAD_INDEX;
@@ -1611,6 +1623,7 @@
bufferData->buffer = buffer;
uint64_t frameNumber = sc->resolveFrameNumber(optFrameNumber);
bufferData->frameNumber = frameNumber;
+ bufferData->producerId = producerId;
bufferData->flags |= BufferData::BufferDataChange::frameNumberChanged;
if (fence) {
bufferData->acquireFence = *fence;
@@ -1673,6 +1686,21 @@
return *this;
}
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setExtendedRangeBrightness(
+ const sp<SurfaceControl>& sc, float currentBufferRatio, float desiredRatio) {
+ layer_state_t* s = getLayerState(sc);
+ if (!s) {
+ mStatus = BAD_INDEX;
+ return *this;
+ }
+ s->what |= layer_state_t::eExtendedRangeBrightnessChanged;
+ s->currentSdrHdrRatio = currentBufferRatio;
+ s->desiredSdrHdrRatio = desiredRatio;
+
+ registerSurfaceControlForCallback(sc);
+ return *this;
+}
+
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setHdrMetadata(
const sp<SurfaceControl>& sc, const HdrMetadata& hdrMetadata) {
layer_state_t* s = getLayerState(sc);
@@ -2977,6 +3005,7 @@
while (true) {
{
std::unique_lock<std::mutex> lock(mMutex);
+ base::ScopedLockAssertion assumeLocked(mMutex);
callbackInfos = std::move(mCallbackInfos);
mCallbackInfos = {};
}
@@ -2989,6 +3018,7 @@
{
std::unique_lock<std::mutex> lock(mMutex);
+ base::ScopedLockAssertion assumeLocked(mMutex);
if (mCallbackInfos.size() == 0) {
mReleaseCallbackPending.wait(lock);
}
diff --git a/libs/gui/SurfaceControl.cpp b/libs/gui/SurfaceControl.cpp
index 7aee882..c5f9c38 100644
--- a/libs/gui/SurfaceControl.cpp
+++ b/libs/gui/SurfaceControl.cpp
@@ -26,6 +26,7 @@
#include <utils/Errors.h>
#include <utils/KeyedVector.h>
#include <utils/Log.h>
+#include <utils/Looper.h>
#include <utils/threads.h>
#include <binder/IPCThreadState.h>
@@ -34,8 +35,9 @@
#include <ui/Rect.h>
#include <ui/StaticDisplayInfo.h>
-#include <gui/BufferQueueCore.h>
#include <gui/BLASTBufferQueue.h>
+#include <gui/BufferQueueCore.h>
+#include <gui/Choreographer.h>
#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
@@ -191,6 +193,24 @@
return mName;
}
+std::shared_ptr<Choreographer> SurfaceControl::getChoreographer() {
+ if (mChoreographer) {
+ return mChoreographer;
+ }
+ sp<Looper> looper = Looper::getForThread();
+ if (!looper.get()) {
+ ALOGE("%s: No looper prepared for thread", __func__);
+ return nullptr;
+ }
+ mChoreographer = std::make_shared<Choreographer>(looper, getHandle());
+ status_t result = mChoreographer->initialize();
+ if (result != OK) {
+ ALOGE("Failed to initialize choreographer");
+ mChoreographer = nullptr;
+ }
+ return mChoreographer;
+}
+
sp<IGraphicBufferProducer> SurfaceControl::getIGraphicBufferProducer()
{
getSurface();
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index c08a7c6..9812142 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -68,9 +68,15 @@
/**
* Create a display event connection
+ *
+ * layerHandle
+ * Optional binder handle representing a Layer in SF to associate the new
+ * DisplayEventConnection with. This handle can be found inside a surface control after
+ * surface creation, see ISurfaceComposerClient::createSurface. Set to null if no layer
+ * association should be made.
*/
@nullable IDisplayEventConnection createDisplayEventConnection(VsyncSource vsyncSource,
- EventRegistration eventRegistration);
+ EventRegistration eventRegistration, @nullable IBinder layerHandle);
/**
* Create a connection with SurfaceFlinger.
@@ -79,9 +85,21 @@
/**
* Create a virtual display
+ *
+ * displayName
+ * The name of the virtual display
+ * secure
+ * Whether this virtual display is secure
+ * requestedRefreshRate
+ * The refresh rate, frames per second, to request on the virtual display.
+ * This is just a request, the actual rate may be adjusted to align well
+ * with physical displays running concurrently. If 0 is specified, the
+ * virtual display is refreshed at the physical display refresh rate.
+ *
* requires ACCESS_SURFACE_FLINGER permission.
*/
- @nullable IBinder createDisplay(@utf8InCpp String displayName, boolean secure);
+ @nullable IBinder createDisplay(@utf8InCpp String displayName, boolean secure,
+ float requestedRefreshRate);
/**
* Destroy a virtual display
diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h
index 685bd92..f01c2a9 100644
--- a/libs/gui/fuzzer/libgui_fuzzer_utils.h
+++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h
@@ -64,10 +64,10 @@
MOCK_METHOD(binder::Status, bootFinished, (), (override));
MOCK_METHOD(binder::Status, createDisplayEventConnection,
(gui::ISurfaceComposer::VsyncSource, gui::ISurfaceComposer::EventRegistration,
- sp<gui::IDisplayEventConnection>*),
+ const sp<IBinder>& /*layerHandle*/, sp<gui::IDisplayEventConnection>*),
(override));
MOCK_METHOD(binder::Status, createConnection, (sp<gui::ISurfaceComposerClient>*), (override));
- MOCK_METHOD(binder::Status, createDisplay, (const std::string&, bool, sp<IBinder>*),
+ MOCK_METHOD(binder::Status, createDisplay, (const std::string&, bool, float, sp<IBinder>*),
(override));
MOCK_METHOD(binder::Status, destroyDisplay, (const sp<IBinder>&), (override));
MOCK_METHOD(binder::Status, getPhysicalDisplayIds, (std::vector<int64_t>*), (override));
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index c93ab86..b9e0647 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -44,23 +44,23 @@
mCurrentlyConnected(false),
mPreviouslyConnected(false) {}
- void onDisconnect() override;
+ void onDisconnect() override EXCLUDES(mMutex);
void addAndGetFrameTimestamps(const NewFrameEventsEntry* newTimestamps,
- FrameEventHistoryDelta* outDelta) override REQUIRES(mMutex);
+ FrameEventHistoryDelta* outDelta) override EXCLUDES(mMutex);
void updateFrameTimestamps(uint64_t frameNumber, nsecs_t refreshStartTime,
const sp<Fence>& gpuCompositionDoneFence,
const sp<Fence>& presentFence, const sp<Fence>& prevReleaseFence,
CompositorTiming compositorTiming, nsecs_t latchTime,
- nsecs_t dequeueReadyTime) REQUIRES(mMutex);
- void getConnectionEvents(uint64_t frameNumber, bool* needsDisconnect);
+ nsecs_t dequeueReadyTime) EXCLUDES(mMutex);
+ void getConnectionEvents(uint64_t frameNumber, bool* needsDisconnect) EXCLUDES(mMutex);
protected:
- void onSidebandStreamChanged() override REQUIRES(mMutex);
+ void onSidebandStreamChanged() override EXCLUDES(mMutex);
private:
const wp<BLASTBufferQueue> mBLASTBufferQueue;
- uint64_t mCurrentFrameNumber = 0;
+ uint64_t mCurrentFrameNumber GUARDED_BY(mMutex) = 0;
Mutex mMutex;
ConsumerFrameEventHistory mFrameEventHistory GUARDED_BY(mMutex);
@@ -94,7 +94,7 @@
std::optional<uint32_t> currentMaxAcquiredBufferCount);
void releaseBufferCallbackLocked(const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
std::optional<uint32_t> currentMaxAcquiredBufferCount,
- bool fakeRelease);
+ bool fakeRelease) REQUIRES(mMutex);
void syncNextTransaction(std::function<void(SurfaceComposerClient::Transaction*)> callback,
bool acquireSingleBuffer = true);
void stopContinuousSyncTransaction();
@@ -150,7 +150,7 @@
// mNumAcquired (buffers that queued to SF) mPendingRelease.size() (buffers that are held by
// blast). This counter is read by android studio profiler.
std::string mQueuedBufferTrace;
- sp<SurfaceControl> mSurfaceControl;
+ sp<SurfaceControl> mSurfaceControl GUARDED_BY(mMutex);
mutable std::mutex mMutex;
std::condition_variable mCallbackCV;
@@ -162,6 +162,11 @@
int32_t mNumFrameAvailable GUARDED_BY(mMutex) = 0;
int32_t mNumAcquired GUARDED_BY(mMutex) = 0;
+ // A value used to identify if a producer has been changed for the same SurfaceControl.
+ // This is needed to know when the frame number has been reset to make sure we don't
+ // latch stale buffers and that we don't wait on barriers from an old producer.
+ uint32_t mProducerId = 0;
+
// Keep a reference to the submitted buffers so we can release when surfaceflinger drops the
// buffer or the buffer has been presented and a new buffer is ready to be presented.
std::unordered_map<ReleaseCallbackId, BufferItem, ReleaseBufferCallbackIdHash> mSubmitted
@@ -252,7 +257,7 @@
// callback for them.
std::queue<sp<SurfaceControl>> mSurfaceControlsWithPendingCallback GUARDED_BY(mMutex);
- uint32_t mCurrentMaxAcquiredBufferCount;
+ uint32_t mCurrentMaxAcquiredBufferCount GUARDED_BY(mMutex);
// Flag to determine if syncTransaction should only acquire a single buffer and then clear or
// continue to acquire buffers until explicitly cleared
@@ -276,8 +281,8 @@
// need to set this flag, notably only in the case where we are transitioning from a previous
// transaction applied by us (one way, may not yet have reached server) and an upcoming
// transaction that will be applied by some sync consumer.
- bool mAppliedLastTransaction = false;
- uint64_t mLastAppliedFrameNumber = 0;
+ bool mAppliedLastTransaction GUARDED_BY(mMutex) = false;
+ uint64_t mLastAppliedFrameNumber GUARDED_BY(mMutex) = 0;
std::function<void(const std::string&)> mTransactionHangCallback;
diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h
index 89a7058..1df9b11 100644
--- a/libs/gui/include/gui/Choreographer.h
+++ b/libs/gui/include/gui/Choreographer.h
@@ -73,7 +73,8 @@
};
static Context gChoreographers;
- explicit Choreographer(const sp<Looper>& looper) EXCLUDES(gChoreographers.lock);
+ explicit Choreographer(const sp<Looper>& looper, const sp<IBinder>& layerHandle = nullptr)
+ EXCLUDES(gChoreographers.lock);
void postFrameCallbackDelayed(AChoreographer_frameCallback cb,
AChoreographer_frameCallback64 cb64,
AChoreographer_vsyncCallback vsyncCallback, void* data,
diff --git a/libs/gui/include/gui/DisplayCaptureArgs.h b/libs/gui/include/gui/DisplayCaptureArgs.h
index ec884cf..c826c17 100644
--- a/libs/gui/include/gui/DisplayCaptureArgs.h
+++ b/libs/gui/include/gui/DisplayCaptureArgs.h
@@ -24,6 +24,7 @@
#include <binder/Parcelable.h>
#include <ui/GraphicTypes.h>
#include <ui/PixelFormat.h>
+#include <ui/Rect.h>
namespace android::gui {
diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h
index bf3a07b..140efa6 100644
--- a/libs/gui/include/gui/DisplayEventDispatcher.h
+++ b/libs/gui/include/gui/DisplayEventDispatcher.h
@@ -26,7 +26,8 @@
explicit DisplayEventDispatcher(const sp<Looper>& looper,
gui::ISurfaceComposer::VsyncSource vsyncSource =
gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp,
- EventRegistrationFlags eventRegistration = {});
+ EventRegistrationFlags eventRegistration = {},
+ const sp<IBinder>& layerHandle = nullptr);
status_t initialize();
void dispose();
diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h
index 0f4907f..7fd6c35 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -119,7 +119,8 @@
*/
explicit DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource vsyncSource =
gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp,
- EventRegistrationFlags eventRegistration = {});
+ EventRegistrationFlags eventRegistration = {},
+ const sp<IBinder>& layerHandle = nullptr);
/*
* ~DisplayEventReceiver severs the connection with SurfaceFlinger, new events
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index ae56f9f..045cc2a 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -113,9 +113,8 @@
const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
- bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffer,
- bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
- uint64_t transactionId) = 0;
+ bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
+ const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId) = 0;
};
// ----------------------------------------------------------------------------
diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h
index d593f56..39bcb4a 100644
--- a/libs/gui/include/gui/ITransactionCompletedListener.h
+++ b/libs/gui/include/gui/ITransactionCompletedListener.h
@@ -40,10 +40,15 @@
class CallbackId : public Parcelable {
public:
int64_t id;
- enum class Type : int32_t { ON_COMPLETE, ON_COMMIT } type;
+ enum class Type : int32_t {
+ ON_COMPLETE = 0,
+ ON_COMMIT = 1,
+ /*reserved for serialization = 2*/
+ } type;
+ bool includeJankData; // Only respected for ON_COMPLETE callbacks.
CallbackId() {}
- CallbackId(int64_t id, Type type) : id(id), type(type) {}
+ CallbackId(int64_t id, Type type) : id(id), type(type), includeJankData(false) {}
status_t writeToParcel(Parcel* output) const override;
status_t readFromParcel(const Parcel* input) override;
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 70c9daf..ddaf473 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -111,6 +111,7 @@
uint64_t frameNumber = 0;
bool hasBarrier = false;
uint64_t barrierFrameNumber = 0;
+ uint32_t producerId = 0;
// Listens to when the buffer is safe to be released. This is used for blast
// layers only. The callback includes a release fence as well as the graphic
@@ -170,7 +171,7 @@
eTransparentRegionChanged = 0x00000020,
eFlagsChanged = 0x00000040,
eLayerStackChanged = 0x00000080,
- /* unused = 0x00000100, */
+ eFlushJankData = 0x00000100,
/* unused = 0x00000200, */
eDimmingEnabledChanged = 0x00000400,
eShadowRadiusChanged = 0x00000800,
@@ -209,7 +210,8 @@
eAutoRefreshChanged = 0x1000'00000000,
eStretchChanged = 0x2000'00000000,
eTrustedOverlayChanged = 0x4000'00000000,
- eDropInputModeChanged = 0x8000'00000000
+ eDropInputModeChanged = 0x8000'00000000,
+ eExtendedRangeBrightnessChanged = 0x10000'00000000
};
layer_state_t();
@@ -240,7 +242,8 @@
layer_state_t::eBufferTransformChanged | layer_state_t::eDataspaceChanged |
layer_state_t::eSidebandStreamChanged | layer_state_t::eSurfaceDamageRegionChanged |
layer_state_t::eTransformToDisplayInverseChanged |
- layer_state_t::eTransparentRegionChanged;
+ layer_state_t::eTransparentRegionChanged |
+ layer_state_t::eExtendedRangeBrightnessChanged;
// Content updates.
static constexpr uint64_t CONTENT_CHANGES = layer_state_t::BUFFER_CHANGES |
@@ -385,6 +388,8 @@
gui::DropInputMode dropInputMode;
bool dimmingEnabled;
+ float currentSdrHdrRatio = 1.f;
+ float desiredSdrHdrRatio = 1.f;
TrustedPresentationThresholds trustedPresentationThresholds;
TrustedPresentationListener trustedPresentationListener;
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 27353fb..63c0505 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -357,7 +357,8 @@
sp<SurfaceControl> mirrorDisplay(DisplayId displayId);
//! Create a virtual display
- static sp<IBinder> createDisplay(const String8& displayName, bool secure);
+ static sp<IBinder> createDisplay(const String8& displayName, bool secure,
+ float requestedRefereshRate = 0);
//! Destroy a virtual display
static void destroyDisplay(const sp<IBinder>& display);
@@ -414,7 +415,6 @@
SortedVector<DisplayState> mDisplayStates;
std::unordered_map<sp<ITransactionCompletedListener>, CallbackInfo, TCLHash>
mListenerCallbacks;
- std::vector<client_cache_t> mUncacheBuffers;
uint64_t mId;
@@ -537,7 +537,7 @@
Transaction& setBuffer(const sp<SurfaceControl>& sc, const sp<GraphicBuffer>& buffer,
const std::optional<sp<Fence>>& fence = std::nullopt,
const std::optional<uint64_t>& frameNumber = std::nullopt,
- ReleaseBufferCallback callback = nullptr);
+ uint32_t producerId = 0, ReleaseBufferCallback callback = nullptr);
std::shared_ptr<BufferData> getAndClearBuffer(const sp<SurfaceControl>& sc);
/**
@@ -561,6 +561,8 @@
Transaction& setBufferHasBarrier(const sp<SurfaceControl>& sc,
uint64_t barrierFrameNumber);
Transaction& setDataspace(const sp<SurfaceControl>& sc, ui::Dataspace dataspace);
+ Transaction& setExtendedRangeBrightness(const sp<SurfaceControl>& sc,
+ float currentBufferRatio, float desiredRatio);
Transaction& setHdrMetadata(const sp<SurfaceControl>& sc, const HdrMetadata& hdrMetadata);
Transaction& setSurfaceDamageRegion(const sp<SurfaceControl>& sc,
const Region& surfaceDamageRegion);
@@ -741,6 +743,8 @@
static sp<IBinder> getDefaultApplyToken();
static void setDefaultApplyToken(sp<IBinder> applyToken);
+
+ static status_t sendSurfaceFlushJankDataTransaction(const sp<SurfaceControl>& sc);
};
status_t clearLayerFrameStats(const sp<IBinder>& token) const;
@@ -875,10 +879,14 @@
const std::unordered_set<sp<SurfaceControl>, SurfaceComposerClient::SCHash>&
surfaceControls,
CallbackId::Type callbackType);
+ CallbackId addCallbackFunctionLocked(
+ const TransactionCompletedCallback& callbackFunction,
+ const std::unordered_set<sp<SurfaceControl>, SurfaceComposerClient::SCHash>&
+ surfaceControls,
+ CallbackId::Type callbackType) REQUIRES(mMutex);
- void addSurfaceControlToCallbacks(
- const sp<SurfaceControl>& surfaceControl,
- const std::unordered_set<CallbackId, CallbackIdHash>& callbackIds);
+ void addSurfaceControlToCallbacks(SurfaceComposerClient::CallbackInfo& callbackInfo,
+ const sp<SurfaceControl>& surfaceControl);
void addQueueStallListener(std::function<void(const std::string&)> stallListener, void* id);
void removeQueueStallListener(void *id);
@@ -920,7 +928,7 @@
void onTrustedPresentationChanged(int id, bool presentedWithinThresholds) override;
private:
- ReleaseBufferCallback popReleaseBufferCallbackLocked(const ReleaseCallbackId&);
+ ReleaseBufferCallback popReleaseBufferCallbackLocked(const ReleaseCallbackId&) REQUIRES(mMutex);
static sp<TransactionCompletedListener> sInstance;
};
diff --git a/libs/gui/include/gui/SurfaceControl.h b/libs/gui/include/gui/SurfaceControl.h
index 1d4fc7f..344b957 100644
--- a/libs/gui/include/gui/SurfaceControl.h
+++ b/libs/gui/include/gui/SurfaceControl.h
@@ -36,6 +36,7 @@
// ---------------------------------------------------------------------------
+class Choreographer;
class IGraphicBufferProducer;
class Surface;
class SurfaceComposerClient;
@@ -80,6 +81,9 @@
int32_t getLayerId() const;
const std::string& getName() const;
+ // TODO(b/267195698): Consider renaming.
+ std::shared_ptr<Choreographer> getChoreographer();
+
sp<IGraphicBufferProducer> getIGraphicBufferProducer();
status_t clearLayerFrameStats() const;
@@ -130,6 +134,7 @@
PixelFormat mFormat = PIXEL_FORMAT_NONE;
uint32_t mCreateFlags = 0;
uint64_t mFallbackFrameNumber = 100;
+ std::shared_ptr<Choreographer> mChoreographer;
};
}; // namespace android
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 32d60cd..be1ef8e 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -701,7 +701,7 @@
const sp<IBinder>& /*applyToken*/,
const InputWindowCommands& /*inputWindowCommands*/,
int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
- const std::vector<client_cache_t>& /*cachedBuffer*/,
+ const client_cache_t& /*cachedBuffer*/,
bool /*hasListenerCallbacks*/,
const std::vector<ListenerCallbacks>& /*listenerCallbacks*/,
uint64_t /*transactionId*/) override {
@@ -725,6 +725,7 @@
binder::Status createDisplayEventConnection(
VsyncSource /*vsyncSource*/, EventRegistration /*eventRegistration*/,
+ const sp<IBinder>& /*layerHandle*/,
sp<gui::IDisplayEventConnection>* outConnection) override {
*outConnection = nullptr;
return binder::Status::ok();
@@ -736,6 +737,7 @@
}
binder::Status createDisplay(const std::string& /*displayName*/, bool /*secure*/,
+ float /*requestedRefreshRate*/,
sp<IBinder>* /*outDisplay*/) override {
return binder::Status::ok();
}
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 8f41cc1..fd4fc16 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -41,6 +41,7 @@
"-Wall",
"-Wextra",
"-Werror",
+ "-Wno-unused-parameter",
],
srcs: [
"Input.cpp",
@@ -52,13 +53,18 @@
"MotionPredictor.cpp",
"PrintTools.cpp",
"PropertyMap.cpp",
+ "TfLiteMotionPredictor.cpp",
"TouchVideoFrame.cpp",
"VelocityControl.cpp",
"VelocityTracker.cpp",
"VirtualKeyMap.cpp",
],
- header_libs: ["jni_headers"],
+ header_libs: [
+ "flatbuffer_headers",
+ "jni_headers",
+ "tensorflow_headers",
+ ],
export_header_lib_headers: ["jni_headers"],
shared_libs: [
@@ -69,8 +75,13 @@
"libvintf",
],
+ ldflags: [
+ "-Wl,--exclude-libs=libtflite_static.a",
+ ],
+
static_libs: [
"libui-types",
+ "libtflite_static",
],
export_static_lib_headers: [
@@ -103,6 +114,10 @@
sanitize: {
misc_undefined: ["integer"],
},
+
+ required: [
+ "motion_predictor_model_prebuilt",
+ ],
},
host: {
shared: {
diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index 0fa0f12..7d11ef2 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -18,118 +18,193 @@
#include <input/MotionPredictor.h>
+#include <cinttypes>
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include <android-base/strings.h>
+#include <android/input.h>
+#include <log/log.h>
+
+#include <attestation/HmacKeyManager.h>
+#include <input/TfLiteMotionPredictor.h>
+
+namespace android {
+namespace {
+
+const char DEFAULT_MODEL_PATH[] = "/system/etc/motion_predictor_model.fb";
+const int64_t PREDICTION_INTERVAL_NANOS =
+ 12500000 / 3; // TODO(b/266747937): Get this from the model.
+
/**
* Log debug messages about predictions.
* Enable this via "adb shell setprop log.tag.MotionPredictor DEBUG"
*/
-static bool isDebug() {
+bool isDebug() {
return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO);
}
-namespace android {
+// Converts a prediction of some polar (r, phi) to Cartesian (x, y) when applied to an axis.
+TfLiteMotionPredictorSample::Point convertPrediction(
+ const TfLiteMotionPredictorSample::Point& axisFrom,
+ const TfLiteMotionPredictorSample::Point& axisTo, float r, float phi) {
+ const TfLiteMotionPredictorSample::Point axis = axisTo - axisFrom;
+ const float axis_phi = std::atan2(axis.y, axis.x);
+ const float x_delta = r * std::cos(axis_phi + phi);
+ const float y_delta = r * std::sin(axis_phi + phi);
+ return {.x = axisTo.x + x_delta, .y = axisTo.y + y_delta};
+}
+
+} // namespace
// --- MotionPredictor ---
-MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
+MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos, const char* modelPath,
std::function<bool()> checkMotionPredictionEnabled)
: mPredictionTimestampOffsetNanos(predictionTimestampOffsetNanos),
+ mModelPath(modelPath == nullptr ? DEFAULT_MODEL_PATH : modelPath),
mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)) {}
void MotionPredictor::record(const MotionEvent& event) {
- mEvents.push_back({});
- mEvents.back().copyFrom(&event, /*keepHistory=*/true);
- if (mEvents.size() > 2) {
- // Just need 2 samples in order to extrapolate
- mEvents.erase(mEvents.begin());
+ if (!isPredictionAvailable(event.getDeviceId(), event.getSource())) {
+ ALOGE("Prediction not supported for device %d's %s source", event.getDeviceId(),
+ inputEventSourceToString(event.getSource()).c_str());
+ return;
}
+
+ // Initialise the model now that it's likely to be used.
+ if (!mModel) {
+ mModel = TfLiteMotionPredictorModel::create(mModelPath.c_str());
+ }
+
+ TfLiteMotionPredictorBuffers& buffers =
+ mDeviceBuffers.try_emplace(event.getDeviceId(), mModel->inputLength()).first->second;
+
+ const int32_t action = event.getActionMasked();
+ if (action == AMOTION_EVENT_ACTION_UP) {
+ ALOGD_IF(isDebug(), "End of event stream");
+ buffers.reset();
+ return;
+ } else if (action != AMOTION_EVENT_ACTION_DOWN && action != AMOTION_EVENT_ACTION_MOVE) {
+ ALOGD_IF(isDebug(), "Skipping unsupported %s action",
+ MotionEvent::actionToString(action).c_str());
+ return;
+ }
+
+ if (event.getPointerCount() != 1) {
+ ALOGD_IF(isDebug(), "Prediction not supported for multiple pointers");
+ return;
+ }
+
+ const int32_t toolType = event.getPointerProperties(0)->toolType;
+ if (toolType != AMOTION_EVENT_TOOL_TYPE_STYLUS) {
+ ALOGD_IF(isDebug(), "Prediction not supported for non-stylus tool: %s",
+ motionToolTypeToString(toolType));
+ return;
+ }
+
+ for (size_t i = 0; i <= event.getHistorySize(); ++i) {
+ if (event.isResampled(0, i)) {
+ continue;
+ }
+ const PointerCoords* coords = event.getHistoricalRawPointerCoords(0, i);
+ buffers.pushSample(event.getHistoricalEventTime(i),
+ {
+ .position.x = coords->getAxisValue(AMOTION_EVENT_AXIS_X),
+ .position.y = coords->getAxisValue(AMOTION_EVENT_AXIS_Y),
+ .pressure = event.getHistoricalPressure(0, i),
+ .tilt = event.getHistoricalAxisValue(AMOTION_EVENT_AXIS_TILT, 0,
+ i),
+ .orientation = event.getHistoricalOrientation(0, i),
+ });
+ }
+
+ mLastEvents.try_emplace(event.getDeviceId())
+ .first->second.copyFrom(&event, /*keepHistory=*/false);
}
-/**
- * This is an example implementation that should be replaced with the actual prediction.
- * The returned MotionEvent should be similar to the incoming MotionEvent, except for the
- * fields that are predicted:
- *
- * 1) event.getEventTime
- * 2) event.getPointerCoords
- *
- * The returned event should not contain any of the real, existing data. It should only
- * contain the predicted samples.
- */
std::vector<std::unique_ptr<MotionEvent>> MotionPredictor::predict(nsecs_t timestamp) {
- if (mEvents.size() < 2) {
- return {};
- }
+ std::vector<std::unique_ptr<MotionEvent>> predictions;
- const MotionEvent& event = mEvents.back();
- if (!isPredictionAvailable(event.getDeviceId(), event.getSource())) {
- return {};
- }
-
- std::unique_ptr<MotionEvent> prediction = std::make_unique<MotionEvent>();
- std::vector<PointerCoords> futureCoords;
- const nsecs_t futureTime = timestamp + mPredictionTimestampOffsetNanos;
- const nsecs_t currentTime = event.getEventTime();
- const MotionEvent& previous = mEvents.rbegin()[1];
- const nsecs_t oldTime = previous.getEventTime();
- if (currentTime == oldTime) {
- // This can happen if it's an ACTION_POINTER_DOWN event, for example.
- return {}; // prevent division by zero.
- }
-
- for (size_t i = 0; i < event.getPointerCount(); i++) {
- const int32_t pointerId = event.getPointerId(i);
- const PointerCoords* currentPointerCoords = event.getRawPointerCoords(i);
- const float currentX = currentPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_X);
- const float currentY = currentPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_Y);
-
- PointerCoords coords;
- coords.clear();
-
- ssize_t index = previous.findPointerIndex(pointerId);
- if (index >= 0) {
- // We have old data for this pointer. Compute the prediction.
- const PointerCoords* oldPointerCoords = previous.getRawPointerCoords(index);
- const float oldX = oldPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_X);
- const float oldY = oldPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_Y);
-
- // Let's do a linear interpolation while waiting for a real model
- const float scale =
- static_cast<float>(futureTime - currentTime) / (currentTime - oldTime);
- const float futureX = currentX + (currentX - oldX) * scale;
- const float futureY = currentY + (currentY - oldY) * scale;
-
- coords.setAxisValue(AMOTION_EVENT_AXIS_X, futureX);
- coords.setAxisValue(AMOTION_EVENT_AXIS_Y, futureY);
- ALOGD_IF(isDebug(),
- "Prediction by %.1f ms, (%.1f, %.1f), (%.1f, %.1f) --> (%.1f, %.1f)",
- (futureTime - event.getEventTime()) * 1E-6, oldX, oldY, currentX, currentY,
- futureX, futureY);
+ for (const auto& [deviceId, buffer] : mDeviceBuffers) {
+ if (!buffer.isReady()) {
+ continue;
}
- futureCoords.push_back(coords);
+ LOG_ALWAYS_FATAL_IF(!mModel);
+ buffer.copyTo(*mModel);
+ LOG_ALWAYS_FATAL_IF(!mModel->invoke());
+
+ // Read out the predictions.
+ const std::span<const float> predictedR = mModel->outputR();
+ const std::span<const float> predictedPhi = mModel->outputPhi();
+ const std::span<const float> predictedPressure = mModel->outputPressure();
+
+ TfLiteMotionPredictorSample::Point axisFrom = buffer.axisFrom().position;
+ TfLiteMotionPredictorSample::Point axisTo = buffer.axisTo().position;
+
+ if (isDebug()) {
+ ALOGD("deviceId: %d", deviceId);
+ ALOGD("axisFrom: %f, %f", axisFrom.x, axisFrom.y);
+ ALOGD("axisTo: %f, %f", axisTo.x, axisTo.y);
+ ALOGD("mInputR: %s", base::Join(mModel->inputR(), ", ").c_str());
+ ALOGD("mInputPhi: %s", base::Join(mModel->inputPhi(), ", ").c_str());
+ ALOGD("mInputPressure: %s", base::Join(mModel->inputPressure(), ", ").c_str());
+ ALOGD("mInputTilt: %s", base::Join(mModel->inputTilt(), ", ").c_str());
+ ALOGD("mInputOrientation: %s", base::Join(mModel->inputOrientation(), ", ").c_str());
+ ALOGD("predictedR: %s", base::Join(predictedR, ", ").c_str());
+ ALOGD("predictedPhi: %s", base::Join(predictedPhi, ", ").c_str());
+ ALOGD("predictedPressure: %s", base::Join(predictedPressure, ", ").c_str());
+ }
+
+ const MotionEvent& event = mLastEvents[deviceId];
+ bool hasPredictions = false;
+ std::unique_ptr<MotionEvent> prediction = std::make_unique<MotionEvent>();
+ int64_t predictionTime = buffer.lastTimestamp();
+ const int64_t futureTime = timestamp + mPredictionTimestampOffsetNanos;
+
+ for (int i = 0; i < predictedR.size() && predictionTime <= futureTime; ++i) {
+ const TfLiteMotionPredictorSample::Point point =
+ convertPrediction(axisFrom, axisTo, predictedR[i], predictedPhi[i]);
+ // TODO(b/266747654): Stop predictions if confidence is < some threshold.
+
+ ALOGD_IF(isDebug(), "prediction %d: %f, %f", i, point.x, point.y);
+ PointerCoords coords;
+ coords.clear();
+ coords.setAxisValue(AMOTION_EVENT_AXIS_X, point.x);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_Y, point.y);
+ // TODO(b/266747654): Stop predictions if predicted pressure is < some threshold.
+ coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, predictedPressure[i]);
+
+ predictionTime += PREDICTION_INTERVAL_NANOS;
+ if (i == 0) {
+ hasPredictions = true;
+ prediction->initialize(InputEvent::nextId(), event.getDeviceId(), event.getSource(),
+ event.getDisplayId(), INVALID_HMAC,
+ AMOTION_EVENT_ACTION_MOVE, event.getActionButton(),
+ event.getFlags(), event.getEdgeFlags(), event.getMetaState(),
+ event.getButtonState(), event.getClassification(),
+ event.getTransform(), event.getXPrecision(),
+ event.getYPrecision(), event.getRawXCursorPosition(),
+ event.getRawYCursorPosition(), event.getRawTransform(),
+ event.getDownTime(), predictionTime, event.getPointerCount(),
+ event.getPointerProperties(), &coords);
+ } else {
+ prediction->addSample(predictionTime, &coords);
+ }
+
+ axisFrom = axisTo;
+ axisTo = point;
+ }
+ // TODO(b/266747511): Interpolate to futureTime?
+ if (hasPredictions) {
+ predictions.push_back(std::move(prediction));
+ }
}
-
- /**
- * The process of adding samples is different for the first and subsequent samples:
- * 1. Add the first sample via 'initialize' as below
- * 2. Add subsequent samples via 'addSample'
- */
- prediction->initialize(event.getId(), event.getDeviceId(), event.getSource(),
- event.getDisplayId(), event.getHmac(), event.getAction(),
- event.getActionButton(), event.getFlags(), event.getEdgeFlags(),
- event.getMetaState(), event.getButtonState(), event.getClassification(),
- event.getTransform(), event.getXPrecision(), event.getYPrecision(),
- event.getRawXCursorPosition(), event.getRawYCursorPosition(),
- event.getRawTransform(), event.getDownTime(), futureTime,
- event.getPointerCount(), event.getPointerProperties(),
- futureCoords.data());
-
- // To add more predicted samples, use 'addSample':
- prediction->addSample(futureTime + 1, futureCoords.data());
-
- std::vector<std::unique_ptr<MotionEvent>> out;
- out.push_back(std::move(prediction));
- return out;
+ return predictions;
}
bool MotionPredictor::isPredictionAvailable(int32_t /*deviceId*/, int32_t source) {
diff --git a/libs/input/TfLiteMotionPredictor.cpp b/libs/input/TfLiteMotionPredictor.cpp
new file mode 100644
index 0000000..10510d6
--- /dev/null
+++ b/libs/input/TfLiteMotionPredictor.cpp
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "TfLiteMotionPredictor"
+#include <input/TfLiteMotionPredictor.h>
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <span>
+#include <type_traits>
+#include <utility>
+
+#include <android-base/logging.h>
+#include <android-base/mapped_file.h>
+#define ATRACE_TAG ATRACE_TAG_INPUT
+#include <cutils/trace.h>
+#include <log/log.h>
+
+#include "tensorflow/lite/core/api/error_reporter.h"
+#include "tensorflow/lite/core/api/op_resolver.h"
+#include "tensorflow/lite/interpreter.h"
+#include "tensorflow/lite/kernels/builtin_op_kernels.h"
+#include "tensorflow/lite/model.h"
+#include "tensorflow/lite/mutable_op_resolver.h"
+
+namespace android {
+namespace {
+
+constexpr char SIGNATURE_KEY[] = "serving_default";
+
+// Input tensor names.
+constexpr char INPUT_R[] = "r";
+constexpr char INPUT_PHI[] = "phi";
+constexpr char INPUT_PRESSURE[] = "pressure";
+constexpr char INPUT_TILT[] = "tilt";
+constexpr char INPUT_ORIENTATION[] = "orientation";
+
+// Output tensor names.
+constexpr char OUTPUT_R[] = "r";
+constexpr char OUTPUT_PHI[] = "phi";
+constexpr char OUTPUT_PRESSURE[] = "pressure";
+
+// A TFLite ErrorReporter that logs to logcat.
+class LoggingErrorReporter : public tflite::ErrorReporter {
+public:
+ int Report(const char* format, va_list args) override {
+ return LOG_PRI_VA(ANDROID_LOG_ERROR, LOG_TAG, format, args);
+ }
+};
+
+// Searches a runner for an input tensor.
+TfLiteTensor* findInputTensor(const char* name, tflite::SignatureRunner* runner) {
+ TfLiteTensor* tensor = runner->input_tensor(name);
+ LOG_ALWAYS_FATAL_IF(!tensor, "Failed to find input tensor '%s'", name);
+ return tensor;
+}
+
+// Searches a runner for an output tensor.
+const TfLiteTensor* findOutputTensor(const char* name, tflite::SignatureRunner* runner) {
+ const TfLiteTensor* tensor = runner->output_tensor(name);
+ LOG_ALWAYS_FATAL_IF(!tensor, "Failed to find output tensor '%s'", name);
+ return tensor;
+}
+
+// Returns the buffer for a tensor of type T.
+template <typename T>
+std::span<T> getTensorBuffer(typename std::conditional<std::is_const<T>::value, const TfLiteTensor*,
+ TfLiteTensor*>::type tensor) {
+ LOG_ALWAYS_FATAL_IF(!tensor);
+
+ const TfLiteType type = tflite::typeToTfLiteType<typename std::remove_cv<T>::type>();
+ LOG_ALWAYS_FATAL_IF(tensor->type != type, "Unexpected type for '%s' tensor: %s (expected %s)",
+ tensor->name, TfLiteTypeGetName(tensor->type), TfLiteTypeGetName(type));
+
+ LOG_ALWAYS_FATAL_IF(!tensor->data.data);
+ return {reinterpret_cast<T*>(tensor->data.data),
+ static_cast<typename std::span<T>::index_type>(tensor->bytes / sizeof(T))};
+}
+
+// Verifies that a tensor exists and has an underlying buffer of type T.
+template <typename T>
+void checkTensor(const TfLiteTensor* tensor) {
+ LOG_ALWAYS_FATAL_IF(!tensor);
+
+ const auto buffer = getTensorBuffer<const T>(tensor);
+ LOG_ALWAYS_FATAL_IF(buffer.empty(), "No buffer for tensor '%s'", tensor->name);
+}
+
+std::unique_ptr<tflite::OpResolver> createOpResolver() {
+ auto resolver = std::make_unique<tflite::MutableOpResolver>();
+ resolver->AddBuiltin(::tflite::BuiltinOperator_CONCATENATION,
+ ::tflite::ops::builtin::Register_CONCATENATION());
+ resolver->AddBuiltin(::tflite::BuiltinOperator_FULLY_CONNECTED,
+ ::tflite::ops::builtin::Register_FULLY_CONNECTED());
+ return resolver;
+}
+
+} // namespace
+
+TfLiteMotionPredictorBuffers::TfLiteMotionPredictorBuffers(size_t inputLength)
+ : mInputR(inputLength, 0),
+ mInputPhi(inputLength, 0),
+ mInputPressure(inputLength, 0),
+ mInputTilt(inputLength, 0),
+ mInputOrientation(inputLength, 0) {
+ LOG_ALWAYS_FATAL_IF(inputLength == 0, "Buffer input size must be greater than 0");
+}
+
+void TfLiteMotionPredictorBuffers::reset() {
+ std::fill(mInputR.begin(), mInputR.end(), 0);
+ std::fill(mInputPhi.begin(), mInputPhi.end(), 0);
+ std::fill(mInputPressure.begin(), mInputPressure.end(), 0);
+ std::fill(mInputTilt.begin(), mInputTilt.end(), 0);
+ std::fill(mInputOrientation.begin(), mInputOrientation.end(), 0);
+ mAxisFrom.reset();
+ mAxisTo.reset();
+}
+
+void TfLiteMotionPredictorBuffers::copyTo(TfLiteMotionPredictorModel& model) const {
+ LOG_ALWAYS_FATAL_IF(mInputR.size() != model.inputLength(),
+ "Buffer length %zu doesn't match model input length %zu", mInputR.size(),
+ model.inputLength());
+ LOG_ALWAYS_FATAL_IF(!isReady(), "Buffers are incomplete");
+
+ std::copy(mInputR.begin(), mInputR.end(), model.inputR().begin());
+ std::copy(mInputPhi.begin(), mInputPhi.end(), model.inputPhi().begin());
+ std::copy(mInputPressure.begin(), mInputPressure.end(), model.inputPressure().begin());
+ std::copy(mInputTilt.begin(), mInputTilt.end(), model.inputTilt().begin());
+ std::copy(mInputOrientation.begin(), mInputOrientation.end(), model.inputOrientation().begin());
+}
+
+void TfLiteMotionPredictorBuffers::pushSample(int64_t timestamp,
+ const TfLiteMotionPredictorSample sample) {
+ // Convert the sample (x, y) into polar (r, φ) based on a reference axis
+ // from the preceding two points (mAxisFrom/mAxisTo).
+
+ mTimestamp = timestamp;
+
+ if (!mAxisTo) { // First point.
+ mAxisTo = sample;
+ return;
+ }
+
+ // Vector from the last point to the current sample point.
+ const TfLiteMotionPredictorSample::Point v = sample.position - mAxisTo->position;
+
+ const float r = std::hypot(v.x, v.y);
+ float phi = 0;
+ float orientation = 0;
+
+ // Ignore the sample if there is no movement. These samples can occur when there's change to a
+ // property other than the coordinates and pollute the input to the model.
+ if (r == 0) {
+ return;
+ }
+
+ if (!mAxisFrom) { // Second point.
+ // We can only determine the distance from the first point, and not any
+ // angle. However, if the second point forms an axis, the orientation can
+ // be transformed relative to that axis.
+ const float axisPhi = std::atan2(v.y, v.x);
+ // A MotionEvent's orientation is measured clockwise from the vertical
+ // axis, but axisPhi is measured counter-clockwise from the horizontal
+ // axis.
+ orientation = M_PI_2 - sample.orientation - axisPhi;
+ } else {
+ const TfLiteMotionPredictorSample::Point axis = mAxisTo->position - mAxisFrom->position;
+ const float axisPhi = std::atan2(axis.y, axis.x);
+ phi = std::atan2(v.y, v.x) - axisPhi;
+
+ if (std::hypot(axis.x, axis.y) > 0) {
+ // See note above.
+ orientation = M_PI_2 - sample.orientation - axisPhi;
+ }
+ }
+
+ // Update the axis for the next point.
+ mAxisFrom = mAxisTo;
+ mAxisTo = sample;
+
+ // Push the current sample onto the end of the input buffers.
+ mInputR.pushBack(r);
+ mInputPhi.pushBack(phi);
+ mInputPressure.pushBack(sample.pressure);
+ mInputTilt.pushBack(sample.tilt);
+ mInputOrientation.pushBack(orientation);
+}
+
+std::unique_ptr<TfLiteMotionPredictorModel> TfLiteMotionPredictorModel::create(
+ const char* modelPath) {
+ const int fd = open(modelPath, O_RDONLY);
+ if (fd == -1) {
+ PLOG(FATAL) << "Could not read model from " << modelPath;
+ }
+
+ const off_t fdSize = lseek(fd, 0, SEEK_END);
+ if (fdSize == -1) {
+ PLOG(FATAL) << "Failed to determine file size";
+ }
+
+ std::unique_ptr<android::base::MappedFile> modelBuffer =
+ android::base::MappedFile::FromFd(fd, /*offset=*/0, fdSize, PROT_READ);
+ if (!modelBuffer) {
+ PLOG(FATAL) << "Failed to mmap model";
+ }
+ if (close(fd) == -1) {
+ PLOG(FATAL) << "Failed to close model fd";
+ }
+
+ return std::unique_ptr<TfLiteMotionPredictorModel>(
+ new TfLiteMotionPredictorModel(std::move(modelBuffer)));
+}
+
+TfLiteMotionPredictorModel::TfLiteMotionPredictorModel(
+ std::unique_ptr<android::base::MappedFile> model)
+ : mFlatBuffer(std::move(model)) {
+ CHECK(mFlatBuffer);
+ mErrorReporter = std::make_unique<LoggingErrorReporter>();
+ mModel = tflite::FlatBufferModel::VerifyAndBuildFromBuffer(mFlatBuffer->data(),
+ mFlatBuffer->size(),
+ /*extra_verifier=*/nullptr,
+ mErrorReporter.get());
+ LOG_ALWAYS_FATAL_IF(!mModel);
+
+ auto resolver = createOpResolver();
+ tflite::InterpreterBuilder builder(*mModel, *resolver);
+
+ if (builder(&mInterpreter) != kTfLiteOk || !mInterpreter) {
+ LOG_ALWAYS_FATAL("Failed to build interpreter");
+ }
+
+ mRunner = mInterpreter->GetSignatureRunner(SIGNATURE_KEY);
+ LOG_ALWAYS_FATAL_IF(!mRunner, "Failed to find runner for signature '%s'", SIGNATURE_KEY);
+
+ allocateTensors();
+}
+
+TfLiteMotionPredictorModel::~TfLiteMotionPredictorModel() {}
+
+void TfLiteMotionPredictorModel::allocateTensors() {
+ if (mRunner->AllocateTensors() != kTfLiteOk) {
+ LOG_ALWAYS_FATAL("Failed to allocate tensors");
+ }
+
+ attachInputTensors();
+ attachOutputTensors();
+
+ checkTensor<float>(mInputR);
+ checkTensor<float>(mInputPhi);
+ checkTensor<float>(mInputPressure);
+ checkTensor<float>(mInputTilt);
+ checkTensor<float>(mInputOrientation);
+ checkTensor<float>(mOutputR);
+ checkTensor<float>(mOutputPhi);
+ checkTensor<float>(mOutputPressure);
+
+ const auto checkInputTensorSize = [this](const TfLiteTensor* tensor) {
+ const size_t size = getTensorBuffer<const float>(tensor).size();
+ LOG_ALWAYS_FATAL_IF(size != inputLength(),
+ "Tensor '%s' length %zu does not match input length %zu", tensor->name,
+ size, inputLength());
+ };
+
+ checkInputTensorSize(mInputR);
+ checkInputTensorSize(mInputPhi);
+ checkInputTensorSize(mInputPressure);
+ checkInputTensorSize(mInputTilt);
+ checkInputTensorSize(mInputOrientation);
+}
+
+void TfLiteMotionPredictorModel::attachInputTensors() {
+ mInputR = findInputTensor(INPUT_R, mRunner);
+ mInputPhi = findInputTensor(INPUT_PHI, mRunner);
+ mInputPressure = findInputTensor(INPUT_PRESSURE, mRunner);
+ mInputTilt = findInputTensor(INPUT_TILT, mRunner);
+ mInputOrientation = findInputTensor(INPUT_ORIENTATION, mRunner);
+}
+
+void TfLiteMotionPredictorModel::attachOutputTensors() {
+ mOutputR = findOutputTensor(OUTPUT_R, mRunner);
+ mOutputPhi = findOutputTensor(OUTPUT_PHI, mRunner);
+ mOutputPressure = findOutputTensor(OUTPUT_PRESSURE, mRunner);
+}
+
+bool TfLiteMotionPredictorModel::invoke() {
+ ATRACE_BEGIN("TfLiteMotionPredictorModel::invoke");
+ TfLiteStatus result = mRunner->Invoke();
+ ATRACE_END();
+
+ if (result != kTfLiteOk) {
+ return false;
+ }
+
+ // Invoke() might reallocate tensors, so they need to be reattached.
+ attachInputTensors();
+ attachOutputTensors();
+
+ if (outputR().size() != outputPhi().size() || outputR().size() != outputPressure().size()) {
+ LOG_ALWAYS_FATAL("Output size mismatch: (r: %zu, phi: %zu, pressure: %zu)",
+ outputR().size(), outputPhi().size(), outputPressure().size());
+ }
+
+ return true;
+}
+
+size_t TfLiteMotionPredictorModel::inputLength() const {
+ return getTensorBuffer<const float>(mInputR).size();
+}
+
+std::span<float> TfLiteMotionPredictorModel::inputR() {
+ return getTensorBuffer<float>(mInputR);
+}
+
+std::span<float> TfLiteMotionPredictorModel::inputPhi() {
+ return getTensorBuffer<float>(mInputPhi);
+}
+
+std::span<float> TfLiteMotionPredictorModel::inputPressure() {
+ return getTensorBuffer<float>(mInputPressure);
+}
+
+std::span<float> TfLiteMotionPredictorModel::inputTilt() {
+ return getTensorBuffer<float>(mInputTilt);
+}
+
+std::span<float> TfLiteMotionPredictorModel::inputOrientation() {
+ return getTensorBuffer<float>(mInputOrientation);
+}
+
+std::span<const float> TfLiteMotionPredictorModel::outputR() const {
+ return getTensorBuffer<const float>(mOutputR);
+}
+
+std::span<const float> TfLiteMotionPredictorModel::outputPhi() const {
+ return getTensorBuffer<const float>(mOutputPhi);
+}
+
+std::span<const float> TfLiteMotionPredictorModel::outputPressure() const {
+ return getTensorBuffer<const float>(mOutputPressure);
+}
+
+} // namespace android
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index e2c0860..37faf91 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -10,6 +10,7 @@
cc_test {
name: "libinput_tests",
+ cpp_std: "c++20",
host_supported: true,
srcs: [
"IdGenerator_test.cpp",
@@ -18,20 +19,29 @@
"InputEvent_test.cpp",
"InputPublisherAndConsumer_test.cpp",
"MotionPredictor_test.cpp",
+ "RingBuffer_test.cpp",
+ "TfLiteMotionPredictor_test.cpp",
"TouchResampling_test.cpp",
"TouchVideoFrame_test.cpp",
"VelocityTracker_test.cpp",
"VerifiedInputEvent_test.cpp",
],
+ header_libs: [
+ "flatbuffer_headers",
+ "tensorflow_headers",
+ ],
static_libs: [
+ "libgmock",
"libgui_window_info_static",
"libinput",
+ "libtflite_static",
"libui-types",
],
cflags: [
"-Wall",
"-Wextra",
"-Werror",
+ "-Wno-unused-parameter",
],
shared_libs: [
"libbase",
@@ -42,7 +52,10 @@
"libutils",
"libvintf",
],
- data: ["data/*"],
+ data: [
+ "data/*",
+ ":motion_predictor_model.fb",
+ ],
test_options: {
unit_test: true,
},
diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp
index d2b59a1..ce87c86 100644
--- a/libs/input/tests/MotionPredictor_test.cpp
+++ b/libs/input/tests/MotionPredictor_test.cpp
@@ -14,17 +14,36 @@
* limitations under the License.
*/
+#include <chrono>
+
+#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <gui/constants.h>
#include <input/Input.h>
#include <input/MotionPredictor.h>
+using namespace std::literals::chrono_literals;
+
namespace android {
+using ::testing::IsEmpty;
+using ::testing::SizeIs;
+using ::testing::UnorderedElementsAre;
+
+const char MODEL_PATH[] =
+#if defined(__ANDROID__)
+ "/system/etc/motion_predictor_model.fb";
+#else
+ "motion_predictor_model.fb";
+#endif
+
constexpr int32_t DOWN = AMOTION_EVENT_ACTION_DOWN;
constexpr int32_t MOVE = AMOTION_EVENT_ACTION_MOVE;
+constexpr int32_t UP = AMOTION_EVENT_ACTION_UP;
+constexpr nsecs_t NSEC_PER_MSEC = 1'000'000;
-static MotionEvent getMotionEvent(int32_t action, float x, float y, nsecs_t eventTime) {
+static MotionEvent getMotionEvent(int32_t action, float x, float y,
+ std::chrono::nanoseconds eventTime, int32_t deviceId = 0) {
MotionEvent event;
constexpr size_t pointerCount = 1;
std::vector<PointerProperties> pointerProperties;
@@ -33,6 +52,7 @@
PointerProperties properties;
properties.clear();
properties.id = i;
+ properties.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
pointerProperties.push_back(properties);
PointerCoords coords;
coords.clear();
@@ -42,73 +62,93 @@
}
ui::Transform identityTransform;
- event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_STYLUS,
- ADISPLAY_ID_DEFAULT, {0}, action, /*actionButton=*/0, /*flags=*/0,
- AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0,
- MotionClassification::NONE, identityTransform, /*xPrecision=*/0.1,
+ event.initialize(InputEvent::nextId(), deviceId, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT, {0},
+ action, /*actionButton=*/0, /*flags=*/0, AMOTION_EVENT_EDGE_FLAG_NONE,
+ AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, identityTransform,
+ /*xPrecision=*/0.1,
/*yPrecision=*/0.2, /*xCursorPosition=*/280, /*yCursorPosition=*/540,
- identityTransform, /*downTime=*/100, eventTime, pointerCount,
+ identityTransform, /*downTime=*/100, eventTime.count(), pointerCount,
pointerProperties.data(), pointerCoords.data());
return event;
}
-/**
- * A linear motion should be predicted to be linear in the future
- */
-TEST(MotionPredictorTest, LinearPrediction) {
- MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
- []() { return true /*enable prediction*/; });
-
- predictor.record(getMotionEvent(DOWN, 0, 1, 0));
- predictor.record(getMotionEvent(MOVE, 1, 3, 10));
- predictor.record(getMotionEvent(MOVE, 2, 5, 20));
- predictor.record(getMotionEvent(MOVE, 3, 7, 30));
- std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
- ASSERT_EQ(1u, predicted.size());
- ASSERT_EQ(predicted[0]->getX(0), 4);
- ASSERT_EQ(predicted[0]->getY(0), 9);
-}
-
-/**
- * A still motion should be predicted to remain still
- */
-TEST(MotionPredictorTest, StationaryPrediction) {
- MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
- []() { return true /*enable prediction*/; });
-
- predictor.record(getMotionEvent(DOWN, 0, 1, 0));
- predictor.record(getMotionEvent(MOVE, 0, 1, 10));
- predictor.record(getMotionEvent(MOVE, 0, 1, 20));
- predictor.record(getMotionEvent(MOVE, 0, 1, 30));
- std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
- ASSERT_EQ(1u, predicted.size());
- ASSERT_EQ(predicted[0]->getX(0), 0);
- ASSERT_EQ(predicted[0]->getY(0), 1);
-}
-
TEST(MotionPredictorTest, IsPredictionAvailable) {
- MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+ MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH,
[]() { return true /*enable prediction*/; });
ASSERT_TRUE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_STYLUS));
ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN));
}
TEST(MotionPredictorTest, Offset) {
- MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/1,
+ MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/1, MODEL_PATH,
[]() { return true /*enable prediction*/; });
- predictor.record(getMotionEvent(DOWN, 0, 1, 30));
- predictor.record(getMotionEvent(MOVE, 0, 1, 35));
- std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
+ predictor.record(getMotionEvent(DOWN, 0, 1, 30ms));
+ predictor.record(getMotionEvent(MOVE, 0, 2, 35ms));
+ std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40 * NSEC_PER_MSEC);
ASSERT_EQ(1u, predicted.size());
ASSERT_GE(predicted[0]->getEventTime(), 41);
}
+TEST(MotionPredictorTest, FollowsGesture) {
+ MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH,
+ []() { return true /*enable prediction*/; });
+
+ // MOVE without a DOWN is ignored.
+ predictor.record(getMotionEvent(MOVE, 1, 3, 10ms));
+ EXPECT_THAT(predictor.predict(20 * NSEC_PER_MSEC), IsEmpty());
+
+ predictor.record(getMotionEvent(DOWN, 2, 5, 20ms));
+ predictor.record(getMotionEvent(MOVE, 2, 7, 30ms));
+ predictor.record(getMotionEvent(MOVE, 3, 9, 40ms));
+ EXPECT_THAT(predictor.predict(50 * NSEC_PER_MSEC), SizeIs(1));
+
+ predictor.record(getMotionEvent(UP, 4, 11, 50ms));
+ EXPECT_THAT(predictor.predict(20 * NSEC_PER_MSEC), IsEmpty());
+}
+
+TEST(MotionPredictorTest, MultipleDevicesTracked) {
+ MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH,
+ []() { return true /*enable prediction*/; });
+
+ predictor.record(getMotionEvent(DOWN, 1, 3, 0ms, /*deviceId=*/0));
+ predictor.record(getMotionEvent(MOVE, 1, 3, 10ms, /*deviceId=*/0));
+ predictor.record(getMotionEvent(MOVE, 2, 5, 20ms, /*deviceId=*/0));
+ predictor.record(getMotionEvent(MOVE, 3, 7, 30ms, /*deviceId=*/0));
+
+ predictor.record(getMotionEvent(DOWN, 100, 300, 0ms, /*deviceId=*/1));
+ predictor.record(getMotionEvent(MOVE, 100, 300, 10ms, /*deviceId=*/1));
+ predictor.record(getMotionEvent(MOVE, 200, 500, 20ms, /*deviceId=*/1));
+ predictor.record(getMotionEvent(MOVE, 300, 700, 30ms, /*deviceId=*/1));
+
+ {
+ std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40 * NSEC_PER_MSEC);
+ ASSERT_EQ(2u, predicted.size());
+
+ // Order of the returned vector is not guaranteed.
+ std::vector<int32_t> seenDeviceIds;
+ for (const auto& prediction : predicted) {
+ seenDeviceIds.push_back(prediction->getDeviceId());
+ }
+ EXPECT_THAT(seenDeviceIds, UnorderedElementsAre(0, 1));
+ }
+
+ // End the gesture for device 0.
+ predictor.record(getMotionEvent(UP, 4, 9, 40ms, /*deviceId=*/0));
+ predictor.record(getMotionEvent(MOVE, 400, 900, 40ms, /*deviceId=*/1));
+
+ {
+ std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40 * NSEC_PER_MSEC);
+ ASSERT_EQ(1u, predicted.size());
+ ASSERT_EQ(predicted[0]->getDeviceId(), 1);
+ }
+}
+
TEST(MotionPredictorTest, FlagDisablesPrediction) {
- MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+ MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH,
[]() { return false /*disable prediction*/; });
- predictor.record(getMotionEvent(DOWN, 0, 1, 30));
- predictor.record(getMotionEvent(MOVE, 0, 1, 35));
- std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
+ predictor.record(getMotionEvent(DOWN, 0, 1, 30ms));
+ predictor.record(getMotionEvent(MOVE, 0, 1, 35ms));
+ std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40 * NSEC_PER_MSEC);
ASSERT_EQ(0u, predicted.size());
ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_STYLUS));
ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN));
diff --git a/libs/input/tests/RingBuffer_test.cpp b/libs/input/tests/RingBuffer_test.cpp
new file mode 100644
index 0000000..8a6ef4c
--- /dev/null
+++ b/libs/input/tests/RingBuffer_test.cpp
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <iterator>
+#include <memory>
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <input/RingBuffer.h>
+
+namespace android {
+namespace {
+
+using ::testing::ElementsAre;
+using ::testing::ElementsAreArray;
+using ::testing::IsEmpty;
+using ::testing::Not;
+using ::testing::SizeIs;
+
+TEST(RingBufferTest, PushPop) {
+ RingBuffer<int> buffer(/*capacity=*/3);
+
+ buffer.pushBack(1);
+ buffer.pushBack(2);
+ buffer.pushBack(3);
+ EXPECT_THAT(buffer, ElementsAre(1, 2, 3));
+
+ buffer.pushBack(4);
+ EXPECT_THAT(buffer, ElementsAre(2, 3, 4));
+
+ buffer.pushFront(1);
+ EXPECT_THAT(buffer, ElementsAre(1, 2, 3));
+
+ EXPECT_EQ(1, buffer.popFront());
+ EXPECT_THAT(buffer, ElementsAre(2, 3));
+
+ buffer.pushBack(4);
+ EXPECT_THAT(buffer, ElementsAre(2, 3, 4));
+
+ buffer.pushBack(5);
+ EXPECT_THAT(buffer, ElementsAre(3, 4, 5));
+
+ EXPECT_EQ(5, buffer.popBack());
+ EXPECT_THAT(buffer, ElementsAre(3, 4));
+
+ EXPECT_EQ(4, buffer.popBack());
+ EXPECT_THAT(buffer, ElementsAre(3));
+
+ EXPECT_EQ(3, buffer.popBack());
+ EXPECT_THAT(buffer, ElementsAre());
+
+ buffer.pushBack(1);
+ EXPECT_THAT(buffer, ElementsAre(1));
+
+ EXPECT_EQ(1, buffer.popFront());
+ EXPECT_THAT(buffer, ElementsAre());
+}
+
+TEST(RingBufferTest, ObjectType) {
+ RingBuffer<std::unique_ptr<int>> buffer(/*capacity=*/2);
+ buffer.pushBack(std::make_unique<int>(1));
+ buffer.pushBack(std::make_unique<int>(2));
+ buffer.pushBack(std::make_unique<int>(3));
+
+ EXPECT_EQ(2, *buffer[0]);
+ EXPECT_EQ(3, *buffer[1]);
+}
+
+TEST(RingBufferTest, ConstructConstantValue) {
+ RingBuffer<int> buffer(/*count=*/3, /*value=*/10);
+ EXPECT_THAT(buffer, ElementsAre(10, 10, 10));
+ EXPECT_EQ(3u, buffer.capacity());
+}
+
+TEST(RingBufferTest, Assignment) {
+ RingBuffer<int> a(/*capacity=*/2);
+ a.pushBack(1);
+ a.pushBack(2);
+
+ RingBuffer<int> b(/*capacity=*/3);
+ b.pushBack(10);
+ b.pushBack(20);
+ b.pushBack(30);
+
+ std::swap(a, b);
+ EXPECT_THAT(a, ElementsAre(10, 20, 30));
+ EXPECT_THAT(b, ElementsAre(1, 2));
+
+ a = b;
+ EXPECT_THAT(a, ElementsAreArray(b));
+
+ RingBuffer<int> c(b);
+ EXPECT_THAT(c, ElementsAreArray(b));
+
+ RingBuffer<int> d(std::move(b));
+ EXPECT_EQ(0u, b.capacity());
+ EXPECT_THAT(b, ElementsAre());
+ EXPECT_THAT(d, ElementsAre(1, 2));
+
+ b = std::move(d);
+ EXPECT_THAT(b, ElementsAre(1, 2));
+ EXPECT_THAT(d, ElementsAre());
+ EXPECT_EQ(0u, d.capacity());
+}
+
+TEST(RingBufferTest, Subscripting) {
+ RingBuffer<int> buffer(/*capacity=*/2);
+ buffer.pushBack(1);
+ EXPECT_EQ(1, buffer[0]);
+
+ buffer.pushFront(0);
+ EXPECT_EQ(0, buffer[0]);
+ EXPECT_EQ(1, buffer[1]);
+
+ buffer.pushFront(-1);
+ EXPECT_EQ(-1, buffer[0]);
+ EXPECT_EQ(0, buffer[1]);
+}
+
+TEST(RingBufferTest, Iterator) {
+ RingBuffer<int> buffer(/*capacity=*/3);
+ buffer.pushFront(2);
+ buffer.pushBack(3);
+
+ auto begin = buffer.begin();
+ auto end = buffer.end();
+
+ EXPECT_NE(begin, end);
+ EXPECT_LE(begin, end);
+ EXPECT_GT(end, begin);
+ EXPECT_EQ(end, begin + 2);
+ EXPECT_EQ(begin, end - 2);
+
+ EXPECT_EQ(2, end - begin);
+ EXPECT_EQ(1, end - (begin + 1));
+
+ EXPECT_EQ(2, *begin);
+ ++begin;
+ EXPECT_EQ(3, *begin);
+ --begin;
+ EXPECT_EQ(2, *begin);
+ begin += 1;
+ EXPECT_EQ(3, *begin);
+ begin += -1;
+ EXPECT_EQ(2, *begin);
+ begin -= -1;
+ EXPECT_EQ(3, *begin);
+}
+
+TEST(RingBufferTest, Clear) {
+ RingBuffer<int> buffer(/*capacity=*/2);
+ EXPECT_THAT(buffer, ElementsAre());
+
+ buffer.pushBack(1);
+ EXPECT_THAT(buffer, ElementsAre(1));
+
+ buffer.clear();
+ EXPECT_THAT(buffer, ElementsAre());
+ EXPECT_THAT(buffer, SizeIs(0));
+ EXPECT_THAT(buffer, IsEmpty());
+
+ buffer.pushFront(1);
+ EXPECT_THAT(buffer, ElementsAre(1));
+}
+
+TEST(RingBufferTest, SizeAndIsEmpty) {
+ RingBuffer<int> buffer(/*capacity=*/2);
+ EXPECT_THAT(buffer, SizeIs(0));
+ EXPECT_THAT(buffer, IsEmpty());
+
+ buffer.pushBack(1);
+ EXPECT_THAT(buffer, SizeIs(1));
+ EXPECT_THAT(buffer, Not(IsEmpty()));
+
+ buffer.pushBack(2);
+ EXPECT_THAT(buffer, SizeIs(2));
+ EXPECT_THAT(buffer, Not(IsEmpty()));
+
+ buffer.pushBack(3);
+ EXPECT_THAT(buffer, SizeIs(2));
+ EXPECT_THAT(buffer, Not(IsEmpty()));
+
+ buffer.popFront();
+ EXPECT_THAT(buffer, SizeIs(1));
+ EXPECT_THAT(buffer, Not(IsEmpty()));
+
+ buffer.popBack();
+ EXPECT_THAT(buffer, SizeIs(0));
+ EXPECT_THAT(buffer, IsEmpty());
+}
+
+} // namespace
+} // namespace android
diff --git a/libs/input/tests/TfLiteMotionPredictor_test.cpp b/libs/input/tests/TfLiteMotionPredictor_test.cpp
new file mode 100644
index 0000000..454f2aa
--- /dev/null
+++ b/libs/input/tests/TfLiteMotionPredictor_test.cpp
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <cmath>
+#include <fstream>
+#include <ios>
+#include <iterator>
+#include <string>
+
+#include <android-base/file.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <input/TfLiteMotionPredictor.h>
+
+namespace android {
+namespace {
+
+using ::testing::Each;
+using ::testing::ElementsAre;
+using ::testing::FloatNear;
+
+std::string getModelPath() {
+#if defined(__ANDROID__)
+ return "/system/etc/motion_predictor_model.fb";
+#else
+ return base::GetExecutableDirectory() + "/motion_predictor_model.fb";
+#endif
+}
+
+TEST(TfLiteMotionPredictorTest, BuffersReadiness) {
+ TfLiteMotionPredictorBuffers buffers(/*inputLength=*/5);
+ ASSERT_FALSE(buffers.isReady());
+
+ buffers.pushSample(/*timestamp=*/0, {.position = {.x = 100, .y = 100}});
+ ASSERT_FALSE(buffers.isReady());
+
+ buffers.pushSample(/*timestamp=*/1, {.position = {.x = 100, .y = 100}});
+ ASSERT_FALSE(buffers.isReady());
+
+ // Two samples with distinct positions are required.
+ buffers.pushSample(/*timestamp=*/2, {.position = {.x = 100, .y = 110}});
+ ASSERT_TRUE(buffers.isReady());
+
+ buffers.reset();
+ ASSERT_FALSE(buffers.isReady());
+}
+
+TEST(TfLiteMotionPredictorTest, BuffersRecentData) {
+ TfLiteMotionPredictorBuffers buffers(/*inputLength=*/5);
+
+ buffers.pushSample(/*timestamp=*/1, {.position = {.x = 100, .y = 200}});
+ ASSERT_EQ(buffers.lastTimestamp(), 1);
+
+ buffers.pushSample(/*timestamp=*/2, {.position = {.x = 150, .y = 250}});
+ ASSERT_EQ(buffers.lastTimestamp(), 2);
+ ASSERT_TRUE(buffers.isReady());
+ ASSERT_EQ(buffers.axisFrom().position.x, 100);
+ ASSERT_EQ(buffers.axisFrom().position.y, 200);
+ ASSERT_EQ(buffers.axisTo().position.x, 150);
+ ASSERT_EQ(buffers.axisTo().position.y, 250);
+
+ // Position doesn't change, so neither do the axes.
+ buffers.pushSample(/*timestamp=*/3, {.position = {.x = 150, .y = 250}});
+ ASSERT_EQ(buffers.lastTimestamp(), 3);
+ ASSERT_TRUE(buffers.isReady());
+ ASSERT_EQ(buffers.axisFrom().position.x, 100);
+ ASSERT_EQ(buffers.axisFrom().position.y, 200);
+ ASSERT_EQ(buffers.axisTo().position.x, 150);
+ ASSERT_EQ(buffers.axisTo().position.y, 250);
+
+ buffers.pushSample(/*timestamp=*/4, {.position = {.x = 180, .y = 280}});
+ ASSERT_EQ(buffers.lastTimestamp(), 4);
+ ASSERT_TRUE(buffers.isReady());
+ ASSERT_EQ(buffers.axisFrom().position.x, 150);
+ ASSERT_EQ(buffers.axisFrom().position.y, 250);
+ ASSERT_EQ(buffers.axisTo().position.x, 180);
+ ASSERT_EQ(buffers.axisTo().position.y, 280);
+}
+
+TEST(TfLiteMotionPredictorTest, BuffersCopyTo) {
+ std::unique_ptr<TfLiteMotionPredictorModel> model =
+ TfLiteMotionPredictorModel::create(getModelPath().c_str());
+ TfLiteMotionPredictorBuffers buffers(model->inputLength());
+
+ buffers.pushSample(/*timestamp=*/1,
+ {.position = {.x = 10, .y = 10},
+ .pressure = 0,
+ .orientation = 0,
+ .tilt = 0.2});
+ buffers.pushSample(/*timestamp=*/2,
+ {.position = {.x = 10, .y = 50},
+ .pressure = 0.4,
+ .orientation = M_PI / 4,
+ .tilt = 0.3});
+ buffers.pushSample(/*timestamp=*/3,
+ {.position = {.x = 30, .y = 50},
+ .pressure = 0.5,
+ .orientation = -M_PI / 4,
+ .tilt = 0.4});
+ buffers.pushSample(/*timestamp=*/3,
+ {.position = {.x = 30, .y = 60},
+ .pressure = 0,
+ .orientation = 0,
+ .tilt = 0.5});
+ buffers.copyTo(*model);
+
+ const int zeroPadding = model->inputLength() - 3;
+ ASSERT_GE(zeroPadding, 0);
+
+ EXPECT_THAT(model->inputR().subspan(0, zeroPadding), Each(0));
+ EXPECT_THAT(model->inputPhi().subspan(0, zeroPadding), Each(0));
+ EXPECT_THAT(model->inputPressure().subspan(0, zeroPadding), Each(0));
+ EXPECT_THAT(model->inputTilt().subspan(0, zeroPadding), Each(0));
+ EXPECT_THAT(model->inputOrientation().subspan(0, zeroPadding), Each(0));
+
+ EXPECT_THAT(model->inputR().subspan(zeroPadding), ElementsAre(40, 20, 10));
+ EXPECT_THAT(model->inputPhi().subspan(zeroPadding), ElementsAre(0, -M_PI / 2, M_PI / 2));
+ EXPECT_THAT(model->inputPressure().subspan(zeroPadding), ElementsAre(0.4, 0.5, 0));
+ EXPECT_THAT(model->inputTilt().subspan(zeroPadding), ElementsAre(0.3, 0.4, 0.5));
+ EXPECT_THAT(model->inputOrientation().subspan(zeroPadding),
+ ElementsAre(FloatNear(-M_PI / 4, 1e-5), FloatNear(M_PI / 4, 1e-5),
+ FloatNear(M_PI / 2, 1e-5)));
+}
+
+TEST(TfLiteMotionPredictorTest, ModelInputOutputLength) {
+ std::unique_ptr<TfLiteMotionPredictorModel> model =
+ TfLiteMotionPredictorModel::create(getModelPath().c_str());
+ ASSERT_GT(model->inputLength(), 0u);
+
+ const int inputLength = model->inputLength();
+ ASSERT_EQ(inputLength, model->inputR().size());
+ ASSERT_EQ(inputLength, model->inputPhi().size());
+ ASSERT_EQ(inputLength, model->inputPressure().size());
+ ASSERT_EQ(inputLength, model->inputOrientation().size());
+ ASSERT_EQ(inputLength, model->inputTilt().size());
+
+ ASSERT_TRUE(model->invoke());
+
+ ASSERT_EQ(model->outputR().size(), model->outputPhi().size());
+ ASSERT_EQ(model->outputR().size(), model->outputPressure().size());
+}
+
+TEST(TfLiteMotionPredictorTest, ModelOutput) {
+ std::unique_ptr<TfLiteMotionPredictorModel> model =
+ TfLiteMotionPredictorModel::create(getModelPath().c_str());
+ TfLiteMotionPredictorBuffers buffers(model->inputLength());
+
+ buffers.pushSample(/*timestamp=*/1, {.position = {.x = 100, .y = 200}, .pressure = 0.2});
+ buffers.pushSample(/*timestamp=*/2, {.position = {.x = 150, .y = 250}, .pressure = 0.4});
+ buffers.pushSample(/*timestamp=*/3, {.position = {.x = 180, .y = 280}, .pressure = 0.6});
+ buffers.copyTo(*model);
+
+ ASSERT_TRUE(model->invoke());
+
+ // The actual model output is implementation-defined, but it should at least be non-zero and
+ // non-NaN.
+ const auto is_valid = [](float value) { return !isnan(value) && value != 0; };
+ ASSERT_TRUE(std::all_of(model->outputR().begin(), model->outputR().end(), is_valid));
+ ASSERT_TRUE(std::all_of(model->outputPhi().begin(), model->outputPhi().end(), is_valid));
+ ASSERT_TRUE(
+ std::all_of(model->outputPressure().begin(), model->outputPressure().end(), is_valid));
+}
+
+} // namespace
+} // namespace android
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp
index 01318dc..78d1912 100644
--- a/libs/jpegrecoverymap/Android.bp
+++ b/libs/jpegrecoverymap/Android.bp
@@ -29,9 +29,9 @@
local_include_dirs: ["include"],
srcs: [
- "recoverymap.cpp",
+ "jpegr.cpp",
"recoverymapmath.cpp",
- "recoverymaputils.cpp",
+ "jpegrutils.cpp",
],
shared_libs: [
@@ -41,6 +41,8 @@
"libjpegdecoder",
"liblog",
],
+
+ static_libs: ["libskia"],
}
cc_library {
@@ -55,7 +57,7 @@
export_include_dirs: ["include"],
srcs: [
- "jpegencoder.cpp",
+ "jpegencoderhelper.cpp",
],
}
@@ -71,6 +73,6 @@
export_include_dirs: ["include"],
srcs: [
- "jpegdecoder.cpp",
+ "jpegdecoderhelper.cpp",
],
}
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h
similarity index 88%
rename from libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
rename to libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h
index d0de48f..8748237 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h
@@ -1,4 +1,3 @@
-
/*
* Copyright 2022 The Android Open Source Project
*
@@ -15,8 +14,8 @@
* limitations under the License.
*/
-#ifndef ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H
-#define ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H
+#ifndef ANDROID_JPEGRECOVERYMAP_JPEGDECODERHELPER_H
+#define ANDROID_JPEGRECOVERYMAP_JPEGDECODERHELPER_H
// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
#include <cstdio>
@@ -26,15 +25,15 @@
}
#include <utils/Errors.h>
#include <vector>
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
/*
* Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format.
* This class is not thread-safe.
*/
-class JpegDecoder {
+class JpegDecoderHelper {
public:
- JpegDecoder();
- ~JpegDecoder();
+ JpegDecoderHelper();
+ ~JpegDecoderHelper();
/*
* Decompresses JPEG image to raw image (YUV420planer, grey-scale or RGBA) format. After
* calling this method, call getDecompressedImage() to get the image.
@@ -47,7 +46,7 @@
*/
void* getDecompressedImagePtr();
/*
- * Returns the decompressed raw image buffer size. This mgit ethod must be called only after
+ * Returns the decompressed raw image buffer size. This method must be called only after
* calling decompressImage().
*/
size_t getDecompressedImageSize();
@@ -92,10 +91,6 @@
size_t* pWidth, size_t* pHeight,
std::vector<uint8_t>* iccData,
std::vector<uint8_t>* exifData);
- /*
- * Extracts EXIF package and updates the EXIF position / length without decoding the image.
- */
- bool extractEXIF(const void* image, int length);
private:
bool decode(const void* image, int length, bool decodeToRGBA);
@@ -120,6 +115,6 @@
// Position of EXIF package, default value is -1 which means no EXIF package appears.
size_t mExifPos;
};
-} /* namespace android */
+} /* namespace android::jpegrecoverymap */
-#endif // ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H
+#endif // ANDROID_JPEGRECOVERYMAP_JPEGDECODERHELPER_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h
similarity index 91%
rename from libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h
rename to libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h
index 61aeb8a..8b82b2b 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H
-#define ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H
+#ifndef ANDROID_JPEGRECOVERYMAP_JPEGENCODERHELPER_H
+#define ANDROID_JPEGRECOVERYMAP_JPEGENCODERHELPER_H
// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
#include <cstdio>
@@ -28,16 +28,16 @@
#include <utils/Errors.h>
#include <vector>
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
/*
* Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format.
* This class is not thread-safe.
*/
-class JpegEncoder {
+class JpegEncoderHelper {
public:
- JpegEncoder();
- ~JpegEncoder();
+ JpegEncoderHelper();
+ ~JpegEncoderHelper();
/*
* Compresses YUV420Planer image to JPEG format. After calling this method, call
@@ -90,6 +90,6 @@
std::vector<JOCTET> mResultBuffer;
};
-} /* namespace android */
+} /* namespace android::jpegrecoverymap */
-#endif // ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H
+#endif // ANDROID_JPEGRECOVERYMAP_JPEGENCODERHELPER_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h
similarity index 86%
rename from libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
rename to libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h
index ae15d24..5455ba6 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H
-#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H
+#ifndef ANDROID_JPEGRECOVERYMAP_JPEGR_H
+#define ANDROID_JPEGRECOVERYMAP_JPEGR_H
#include "jpegrerrorcode.h"
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
+// Color gamuts for image data
typedef enum {
JPEGR_COLORGAMUT_UNSPECIFIED,
JPEGR_COLORGAMUT_BT709,
@@ -28,12 +29,13 @@
JPEGR_COLORGAMUT_BT2100,
} jpegr_color_gamut;
-// Transfer functions as defined for XMP metadata
+// Transfer functions for image data
typedef enum {
JPEGR_TF_UNSPECIFIED = -1,
JPEGR_TF_LINEAR = 0,
JPEGR_TF_HLG = 1,
JPEGR_TF_PQ = 2,
+ JPEGR_TF_SRGB = 3,
} jpegr_transfer_function;
struct jpegr_info_struct {
@@ -81,45 +83,13 @@
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;
+ // Max Content Boost for the map
+ float maxContentBoost;
+ // Min Content Boost for the map
+ float minContentBoost;
};
typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr;
@@ -128,7 +98,7 @@
typedef struct jpegr_metadata* jr_metadata_ptr;
typedef struct jpegr_info_struct* jr_info_ptr;
-class RecoveryMap {
+class JpegR {
public:
/*
* Encode API-0
@@ -251,17 +221,7 @@
*/
status_t getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
jr_info_ptr jpegr_info);
-private:
- /*
- * This method is called in the encoding pipeline. It will encode the recovery map.
- *
- * @param uncompressed_recovery_map uncompressed recovery map
- * @param dest encoded recover map
- * @return NO_ERROR if encoding succeeds, error code if error occurs.
- */
- status_t compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
- jr_compressed_ptr dest);
-
+protected:
/*
* This method is called in the encoding pipeline. It will take the uncompressed 8-bit and
* 10-bit yuv images as input, and calculate the uncompressed recovery map. The input images
@@ -269,14 +229,14 @@
*
* @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 hdr_tf transfer function of the HDR image
* @param dest recovery map; caller responsible for memory of data
- * @param metadata metadata provides the transfer function for the HDR
- * image; range_scaling_factor and hdr10 FALL and CLL will
- * be updated.
+ * @param metadata max_content_boost is filled in
* @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,
+ jpegr_transfer_function hdr_tf,
jr_metadata_ptr metadata,
jr_uncompressed_ptr dest);
@@ -284,8 +244,7 @@
* This method is called in the decoding pipeline. It will take the uncompressed (decoded)
* 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.
+ * color gamut as the SDR image, with HLG transfer function, 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
@@ -298,6 +257,17 @@
jr_metadata_ptr metadata,
jr_uncompressed_ptr dest);
+private:
+ /*
+ * This method is called in the encoding pipeline. It will encode the recovery map.
+ *
+ * @param uncompressed_recovery_map uncompressed recovery map
+ * @param dest encoded recover map
+ * @return NO_ERROR if encoding succeeds, error code if error occurs.
+ */
+ status_t compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
+ jr_compressed_ptr dest);
+
/*
* This methoud is called to separate primary image and recovery map image from JPEGR
*
@@ -321,13 +291,17 @@
jr_compressed_ptr dest);
/*
- * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image
- * and the compressed recovery map as input, and update the XMP metadata with the end of JPEG
- * marker, and append the compressed gian map after the JPEG.
+ * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image,
+ * the compressed recovery map and optionally the exif package as inputs, and generate the XMP
+ * metadata, and finally append everything in the order of:
+ * SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, recovery map
+ * Note that EXIF package is only available for encoding API-0 and API-1. For encoding API-2 and
+ * API-3 this parameter is null, but the primary image in JPEG/R may still have EXIF as long as
+ * the input JPEG has EXIF.
*
* @param compressed_jpeg_image compressed 8-bit JPEG image
* @param compress_recovery_map compressed recover map
- * @param exif EXIF package
+ * @param (nullable) exif EXIF package
* @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.
@@ -349,6 +323,6 @@
jr_uncompressed_ptr dest);
};
-} // namespace android::recoverymap
+} // namespace android::jpegrecoverymap
-#endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H
+#endif // ANDROID_JPEGRECOVERYMAP_JPEGR_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
index 699c0d3..f730343 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
@@ -16,7 +16,7 @@
#include <utils/Errors.h>
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
enum {
// status_t map for errors in the media framework
@@ -48,4 +48,4 @@
ERROR_JPEGR_TONEMAP_ERROR = JPEGR_RUNTIME_ERROR_BASE - 5,
};
-} // namespace android::recoverymap
+} // namespace android::jpegrecoverymap
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h
new file mode 100644
index 0000000..3a0f67d
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h
@@ -0,0 +1,97 @@
+/*
+ * 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_JPEGRUTILS_H
+#define ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H
+
+#include <jpegrecoverymap/jpegr.h>
+
+#include <sstream>
+#include <stdint.h>
+#include <string>
+#include <cstdio>
+
+namespace android::jpegrecoverymap {
+
+struct jpegr_metadata;
+
+/*
+ * Helper function used for writing data to destination.
+ *
+ * @param destination destination of the data to be written.
+ * @param source source of data being written.
+ * @param length length of the data to be written.
+ * @param position cursor in desitination where the data is to be written.
+ * @return status of succeed or error code.
+ */
+status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position);
+
+
+/*
+ * 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);
+
+/*
+ * This method generates XMP metadata.
+ *
+ * below is an example of the XMP metadata that this function generates where
+ * secondary_image_length = 1000
+ * max_content_boost = 8.0
+ *
+ * <x:xmpmeta
+ * xmlns:x="adobe:ns:meta/"
+ * x:xmptk="Adobe XMP Core 5.1.2">
+ * <rdf:RDF
+ * xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ * <rdf:Description
+ * xmlns:Container="http://ns.google.com/photos/1.0/container/"
+ * xmlns:Item="http://ns.google.com/photos/1.0/container/item/"
+ * xmlns:RecoveryMap="http://ns.google.com/photos/1.0/recoverymap/">
+ * <Container:Directory>
+ * <rdf:Seq>
+ * <rdf:li>
+ * <Container:Item
+ * Item:Semantic="Primary"
+ * Item:Mime="image/jpeg"
+ * RecoveryMap:Version="1"
+ * RecoveryMap:MaxContentBoost="8.0"/>
+ * </rdf:li>
+ * <rdf:li>
+ * <Container:Item
+ * Item:Semantic="RecoveryMap"
+ * Item:Mime="image/jpeg"
+ * Item:Length="1000"/>
+ * </rdf:li>
+ * </rdf:Seq>
+ * </Container:Directory>
+ * </rdf:Description>
+ * </rdf:RDF>
+ * </x:xmpmeta>
+ *
+ * @param secondary_image_length length of secondary image
+ * @param metadata JPEG/R metadata to encode as XMP
+ * @return XMP metadata in type of string
+ */
+std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata);
+} // namespace android::jpegrecoverymap
+
+#endif //ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
index 0695bb7..c12cee9 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
@@ -20,9 +20,9 @@
#include <cmath>
#include <stdint.h>
-#include <jpegrecoverymap/recoverymap.h>
+#include <jpegrecoverymap/jpegr.h>
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
#define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x)
@@ -118,11 +118,12 @@
constexpr size_t kRecoveryFactorPrecision = 10;
constexpr size_t kRecoveryFactorNumEntries = 1 << kRecoveryFactorPrecision;
struct RecoveryLUT {
- RecoveryLUT(float hdrRatio) {
- float increment = 2.0 / kRecoveryFactorNumEntries;
- float value = -1.0f;
- for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++, value += increment) {
- mRecoveryTable[idx] = pow(hdrRatio, value);
+ RecoveryLUT(jr_metadata_ptr metadata) {
+ for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kRecoveryFactorNumEntries - 1);
+ float logBoost = log2(metadata->minContentBoost) * (1.0f - value)
+ + log2(metadata->maxContentBoost) * value;
+ mRecoveryTable[idx] = exp2(logBoost);
}
}
@@ -130,10 +131,10 @@
}
float getRecoveryFactor(float recovery) {
- uint32_t value = static_cast<uint32_t>(((recovery + 1.0f) / 2.0f) * kRecoveryFactorNumEntries);
+ uint32_t idx = static_cast<uint32_t>(recovery * (kRecoveryFactorNumEntries - 1));
//TODO() : Remove once conversion modules have appropriate clamping in place
- value = CLIP3(value, 0, kRecoveryFactorNumEntries - 1);
- return mRecoveryTable[value];
+ idx = CLIP3(idx, 0, kRecoveryFactorNumEntries - 1);
+ return mRecoveryTable[idx];
}
private:
@@ -219,6 +220,9 @@
float srgbInvOetfLUT(float e_gamma);
Color srgbInvOetfLUT(Color e_gamma);
+constexpr size_t kSrgbInvOETFPrecision = 10;
+constexpr size_t kSrgbInvOETFNumEntries = 1 << kSrgbInvOETFPrecision;
+
////////////////////////////////////////////////////////////////////////////////
// Display-P3 transformations
@@ -260,6 +264,9 @@
float hlgOetfLUT(float e);
Color hlgOetfLUT(Color e);
+constexpr size_t kHlgOETFPrecision = 10;
+constexpr size_t kHlgOETFNumEntries = 1 << kHlgOETFPrecision;
+
/*
* Convert from HLG to scene luminance.
*
@@ -270,6 +277,9 @@
float hlgInvOetfLUT(float e_gamma);
Color hlgInvOetfLUT(Color e_gamma);
+constexpr size_t kHlgInvOETFPrecision = 10;
+constexpr size_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision;
+
/*
* Convert from scene luminance to PQ.
*
@@ -280,6 +290,9 @@
float pqOetfLUT(float e);
Color pqOetfLUT(Color e);
+constexpr size_t kPqOETFPrecision = 10;
+constexpr size_t kPqOETFNumEntries = 1 << kPqOETFPrecision;
+
/*
* Convert from PQ to scene luminance in nits.
*
@@ -290,6 +303,9 @@
float pqInvOetfLUT(float e_gamma);
Color pqInvOetfLUT(Color e_gamma);
+constexpr size_t kPqInvOETFPrecision = 10;
+constexpr size_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision;
+
////////////////////////////////////////////////////////////////////////////////
// Color space conversions
@@ -326,13 +342,13 @@
* Calculate the 8-bit unsigned integer recovery value for the given SDR and HDR
* luminances in linear space, and the hdr ratio to encode against.
*/
-uint8_t encodeRecovery(float y_sdr, float y_hdr, float hdr_ratio);
+uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata);
/*
* Calculates the linear luminance in nits after applying the given recovery
* value, with the given hdr ratio, to the given sdr input in the range [0, 1].
*/
-Color applyRecovery(Color e, float recovery, float hdr_ratio);
+Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata);
Color applyRecoveryLUT(Color e, float recovery, RecoveryLUT& recoveryLUT);
/*
@@ -376,6 +392,6 @@
*/
uint32_t colorToRgba1010102(Color e_gamma);
-} // namespace android::recoverymap
+} // namespace android::jpegrecoverymap
#endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
deleted file mode 100644
index 8b2672f..0000000
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
-#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
-
-#include <jpegrecoverymap/recoverymap.h>
-
-#include <sstream>
-#include <stdint.h>
-#include <string>
-#include <cstdio>
-
-namespace android::recoverymap {
-
-struct jpegr_metadata;
-
-// If the EXIF package doesn't exist in the input JPEG, we'll create one with one entry
-// where the length is represented by this value.
-const size_t PSEUDO_EXIF_PACKAGE_LENGTH = 28;
-// If the EXIF package exists in the input JPEG, we'll add an "JR" entry where the length is
-// represented by this value.
-const size_t EXIF_J_R_ENTRY_LENGTH = 12;
-
-/*
- * Helper function used for writing data to destination.
- *
- * @param destination destination of the data to be written.
- * @param source source of data being written.
- * @param length length of the data to be written.
- * @param position cursor in desitination where the data is to be written.
- * @return status of succeed or error code.
- */
-status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position);
-status_t Write(jr_exif_ptr destination, const void* source, size_t length, int &position);
-
-
-/*
- * 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);
-
-/*
- * This method generates XMP metadata.
- *
- * below is an example of the XMP metadata that this function generates where
- * secondary_image_length = 1000
- * range_scaling_factor = 1.25
- *
- * <x:xmpmeta
- * xmlns:x="adobe:ns:meta/"
- * x:xmptk="Adobe XMP Core 5.1.2">
- * <rdf:RDF
- * xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
- * <rdf:Description
- * xmlns:GContainer="http://ns.google.com/photos/1.0/container/"
- * xmlns:RecoveryMap="http://ns.google.com/photos/1.0/recoverymap/">
- * <GContainer:Version>1</GContainer:Version>
- * <GContainer:Directory>
- * <rdf:Seq>
- * <rdf:li>
- * <GContainer:Item
- * GContainer:ItemSemantic="Primary"
- * GContainer:ItemMime="image/jpeg"
- * RecoveryMap:Version=”1”
- * RecoveryMap:RangeScalingFactor=”1.25”
- * RecoveryMap:TransferFunction=”2”/>
- * <RecoveryMap:HDR10Metadata
- * // some attributes
- * // some elements
- * </RecoveryMap:HDR10Metadata>
- * </rdf:li>
- * <rdf:li>
- * <GContainer:Item
- * GContainer:ItemSemantic="RecoveryMap"
- * GContainer:ItemMime="image/jpeg"
- * GContainer:ItemLength="1000"/>
- * </rdf:li>
- * </rdf:Seq>
- * </GContainer:Directory>
- * </rdf:Description>
- * </rdf:RDF>
- * </x:xmpmeta>
- *
- * @param secondary_image_length length of secondary image
- * @param metadata JPEG/R metadata to encode as XMP
- * @return XMP metadata in type of string
- */
-std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata);
-
-/*
- * Add J R entry to existing exif, or create a new one with J R entry if it's null.
- * EXIF syntax / change:
- * ori:
- * FF E1 - APP1
- * 01 FC - size of APP1 (to be calculated)
- * -----------------------------------------------------
- * 45 78 69 66 00 00 - Exif\0\0 "Exif header"
- * 49 49 2A 00 - TIFF Header
- * 08 00 00 00 - offset to the IFD (image file directory)
- * 06 00 - 6 entries
- * 00 01 - Width Tag
- * 03 00 - 'Short' type
- * 01 00 00 00 - 1 component
- * 00 05 00 00 - image with 0x500
- *--------------------------------------------------------------------------
- * new:
- * FF E1 - APP1
- * 02 08 - new size, equals to old size + EXIF_J_R_ENTRY_LENGTH (12)
- *-----------------------------------------------------
- * 45 78 69 66 00 00 - Exif\0\0 "Exif header"
- * 49 49 2A 00 - TIFF Header
- * 08 00 00 00 - offset to the IFD (image file directory)
- * 07 00 - +1 entry
- * 4A 52 Custom ('J''R') Tag
- * 07 00 - Unknown type
- * 01 00 00 00 - 1 component
- * 00 00 00 00 - empty data
- * 00 01 - Width Tag
- * 03 00 - 'Short' type
- * 01 00 00 00 - 1 component
- * 00 05 00 00 - image with 0x500
- */
-status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest);
-
-/*
- * Modify offsets in EXIF in place.
- *
- * Each tag has the following structure:
- *
- * 00 01 - Tag
- * 03 00 - data format
- * 01 00 00 00 - number of components
- * 00 05 00 00 - value
- *
- * The value means offset if
- * (1) num_of_components * bytes_per_component > 4 bytes, or
- * (2) tag == 0x8769 (ExifOffset).
- * In both cases, the method will add EXIF_J_R_ENTRY_LENGTH (12) to the offsets.
- */
-void updateExifOffsets(jr_exif_ptr exif, int pos, bool use_big_endian);
-void updateExifOffsets(jr_exif_ptr exif, int pos, int num_entry, bool use_big_endian);
-
-/*
- * Read data from the target position and target length in bytes;
- */
-int readValue(uint8_t* data, int pos, int length, bool use_big_endian);
-
-/*
- * Returns the length of data format in bytes
- *
- * ----------------------------------------------------------------------------------------------
- * | value | 1 | 2 | 3 | 4 |
- * | format | unsigned byte | ascii strings | unsigned short | unsigned long |
- * | bytes/component | 1 | 1 | 2 | 4 |
- * ----------------------------------------------------------------------------------------------
- * | value | 5 | 6 | 7 | 8 |
- * | format |unsigned rational| signed byte | undefined | signed short |
- * | bytes/component | 8 | 1 | 1 | 2 |
- * ----------------------------------------------------------------------------------------------
- * | value | 9 | 10 | 11 | 12 |
- * | format | signed long | signed rational | single float | double float |
- * | bytes/component | 4 | 8 | 4 | 8 |
- * ----------------------------------------------------------------------------------------------
- */
-int findFormatLengthInBytes(int data_format);
-}
-
-#endif //ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoderhelper.cpp
similarity index 81%
rename from libs/jpegrecoverymap/jpegdecoder.cpp
rename to libs/jpegrecoverymap/jpegdecoderhelper.cpp
index 6fbc6b0..d36bbf8 100644
--- a/libs/jpegrecoverymap/jpegdecoder.cpp
+++ b/libs/jpegrecoverymap/jpegdecoderhelper.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include <jpegrecoverymap/jpegdecoder.h>
+#include <jpegrecoverymap/jpegdecoderhelper.h>
#include <utils/Log.h>
@@ -24,7 +24,7 @@
using namespace std;
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
const uint32_t kAPP0Marker = JPEG_APP0; // JFIF
const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP
@@ -90,14 +90,14 @@
longjmp(err->setjmp_buffer, 1);
}
-JpegDecoder::JpegDecoder() {
+JpegDecoderHelper::JpegDecoderHelper() {
mExifPos = 0;
}
-JpegDecoder::~JpegDecoder() {
+JpegDecoderHelper::~JpegDecoderHelper() {
}
-bool JpegDecoder::decompressImage(const void* image, int length, bool decodeToRGBA) {
+bool JpegDecoderHelper::decompressImage(const void* image, int length, bool decodeToRGBA) {
if (image == nullptr || length <= 0) {
ALOGE("Image size can not be handled: %d", length);
return false;
@@ -112,39 +112,39 @@
return true;
}
-void* JpegDecoder::getDecompressedImagePtr() {
+void* JpegDecoderHelper::getDecompressedImagePtr() {
return mResultBuffer.data();
}
-size_t JpegDecoder::getDecompressedImageSize() {
+size_t JpegDecoderHelper::getDecompressedImageSize() {
return mResultBuffer.size();
}
-void* JpegDecoder::getXMPPtr() {
+void* JpegDecoderHelper::getXMPPtr() {
return mXMPBuffer.data();
}
-size_t JpegDecoder::getXMPSize() {
+size_t JpegDecoderHelper::getXMPSize() {
return mXMPBuffer.size();
}
-void* JpegDecoder::getEXIFPtr() {
+void* JpegDecoderHelper::getEXIFPtr() {
return mEXIFBuffer.data();
}
-size_t JpegDecoder::getEXIFSize() {
+size_t JpegDecoderHelper::getEXIFSize() {
return mEXIFBuffer.size();
}
-size_t JpegDecoder::getDecompressedImageWidth() {
+size_t JpegDecoderHelper::getDecompressedImageWidth() {
return mWidth;
}
-size_t JpegDecoder::getDecompressedImageHeight() {
+size_t JpegDecoderHelper::getDecompressedImageHeight() {
return mHeight;
}
-bool JpegDecoder::decode(const void* image, int length, bool decodeToRGBA) {
+bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) {
jpeg_decompress_struct cinfo;
jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
jpegrerror_mgr myerr;
@@ -248,61 +248,7 @@
return true;
}
-// TODO (Fyodor/Dichen): merge this method with getCompressedImageParameters() since they have
-// similar functionality. Yet Dichen is not familiar with who's calling
-// getCompressedImageParameters(), looks like it's used by some pending CLs.
-bool JpegDecoder::extractEXIF(const void* image, int length) {
- 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, kAPP0Marker, 0xFFFF);
- jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
- jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
-
- cinfo.src = &mgr;
- jpeg_read_header(&cinfo, TRUE);
-
- bool exifAppears = false;
- size_t pos = 2; // position after SOI
- for (jpeg_marker_struct* marker = cinfo.marker_list;
- marker && !exifAppears;
- marker = marker->next) {
-
- pos += 4;
- pos += marker->original_length;
-
- if (marker->marker != kAPP1Marker) {
- continue;
- }
-
- const unsigned int len = marker->data_length;
- if (!exifAppears &&
- len > kExifIdCode.size() &&
- !strncmp(reinterpret_cast<const char*>(marker->data),
- kExifIdCode.c_str(),
- kExifIdCode.size())) {
- mEXIFBuffer.resize(len, 0);
- memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
- exifAppears = true;
- mExifPos = pos - marker->original_length;
- }
- }
-
- jpeg_destroy_decompress(&cinfo);
- return true;
-}
-
-bool JpegDecoder::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest,
+bool JpegDecoderHelper::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest,
bool isSingleChannel) {
if (isSingleChannel) {
return decompressSingleChannel(cinfo, dest);
@@ -313,7 +259,7 @@
return decompressYUV(cinfo, dest);
}
-bool JpegDecoder::getCompressedImageParameters(const void* image, int length,
+bool JpegDecoderHelper::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;
@@ -380,7 +326,7 @@
return true;
}
-bool JpegDecoder::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+bool JpegDecoderHelper::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
JSAMPLE* decodeDst = (JSAMPLE*) dest;
uint32_t lines = 0;
// TODO: use batches for more effectiveness
@@ -395,7 +341,7 @@
return lines == cinfo->image_height;
}
-bool JpegDecoder::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
JSAMPROW y[kCompressBatchSize];
JSAMPROW cb[kCompressBatchSize / 2];
@@ -440,7 +386,7 @@
return true;
}
-bool JpegDecoder::decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
JSAMPROW y[kCompressBatchSize];
JSAMPARRAY planes[1] {y};
@@ -467,4 +413,4 @@
return true;
}
-} // namespace android
+} // namespace jpegrecoverymap
diff --git a/libs/jpegrecoverymap/jpegencoder.cpp b/libs/jpegrecoverymap/jpegencoderhelper.cpp
similarity index 86%
rename from libs/jpegrecoverymap/jpegencoder.cpp
rename to libs/jpegrecoverymap/jpegencoderhelper.cpp
index 627dcdf..586cd34 100644
--- a/libs/jpegrecoverymap/jpegencoder.cpp
+++ b/libs/jpegrecoverymap/jpegencoderhelper.cpp
@@ -14,28 +14,28 @@
* limitations under the License.
*/
-#include <jpegrecoverymap/jpegencoder.h>
+#include <jpegrecoverymap/jpegencoderhelper.h>
#include <utils/Log.h>
#include <errno.h>
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
-// The destination manager that can access |mResultBuffer| in JpegEncoder.
+// The destination manager that can access |mResultBuffer| in JpegEncoderHelper.
struct destination_mgr {
public:
struct jpeg_destination_mgr mgr;
- JpegEncoder* encoder;
+ JpegEncoderHelper* encoder;
};
-JpegEncoder::JpegEncoder() {
+JpegEncoderHelper::JpegEncoderHelper() {
}
-JpegEncoder::~JpegEncoder() {
+JpegEncoderHelper::~JpegEncoderHelper() {
}
-bool JpegEncoder::compressImage(const void* image, int width, int height, int quality,
+bool JpegEncoderHelper::compressImage(const void* image, int width, int height, int quality,
const void* iccBuffer, unsigned int iccSize,
bool isSingleChannel) {
if (width % 8 != 0 || height % 2 != 0) {
@@ -52,15 +52,15 @@
return true;
}
-void* JpegEncoder::getCompressedImagePtr() {
+void* JpegEncoderHelper::getCompressedImagePtr() {
return mResultBuffer.data();
}
-size_t JpegEncoder::getCompressedImageSize() {
+size_t JpegEncoderHelper::getCompressedImageSize() {
return mResultBuffer.size();
}
-void JpegEncoder::initDestination(j_compress_ptr cinfo) {
+void JpegEncoderHelper::initDestination(j_compress_ptr cinfo) {
destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
buffer.resize(kBlockSize);
@@ -68,7 +68,7 @@
dest->mgr.free_in_buffer = buffer.size();
}
-boolean JpegEncoder::emptyOutputBuffer(j_compress_ptr cinfo) {
+boolean JpegEncoderHelper::emptyOutputBuffer(j_compress_ptr cinfo) {
destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
size_t oldsize = buffer.size();
@@ -78,13 +78,13 @@
return true;
}
-void JpegEncoder::terminateDestination(j_compress_ptr cinfo) {
+void JpegEncoderHelper::terminateDestination(j_compress_ptr cinfo) {
destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
buffer.resize(buffer.size() - dest->mgr.free_in_buffer);
}
-void JpegEncoder::outputErrorMessage(j_common_ptr cinfo) {
+void JpegEncoderHelper::outputErrorMessage(j_common_ptr cinfo) {
char buffer[JMSG_LENGTH_MAX];
/* Create the message */
@@ -92,7 +92,7 @@
ALOGE("%s\n", buffer);
}
-bool JpegEncoder::encode(const void* image, int width, int height, int jpegQuality,
+bool JpegEncoderHelper::encode(const void* image, int width, int height, int jpegQuality,
const void* iccBuffer, unsigned int iccSize, bool isSingleChannel) {
jpeg_compress_struct cinfo;
jpeg_error_mgr jerr;
@@ -118,7 +118,7 @@
return true;
}
-void JpegEncoder::setJpegDestination(jpeg_compress_struct* cinfo) {
+void JpegEncoderHelper::setJpegDestination(jpeg_compress_struct* cinfo) {
destination_mgr* dest = static_cast<struct destination_mgr *>((*cinfo->mem->alloc_small) (
(j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(destination_mgr)));
dest->encoder = this;
@@ -128,7 +128,7 @@
cinfo->dest = reinterpret_cast<struct jpeg_destination_mgr*>(dest);
}
-void JpegEncoder::setJpegCompressStruct(int width, int height, int quality,
+void JpegEncoderHelper::setJpegCompressStruct(int width, int height, int quality,
jpeg_compress_struct* cinfo, bool isSingleChannel) {
cinfo->image_width = width;
cinfo->image_height = height;
@@ -158,7 +158,7 @@
}
}
-bool JpegEncoder::compress(
+bool JpegEncoderHelper::compress(
jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel) {
if (isSingleChannel) {
return compressSingleChannel(cinfo, image);
@@ -166,7 +166,7 @@
return compressYuv(cinfo, image);
}
-bool JpegEncoder::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) {
+bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) {
JSAMPROW y[kCompressBatchSize];
JSAMPROW cb[kCompressBatchSize / 2];
JSAMPROW cr[kCompressBatchSize / 2];
@@ -210,7 +210,7 @@
return true;
}
-bool JpegEncoder::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image) {
+bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image) {
JSAMPROW y[kCompressBatchSize];
JSAMPARRAY planes[1] {y};
@@ -236,4 +236,4 @@
return true;
}
-} // namespace android
+} // namespace jpegrecoverymap
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/jpegr.cpp
similarity index 75%
rename from libs/jpegrecoverymap/recoverymap.cpp
rename to libs/jpegrecoverymap/jpegr.cpp
index 22289de..bd8874e 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/jpegr.cpp
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-#include <jpegrecoverymap/recoverymap.h>
-#include <jpegrecoverymap/jpegencoder.h>
-#include <jpegrecoverymap/jpegdecoder.h>
+#include <jpegrecoverymap/jpegr.h>
+#include <jpegrecoverymap/jpegencoderhelper.h>
+#include <jpegrecoverymap/jpegdecoderhelper.h>
#include <jpegrecoverymap/recoverymapmath.h>
-#include <jpegrecoverymap/recoverymaputils.h>
+#include <jpegrecoverymap/jpegrutils.h>
#include <image_io/jpeg/jpeg_marker.h>
#include <image_io/jpeg/jpeg_info.h>
@@ -26,7 +26,10 @@
#include <image_io/jpeg/jpeg_info_builder.h>
#include <image_io/base/data_segment_data_source.h>
#include <utils/Log.h>
+#include "SkColorSpace.h"
+#include "SkICC.h"
+#include <map>
#include <memory>
#include <sstream>
#include <string>
@@ -40,7 +43,7 @@
using namespace std;
using namespace photos_editing_formats::image_io;
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
#define USE_SRGB_INVOETF_LUT 1
#define USE_HLG_OETF_LUT 1
@@ -69,16 +72,6 @@
// 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,
-};
-
#define CONFIG_MULTITHREAD 1
int GetCPUCoreCount() {
int cpuCoreCount = 1;
@@ -93,33 +86,23 @@
return cpuCoreCount;
}
-/*
- * Helper function copies the JPEG image from without EXIF.
- *
- * @param dest destination of the data to be written.
- * @param source source of data being written.
- * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
- * (4 bypes offset to FF sign, the byte after FF E1 XX XX <this byte>).
- * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize().
- */
-void copyJpegWithoutExif(jr_compressed_ptr dest,
- jr_compressed_ptr source,
- size_t exif_pos,
- size_t exif_size) {
- memcpy(dest, source, sizeof(jpegr_compressed_struct));
+static const map<jpegrecoverymap::jpegr_color_gamut, skcms_Matrix3x3> jrGamut_to_skGamut {
+ {JPEGR_COLORGAMUT_BT709, SkNamedGamut::kSRGB},
+ {JPEGR_COLORGAMUT_P3, SkNamedGamut::kDisplayP3},
+ {JPEGR_COLORGAMUT_BT2100, SkNamedGamut::kRec2020},
+};
- const size_t exif_offset = 4; //exif_pos has 4 bypes offset to the FF sign
- dest->length = source->length - exif_size - exif_offset;
- dest->data = malloc(dest->length);
-
- memcpy(dest->data, source->data, exif_pos - exif_offset);
- memcpy((uint8_t*)dest->data + exif_pos - exif_offset,
- (uint8_t*)source->data + exif_pos + exif_size,
- source->length - exif_pos - exif_size);
-}
+static const map<
+ jpegrecoverymap::jpegr_transfer_function,
+ skcms_TransferFunction> jrTransFunc_to_skTransFunc {
+ {JPEGR_TF_SRGB, SkNamedTransferFn::kSRGB},
+ {JPEGR_TF_LINEAR, SkNamedTransferFn::kLinear},
+ {JPEGR_TF_HLG, SkNamedTransferFn::kHLG},
+ {JPEGR_TF_PQ, SkNamedTransferFn::kPQ},
+};
/* Encode API-0 */
-status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
jpegr_transfer_function hdr_tf,
jr_compressed_ptr dest,
int quality,
@@ -141,10 +124,6 @@
jpegr_metadata metadata;
metadata.version = kJpegrVersion;
- metadata.transferFunction = hdr_tf;
- if (hdr_tf == JPEGR_TF_PQ) {
- metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
- }
jpegr_uncompressed_struct uncompressed_yuv_420_image;
unique_ptr<uint8_t[]> uncompressed_yuv_420_image_data = make_unique<uint8_t[]>(
@@ -154,7 +133,7 @@
jpegr_uncompressed_struct map;
JPEGR_CHECK(generateRecoveryMap(
- &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
+ &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
std::unique_ptr<uint8_t[]> map_data;
map_data.reset(reinterpret_cast<uint8_t*>(map.data));
@@ -164,35 +143,28 @@
compressed_map.data = compressed_map_data.get();
JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
- JpegEncoder jpeg_encoder;
- // TODO: determine ICC data based on color gamut information
+ sk_sp<SkData> icc = SkWriteICCProfile(
+ jrTransFunc_to_skTransFunc.at(JPEGR_TF_SRGB),
+ jrGamut_to_skGamut.at(uncompressed_yuv_420_image.colorGamut));
+
+ JpegEncoderHelper jpeg_encoder;
if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data,
uncompressed_yuv_420_image.width,
- uncompressed_yuv_420_image.height, quality, nullptr, 0)) {
+ uncompressed_yuv_420_image.height, quality,
+ icc.get()->data(), icc.get()->size())) {
return ERROR_JPEGR_ENCODE_ERROR;
}
jpegr_compressed_struct jpeg;
jpeg.data = jpeg_encoder.getCompressedImagePtr();
jpeg.length = jpeg_encoder.getCompressedImageSize();
- jpegr_exif_struct new_exif;
- if (exif == nullptr || exif->data == nullptr) {
- new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
- } else {
- new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH;
- }
- new_exif.data = new uint8_t[new_exif.length];
- std::unique_ptr<uint8_t[]> new_exif_data;
- new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
- JPEGR_CHECK(updateExif(exif, &new_exif));
-
- JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest));
+ JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, exif, &metadata, dest));
return NO_ERROR;
}
/* Encode API-1 */
-status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
jr_uncompressed_ptr uncompressed_yuv_420_image,
jpegr_transfer_function hdr_tf,
jr_compressed_ptr dest,
@@ -222,14 +194,10 @@
jpegr_metadata metadata;
metadata.version = kJpegrVersion;
- metadata.transferFunction = hdr_tf;
- if (hdr_tf == JPEGR_TF_PQ) {
- metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
- }
jpegr_uncompressed_struct map;
JPEGR_CHECK(generateRecoveryMap(
- uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
+ uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
std::unique_ptr<uint8_t[]> map_data;
map_data.reset(reinterpret_cast<uint8_t*>(map.data));
@@ -239,36 +207,28 @@
compressed_map.data = compressed_map_data.get();
JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
- JpegEncoder jpeg_encoder;
- // TODO: determine ICC data based on color gamut information
+ sk_sp<SkData> icc = SkWriteICCProfile(
+ jrTransFunc_to_skTransFunc.at(JPEGR_TF_SRGB),
+ jrGamut_to_skGamut.at(uncompressed_yuv_420_image->colorGamut));
+
+ JpegEncoderHelper jpeg_encoder;
if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
uncompressed_yuv_420_image->width,
- uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
+ uncompressed_yuv_420_image->height, quality,
+ icc.get()->data(), icc.get()->size())) {
return ERROR_JPEGR_ENCODE_ERROR;
}
jpegr_compressed_struct jpeg;
jpeg.data = jpeg_encoder.getCompressedImagePtr();
jpeg.length = jpeg_encoder.getCompressedImageSize();
- jpegr_exif_struct new_exif;
- if (exif == nullptr || exif->data == nullptr) {
- new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
- } else {
- new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH;
- }
-
- new_exif.data = new uint8_t[new_exif.length];
- std::unique_ptr<uint8_t[]> new_exif_data;
- new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
- JPEGR_CHECK(updateExif(exif, &new_exif));
-
- JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest));
+ JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, exif, &metadata, dest));
return NO_ERROR;
}
/* Encode API-2 */
-status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+status_t JpegR::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,
@@ -294,14 +254,10 @@
jpegr_metadata metadata;
metadata.version = kJpegrVersion;
- metadata.transferFunction = hdr_tf;
- if (hdr_tf == JPEGR_TF_PQ) {
- metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
- }
jpegr_uncompressed_struct map;
JPEGR_CHECK(generateRecoveryMap(
- uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
+ uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
std::unique_ptr<uint8_t[]> map_data;
map_data.reset(reinterpret_cast<uint8_t*>(map.data));
@@ -311,53 +267,13 @@
compressed_map.data = compressed_map_data.get();
JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
- // Extract EXIF from JPEG without decoding.
- JpegDecoder jpeg_decoder;
- if (!jpeg_decoder.extractEXIF(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
- return ERROR_JPEGR_DECODE_ERROR;
- }
-
- // Update exif.
- jpegr_exif_struct exif;
- exif.data = nullptr;
- exif.length = 0;
- jpegr_compressed_struct new_jpeg_image;
- new_jpeg_image.data = nullptr;
- new_jpeg_image.length = 0;
- if (jpeg_decoder.getEXIFPos() != 0) {
- copyJpegWithoutExif(&new_jpeg_image,
- compressed_jpeg_image,
- jpeg_decoder.getEXIFPos(),
- jpeg_decoder.getEXIFSize());
- exif.data = jpeg_decoder.getEXIFPtr();
- exif.length = jpeg_decoder.getEXIFSize();
- }
-
- jpegr_exif_struct new_exif;
- if (exif.data == nullptr) {
- new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
- } else {
- new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH;
- }
-
- new_exif.data = new uint8_t[new_exif.length];
- std::unique_ptr<uint8_t[]> new_exif_data;
- new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
- JPEGR_CHECK(updateExif(&exif, &new_exif));
-
- JPEGR_CHECK(appendRecoveryMap(
- new_jpeg_image.data == nullptr ? compressed_jpeg_image : &new_jpeg_image,
- &compressed_map, &new_exif, &metadata, dest));
-
- if (new_jpeg_image.data != nullptr) {
- free(new_jpeg_image.data);
- }
+ JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
return NO_ERROR;
}
/* Encode API-3 */
-status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
jr_compressed_ptr compressed_jpeg_image,
jpegr_transfer_function hdr_tf,
jr_compressed_ptr dest) {
@@ -374,7 +290,7 @@
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
- JpegDecoder jpeg_decoder;
+ JpegDecoderHelper jpeg_decoder;
if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
return ERROR_JPEGR_DECODE_ERROR;
}
@@ -384,33 +300,6 @@
uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
- // Update exif.
- jpegr_exif_struct exif;
- exif.data = nullptr;
- exif.length = 0;
- jpegr_compressed_struct new_jpeg_image;
- new_jpeg_image.data = nullptr;
- new_jpeg_image.length = 0;
- if (jpeg_decoder.getEXIFPos() != 0) {
- copyJpegWithoutExif(&new_jpeg_image,
- compressed_jpeg_image,
- jpeg_decoder.getEXIFPos(),
- jpeg_decoder.getEXIFSize());
- exif.data = jpeg_decoder.getEXIFPtr();
- exif.length = jpeg_decoder.getEXIFSize();
- }
-
- jpegr_exif_struct new_exif;
- if (exif.data == nullptr) {
- new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
- } else {
- new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH;
- }
- new_exif.data = new uint8_t[new_exif.length];
- std::unique_ptr<uint8_t[]> new_exif_data;
- new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
- JPEGR_CHECK(updateExif(&exif, &new_exif));
-
if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
|| uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
return ERROR_JPEGR_RESOLUTION_MISMATCH;
@@ -418,14 +307,10 @@
jpegr_metadata metadata;
metadata.version = kJpegrVersion;
- metadata.transferFunction = hdr_tf;
- if (hdr_tf == JPEGR_TF_PQ) {
- metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
- }
jpegr_uncompressed_struct map;
JPEGR_CHECK(generateRecoveryMap(
- &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
+ &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
std::unique_ptr<uint8_t[]> map_data;
map_data.reset(reinterpret_cast<uint8_t*>(map.data));
@@ -435,18 +320,12 @@
compressed_map.data = compressed_map_data.get();
JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
- JPEGR_CHECK(appendRecoveryMap(
- new_jpeg_image.data == nullptr ? compressed_jpeg_image : &new_jpeg_image,
- &compressed_map, &new_exif, &metadata, dest));
-
- if (new_jpeg_image.data != nullptr) {
- free(new_jpeg_image.data);
- }
+ JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
return NO_ERROR;
}
-status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
+status_t JpegR::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;
@@ -456,7 +335,7 @@
JPEGR_CHECK(extractPrimaryImageAndRecoveryMap(compressed_jpegr_image,
&primary_image, &recovery_map));
- JpegDecoder jpeg_decoder;
+ JpegDecoderHelper jpeg_decoder;
if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
&jpegr_info->width, &jpegr_info->height,
jpegr_info->iccData, jpegr_info->exifData)) {
@@ -467,7 +346,7 @@
}
/* Decode API */
-status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
+status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
jr_uncompressed_ptr dest,
jr_exif_ptr exif,
bool request_sdr) {
@@ -478,7 +357,7 @@
(void) exif;
if (request_sdr) {
- JpegDecoder jpeg_decoder;
+ JpegDecoderHelper jpeg_decoder;
if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length,
true)) {
return ERROR_JPEGR_DECODE_ERROR;
@@ -498,12 +377,12 @@
jpegr_metadata metadata;
JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
- JpegDecoder jpeg_decoder;
+ JpegDecoderHelper jpeg_decoder;
if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
return ERROR_JPEGR_DECODE_ERROR;
}
- JpegDecoder recovery_map_decoder;
+ JpegDecoderHelper recovery_map_decoder;
if (!recovery_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) {
return ERROR_JPEGR_DECODE_ERROR;
}
@@ -527,14 +406,13 @@
return NO_ERROR;
}
-status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
+status_t JpegR::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
jr_compressed_ptr dest) {
if (uncompressed_recovery_map == nullptr || dest == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
- // TODO: should we have ICC data for the map?
- JpegEncoder jpeg_encoder;
+ JpegEncoderHelper jpeg_encoder;
if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data,
uncompressed_recovery_map->width,
uncompressed_recovery_map->height,
@@ -612,8 +490,9 @@
mQueuedAllJobs = false;
}
-status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+status_t JpegR::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
jr_uncompressed_ptr uncompressed_p010_image,
+ jpegr_transfer_function hdr_tf,
jr_metadata_ptr metadata,
jr_uncompressed_ptr dest) {
if (uncompressed_yuv_420_image == nullptr
@@ -650,7 +529,7 @@
ColorTransformFn hdrInvOetf = nullptr;
float hdr_white_nits = 0.0f;
- switch (metadata->transferFunction) {
+ switch (hdr_tf) {
case JPEGR_TF_LINEAR:
hdrInvOetf = identityConversion;
break;
@@ -670,7 +549,7 @@
#endif
hdr_white_nits = kPqMaxNits;
break;
- case JPEGR_TF_UNSPECIFIED:
+ default:
// Should be impossible to hit after input validation.
return ERROR_JPEGR_INVALID_TRANS_FUNC;
}
@@ -695,19 +574,20 @@
}
std::mutex mutex;
- float hdr_y_nits_max = 0.0f;
- double hdr_y_nits_avg = 0.0f;
+ float max_gain = 0.0f;
+ float min_gain = 1.0f;
const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
JobQueue jobQueue;
- std::function<void()> computeMetadata = [uncompressed_p010_image, hdrInvOetf,
- hdrGamutConversionFn, luminanceFn, hdr_white_nits,
- threads, &mutex, &hdr_y_nits_avg,
- &hdr_y_nits_max, &jobQueue]() -> void {
+ std::function<void()> computeMetadata = [uncompressed_p010_image, uncompressed_yuv_420_image,
+ hdrInvOetf, hdrGamutConversionFn, luminanceFn,
+ hdr_white_nits, threads, &mutex, &max_gain, &min_gain,
+ &jobQueue]() -> void {
size_t rowStart, rowEnd;
- float hdr_y_nits_max_th = 0.0f;
- double hdr_y_nits_avg_th = 0.0f;
+ float max_gain_th = 0.0f;
+ float min_gain_th = 1.0f;
+
while (jobQueue.dequeueJob(rowStart, rowEnd)) {
for (size_t y = rowStart; y < rowEnd; ++y) {
for (size_t x = 0; x < uncompressed_p010_image->width; ++x) {
@@ -717,25 +597,36 @@
hdr_rgb = hdrGamutConversionFn(hdr_rgb);
float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
- hdr_y_nits_avg_th += hdr_y_nits;
- if (hdr_y_nits > hdr_y_nits_max_th) {
- hdr_y_nits_max_th = hdr_y_nits;
- }
+ Color sdr_yuv_gamma =
+ getYuv420Pixel(uncompressed_yuv_420_image, x, y);
+ Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
+#if USE_SRGB_INVOETF_LUT
+ Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
+#else
+ Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
+#endif
+ float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
+
+ float gain = hdr_y_nits / sdr_y_nits;
+ max_gain_th = std::max(max_gain_th, gain);
+ min_gain_th = std::min(min_gain_th, gain);
}
}
}
std::unique_lock<std::mutex> lock{mutex};
- hdr_y_nits_avg += hdr_y_nits_avg_th;
- hdr_y_nits_max = std::max(hdr_y_nits_max, hdr_y_nits_max_th);
+ max_gain = std::max(max_gain, max_gain_th);
+ min_gain = std::min(min_gain, min_gain_th);
};
std::function<void()> generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image,
metadata, dest, hdrInvOetf, hdrGamutConversionFn,
luminanceFn, hdr_white_nits, &jobQueue]() -> void {
size_t rowStart, rowEnd;
+ size_t dest_map_width = uncompressed_yuv_420_image->width / kMapDimensionScaleFactor;
+ size_t dest_map_stride = dest->width;
while (jobQueue.dequeueJob(rowStart, rowEnd)) {
for (size_t y = rowStart; y < rowEnd; ++y) {
- for (size_t x = 0; x < dest->width; ++x) {
+ for (size_t x = 0; x < dest_map_width; ++x) {
Color sdr_yuv_gamma =
sampleYuv420(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
@@ -752,9 +643,9 @@
hdr_rgb = hdrGamutConversionFn(hdr_rgb);
float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
- size_t pixel_idx = x + y * dest->width;
+ size_t pixel_idx = x + y * dest_map_stride;
reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
- encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
+ encodeRecovery(sdr_y_nits, hdr_y_nits, metadata);
}
}
}
@@ -775,13 +666,9 @@
computeMetadata();
std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
workers.clear();
- hdr_y_nits_avg /= image_width * image_height;
- metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits;
- if (metadata->transferFunction == JPEGR_TF_PQ) {
- metadata->hdr10Metadata.maxFALL = hdr_y_nits_avg;
- metadata->hdr10Metadata.maxCLL = hdr_y_nits_max;
- }
+ metadata->maxContentBoost = max_gain;
+ metadata->minContentBoost = min_gain;
// generate map
jobQueue.reset();
@@ -803,7 +690,7 @@
return NO_ERROR;
}
-status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
jr_uncompressed_ptr uncompressed_recovery_map,
jr_metadata_ptr metadata,
jr_uncompressed_ptr dest) {
@@ -817,39 +704,21 @@
dest->width = uncompressed_yuv_420_image->width;
dest->height = uncompressed_yuv_420_image->height;
ShepardsIDW idwTable(kMapDimensionScaleFactor);
- RecoveryLUT recoveryLUT(metadata->rangeScalingFactor);
+ RecoveryLUT recoveryLUT(metadata);
JobQueue jobQueue;
std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_recovery_map,
metadata, dest, &jobQueue, &idwTable,
&recoveryLUT]() -> void {
- const float hdr_ratio = metadata->rangeScalingFactor;
+ const float hdr_ratio = metadata->maxContentBoost;
size_t width = uncompressed_yuv_420_image->width;
size_t height = uncompressed_yuv_420_image->height;
- ColorTransformFn hdrOetf = nullptr;
- switch (metadata->transferFunction) {
- case JPEGR_TF_LINEAR:
- hdrOetf = identityConversion;
- break;
- case JPEGR_TF_HLG:
#if USE_HLG_OETF_LUT
- hdrOetf = hlgOetfLUT;
+ ColorTransformFn hdrOetf = hlgOetfLUT;
#else
- hdrOetf = hlgOetf;
+ ColorTransformFn hdrOetf = hlgOetf;
#endif
- break;
- case JPEGR_TF_PQ:
-#if USE_PQ_OETF_LUT
- hdrOetf = pqOetfLUT;
-#else
- hdrOetf = pqOetf;
-#endif
- break;
- case JPEGR_TF_UNSPECIFIED:
- // Should be impossible to hit after input validation.
- hdrOetf = identityConversion;
- }
size_t rowStart, rowEnd;
while (jobQueue.dequeueJob(rowStart, rowEnd)) {
@@ -871,15 +740,14 @@
if (map_scale_factor != floorf(map_scale_factor)) {
recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y);
} else {
- recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y,
- idwTable);
+ recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y, idwTable);
}
#if USE_APPLY_RECOVERY_LUT
Color rgb_hdr = applyRecoveryLUT(rgb_sdr, recovery, recoveryLUT);
#else
- Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio);
+ Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata);
#endif
- Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->rangeScalingFactor);
+ Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->maxContentBoost);
uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
size_t pixel_idx = x + y * width;
@@ -906,9 +774,9 @@
return NO_ERROR;
}
-status_t RecoveryMap::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
- jr_compressed_ptr primary_image,
- jr_compressed_ptr recovery_map) {
+status_t JpegR::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;
}
@@ -956,7 +824,7 @@
}
-status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
+status_t JpegR::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
jr_compressed_ptr dest) {
if (compressed_jpegr_image == nullptr || dest == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
@@ -967,29 +835,33 @@
// JPEG/R structure:
// SOI (ff d8)
+//
+// (Optional, only if EXIF package is from outside)
// APP1 (ff e1)
// 2 bytes of length (2 + length of exif package)
// EXIF package (this includes the first two bytes representing the package length)
-// APP1 (ff e1)
+//
+// (Required, XMP package) APP1 (ff e1)
// 2 bytes of length (2 + 29 + length of xmp package)
// name space ("http://ns.adobe.com/xap/1.0/\0")
// xmp
-// primary image (without the first two bytes (SOI) and without EXIF, may have other packages)
-// secondary image (the recovery map)
+//
+// (Required) primary image (without the first two bytes (SOI), may have other packages)
+//
+// (Required) secondary image (the recovery map)
//
// Metadata versions we are using:
// ECMA TR-98 for JFIF marker
// Exif 2.2 spec for EXIF marker
// Adobe XMP spec part 3 for XMP marker
// ICC v4.3 spec for ICC
-status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
+status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
jr_compressed_ptr compressed_recovery_map,
jr_exif_ptr exif,
jr_metadata_ptr metadata,
jr_compressed_ptr dest) {
if (compressed_jpeg_image == nullptr
|| compressed_recovery_map == nullptr
- || exif == nullptr
|| metadata == nullptr
|| dest == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
@@ -1002,7 +874,7 @@
JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
// Write EXIF
- {
+ if (exif != nullptr) {
const int length = 2 + exif->length;
const uint8_t lengthH = ((length >> 8) & 0xff);
const uint8_t lengthL = (length & 0xff);
@@ -1046,7 +918,7 @@
return NO_ERROR;
}
-status_t RecoveryMap::toneMap(jr_uncompressed_ptr src,
+status_t JpegR::toneMap(jr_uncompressed_ptr src,
jr_uncompressed_ptr dest) {
if (src == nullptr || dest == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
@@ -1083,4 +955,4 @@
return NO_ERROR;
}
-} // namespace android::recoverymap
+} // namespace android::jpegrecoverymap
diff --git a/libs/jpegrecoverymap/jpegrutils.cpp b/libs/jpegrecoverymap/jpegrutils.cpp
new file mode 100644
index 0000000..bcca91a
--- /dev/null
+++ b/libs/jpegrecoverymap/jpegrutils.cpp
@@ -0,0 +1,250 @@
+/*
+ * 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/jpegrutils.h>
+#include <image_io/xml/xml_reader.h>
+#include <image_io/xml/xml_writer.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>
+
+using namespace photos_editing_formats::image_io;
+using namespace std;
+
+namespace android::jpegrecoverymap {
+
+/*
+ * Helper function used for generating XMP metadata.
+ *
+ * @param prefix The prefix part of the name.
+ * @param suffix The suffix part of the name.
+ * @return A name of the form "prefix:suffix".
+ */
+string Name(const string &prefix, const string &suffix) {
+ std::stringstream ss;
+ ss << prefix << ":" << suffix;
+ return ss.str();
+}
+
+/*
+ * Helper function used for writing data to destination.
+ */
+status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
+ if (position + length > destination->maxLength) {
+ return ERROR_JPEGR_BUFFER_TOO_SMALL;
+ }
+
+ memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
+ position += length;
+ return NO_ERROR;
+}
+
+// Extremely simple XML Handler - just searches for interesting elements
+class XMPXmlHandler : public XmlHandler {
+public:
+
+ XMPXmlHandler() : XmlHandler() {
+ gContainerItemState = NotStrarted;
+ }
+
+ enum ParseState {
+ NotStrarted,
+ Started,
+ Done
+ };
+
+ virtual DataMatchResult StartElement(const XmlTokenContext& context) {
+ string val;
+ if (context.BuildTokenValue(&val)) {
+ if (!val.compare(gContainerItemName)) {
+ gContainerItemState = Started;
+ } else {
+ if (gContainerItemState != Done) {
+ gContainerItemState = NotStrarted;
+ }
+ }
+ }
+ return context.GetResult();
+ }
+
+ virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
+ if (gContainerItemState == Started) {
+ gContainerItemState = Done;
+ lastAttributeName = "";
+ }
+ return context.GetResult();
+ }
+
+ virtual DataMatchResult AttributeName(const XmlTokenContext& context) {
+ string val;
+ if (gContainerItemState == Started) {
+ if (context.BuildTokenValue(&val)) {
+ if (!val.compare(maxContentBoostAttrName)) {
+ lastAttributeName = maxContentBoostAttrName;
+ } else {
+ lastAttributeName = "";
+ }
+ }
+ }
+ return context.GetResult();
+ }
+
+ virtual DataMatchResult AttributeValue(const XmlTokenContext& context) {
+ string val;
+ if (gContainerItemState == Started) {
+ if (context.BuildTokenValue(&val, true)) {
+ if (!lastAttributeName.compare(maxContentBoostAttrName)) {
+ maxContentBoostStr = val;
+ }
+ }
+ }
+ return context.GetResult();
+ }
+
+ bool getMaxContentBoost(float* max_content_boost) {
+ if (gContainerItemState == Done) {
+ stringstream ss(maxContentBoostStr);
+ float val;
+ if (ss >> val) {
+ *max_content_boost = val;
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+private:
+ static const string gContainerItemName;
+ static const string maxContentBoostAttrName;
+ string maxContentBoostStr;
+ string lastAttributeName;
+ ParseState gContainerItemState;
+};
+
+// GContainer XMP constants - URI and namespace prefix
+const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
+const string kContainerPrefix = "Container";
+
+// GContainer XMP constants - element and attribute names
+const string kConDirectory = Name(kContainerPrefix, "Directory");
+const string kConItem = Name(kContainerPrefix, "Item");
+
+// GContainer XMP constants - names for XMP handlers
+const string XMPXmlHandler::gContainerItemName = kConItem;
+
+// Item XMP constants - URI and namespace prefix
+const string kItemUri = "http://ns.google.com/photos/1.0/container/item/";
+const string kItemPrefix = "Item";
+
+// Item XMP constants - element and attribute names
+const string kItemLength = Name(kItemPrefix, "Length");
+const string kItemMime = Name(kItemPrefix, "Mime");
+const string kItemSemantic = Name(kItemPrefix, "Semantic");
+
+// Item XMP constants - element and attribute values
+const string kSemanticPrimary = "Primary";
+const string kSemanticRecoveryMap = "RecoveryMap";
+const string kMimeImageJpeg = "image/jpeg";
+
+// RecoveryMap XMP constants - URI and namespace prefix
+const string kRecoveryMapUri = "http://ns.google.com/photos/1.0/recoverymap/";
+const string kRecoveryMapPrefix = "RecoveryMap";
+
+// RecoveryMap XMP constants - element and attribute names
+const string kMapMaxContentBoost = Name(kRecoveryMapPrefix, "MaxContentBoost");
+const string kMapVersion = Name(kRecoveryMapPrefix, "Version");
+
+// RecoveryMap XMP constants - names for XMP handlers
+const string XMPXmlHandler::maxContentBoostAttrName = kMapMaxContentBoost;
+
+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.getMaxContentBoost(&metadata->maxContentBoost)) {
+ return false;
+ }
+
+ return true;
+}
+
+string generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
+ const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
+ const vector<string> kLiItem({string("rdf:li"), kConItem});
+
+ std::stringstream ss;
+ photos_editing_formats::image_io::XmlWriter writer(ss);
+ writer.StartWritingElement("x:xmpmeta");
+ writer.WriteXmlns("x", "adobe:ns:meta/");
+ writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
+ writer.StartWritingElement("rdf:RDF");
+ writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
+ writer.StartWritingElement("rdf:Description");
+ writer.WriteXmlns(kContainerPrefix, kContainerUri);
+ writer.WriteXmlns(kItemPrefix, kItemUri);
+ writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri);
+ writer.StartWritingElements(kConDirSeq);
+ size_t item_depth = writer.StartWritingElements(kLiItem);
+ writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary);
+ writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
+ writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
+ writer.WriteAttributeNameAndValue(kMapMaxContentBoost, metadata.maxContentBoost);
+ writer.FinishWritingElementsToDepth(item_depth);
+ writer.StartWritingElements(kLiItem);
+ writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticRecoveryMap);
+ writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
+ writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
+ writer.FinishWriting();
+
+ return ss.str();
+}
+
+} // namespace android::jpegrecoverymap
diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp
index 4f21ac6..7812e18 100644
--- a/libs/jpegrecoverymap/recoverymapmath.cpp
+++ b/libs/jpegrecoverymap/recoverymapmath.cpp
@@ -18,67 +18,48 @@
#include <vector>
#include <jpegrecoverymap/recoverymapmath.h>
-namespace android::recoverymap {
-
-constexpr size_t kPqOETFPrecision = 10;
-constexpr size_t kPqOETFNumEntries = 1 << kPqOETFPrecision;
+namespace android::jpegrecoverymap {
static const std::vector<float> kPqOETF = [] {
std::vector<float> result;
- float increment = 1.0 / kPqOETFNumEntries;
- float value = 0.0f;
- for (int idx = 0; idx < kPqOETFNumEntries; idx++, value += increment) {
+ for (int idx = 0; idx < kPqOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kPqOETFNumEntries - 1);
result.push_back(pqOetf(value));
}
return result;
}();
-constexpr size_t kPqInvOETFPrecision = 10;
-constexpr size_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision;
-
static const std::vector<float> kPqInvOETF = [] {
std::vector<float> result;
- float increment = 1.0 / kPqInvOETFNumEntries;
- float value = 0.0f;
- for (int idx = 0; idx < kPqInvOETFNumEntries; idx++, value += increment) {
+ for (int idx = 0; idx < kPqInvOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kPqInvOETFNumEntries - 1);
result.push_back(pqInvOetf(value));
}
return result;
}();
-constexpr size_t kHlgOETFPrecision = 10;
-constexpr size_t kHlgOETFNumEntries = 1 << kHlgOETFPrecision;
-
static const std::vector<float> kHlgOETF = [] {
std::vector<float> result;
- float increment = 1.0 / kHlgOETFNumEntries;
- float value = 0.0f;
- for (int idx = 0; idx < kHlgOETFNumEntries; idx++, value += increment) {
+ for (int idx = 0; idx < kHlgOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kHlgOETFNumEntries - 1);
result.push_back(hlgOetf(value));
}
return result;
}();
-constexpr size_t kHlgInvOETFPrecision = 10;
-constexpr size_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision;
-
static const std::vector<float> kHlgInvOETF = [] {
std::vector<float> result;
- float increment = 1.0 / kHlgInvOETFNumEntries;
- float value = 0.0f;
- for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++, value += increment) {
+ for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kHlgInvOETFNumEntries - 1);
result.push_back(hlgInvOetf(value));
}
return result;
}();
-constexpr size_t kSRGBInvOETFPrecision = 10;
-constexpr size_t kSRGBInvOETFNumEntries = 1 << kSRGBInvOETFPrecision;
-static const std::vector<float> kSRGBInvOETF = [] {
+static const std::vector<float> kSrgbInvOETF = [] {
std::vector<float> result;
- float increment = 1.0 / kSRGBInvOETFNumEntries;
- float value = 0.0f;
- for (int idx = 0; idx < kSRGBInvOETFNumEntries; idx++, value += increment) {
+ for (int idx = 0; idx < kSrgbInvOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kSrgbInvOETFNumEntries - 1);
result.push_back(srgbInvOetf(value));
}
return result;
@@ -182,10 +163,10 @@
// See IEC 61966-2-1, Equations F.5 and F.6.
float srgbInvOetfLUT(float e_gamma) {
- uint32_t value = static_cast<uint32_t>(e_gamma * kSRGBInvOETFNumEntries);
+ uint32_t value = static_cast<uint32_t>(e_gamma * kSrgbInvOETFNumEntries);
//TODO() : Remove once conversion modules have appropriate clamping in place
- value = CLIP3(value, 0, kSRGBInvOETFNumEntries - 1);
- return kSRGBInvOETF[value];
+ value = CLIP3(value, 0, kSrgbInvOETFNumEntries - 1);
+ return kSrgbInvOETF[value];
}
Color srgbInvOetfLUT(Color e_gamma) {
@@ -461,21 +442,24 @@
////////////////////////////////////////////////////////////////////////////////
// Recovery map calculations
-
-uint8_t encodeRecovery(float y_sdr, float y_hdr, float hdr_ratio) {
+uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata) {
float gain = 1.0f;
if (y_sdr > 0.0f) {
gain = y_hdr / y_sdr;
}
- if (gain < (1.0f / hdr_ratio)) gain = 1.0f / hdr_ratio;
- if (gain > hdr_ratio) gain = hdr_ratio;
+ if (gain < metadata->minContentBoost) gain = metadata->minContentBoost;
+ if (gain > metadata->maxContentBoost) gain = metadata->maxContentBoost;
- return static_cast<uint8_t>(log2(gain) / log2(hdr_ratio) * 127.5f + 127.5f);
+ return static_cast<uint8_t>((log2(gain) - log2(metadata->minContentBoost))
+ / (log2(metadata->maxContentBoost) - log2(metadata->minContentBoost))
+ * 255.0f);
}
-Color applyRecovery(Color e, float recovery, float hdr_ratio) {
- float recoveryFactor = pow(hdr_ratio, recovery);
+Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata) {
+ float logBoost = log2(metadata->minContentBoost) * (1.0f - recovery)
+ + log2(metadata->maxContentBoost) * recovery;
+ float recoveryFactor = exp2(logBoost);
return e * recoveryFactor;
}
@@ -550,7 +534,7 @@
}
static float mapUintToFloat(uint8_t map_uint) {
- return (static_cast<float>(map_uint) - 127.5f) / 127.5f;
+ return static_cast<float>(map_uint) / 255.0f;
}
static float pythDistance(float x_diff, float y_diff) {
@@ -558,9 +542,9 @@
}
// TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
-float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y) {
- float x_map = static_cast<float>(x) / static_cast<float>(map_scale_factor);
- float y_map = static_cast<float>(y) / static_cast<float>(map_scale_factor);
+float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y) {
+ float x_map = static_cast<float>(x) / map_scale_factor;
+ float y_map = static_cast<float>(y) / map_scale_factor;
size_t x_lower = static_cast<size_t>(floor(x_map));
size_t x_upper = x_lower + 1;
@@ -647,4 +631,4 @@
| (0x3 << 30); // Set alpha to 1.0
}
-} // namespace android::recoverymap
+} // namespace android::jpegrecoverymap
diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/recoverymaputils.cpp
deleted file mode 100644
index d5ad9a5..0000000
--- a/libs/jpegrecoverymap/recoverymaputils.cpp
+++ /dev/null
@@ -1,528 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <jpegrecoverymap/recoverymaputils.h>
-#include <image_io/xml/xml_reader.h>
-#include <image_io/xml/xml_writer.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 <utils/Log.h>
-
-using namespace photos_editing_formats::image_io;
-using namespace std;
-
-namespace android::recoverymap {
-
-/*
- * Helper function used for generating XMP metadata.
- *
- * @param prefix The prefix part of the name.
- * @param suffix The suffix part of the name.
- * @return A name of the form "prefix:suffix".
- */
-string Name(const string &prefix, const string &suffix) {
- std::stringstream ss;
- ss << prefix << ":" << suffix;
- return ss.str();
-}
-
-/*
- * Helper function used for writing data to destination.
- */
-status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
- if (position + length > destination->maxLength) {
- return ERROR_JPEGR_BUFFER_TOO_SMALL;
- }
-
- memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
- position += length;
- return NO_ERROR;
-}
-
-status_t Write(jr_exif_ptr destination, const void* source, size_t length, int &position) {
- memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
- position += length;
- return NO_ERROR;
-}
-
-// Extremely simple XML Handler - just searches for interesting elements
-class XMPXmlHandler : public XmlHandler {
-public:
-
- XMPXmlHandler() : XmlHandler() {
- gContainerItemState = NotStrarted;
- }
-
- enum ParseState {
- NotStrarted,
- Started,
- Done
- };
-
- virtual DataMatchResult StartElement(const XmlTokenContext& context) {
- string val;
- if (context.BuildTokenValue(&val)) {
- if (!val.compare(gContainerItemName)) {
- gContainerItemState = Started;
- } else {
- if (gContainerItemState != Done) {
- gContainerItemState = NotStrarted;
- }
- }
- }
- return context.GetResult();
- }
-
- virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
- if (gContainerItemState == Started) {
- gContainerItemState = Done;
- lastAttributeName = "";
- }
- return context.GetResult();
- }
-
- virtual DataMatchResult AttributeName(const XmlTokenContext& context) {
- string val;
- if (gContainerItemState == Started) {
- if (context.BuildTokenValue(&val)) {
- if (!val.compare(rangeScalingFactorAttrName)) {
- lastAttributeName = rangeScalingFactorAttrName;
- } else if (!val.compare(transferFunctionAttrName)) {
- lastAttributeName = transferFunctionAttrName;
- } else {
- lastAttributeName = "";
- }
- }
- }
- return context.GetResult();
- }
-
- virtual DataMatchResult AttributeValue(const XmlTokenContext& context) {
- string val;
- if (gContainerItemState == Started) {
- if (context.BuildTokenValue(&val, true)) {
- if (!lastAttributeName.compare(rangeScalingFactorAttrName)) {
- rangeScalingFactorStr = val;
- } else if (!lastAttributeName.compare(transferFunctionAttrName)) {
- transferFunctionStr = val;
- }
- }
- }
- return context.GetResult();
- }
-
- bool getRangeScalingFactor(float* scaling_factor) {
- if (gContainerItemState == 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) {
- if (gContainerItemState == Done) {
- stringstream ss(transferFunctionStr);
- int val;
- if (ss >> val) {
- *transfer_function = static_cast<jpegr_transfer_function>(val);
- return true;
- } else {
- return false;
- }
- } else {
- return false;
- }
- return true;
- }
-
-private:
- static const string gContainerItemName;
- static const string rangeScalingFactorAttrName;
- static const string transferFunctionAttrName;
- string rangeScalingFactorStr;
- string transferFunctionStr;
- string lastAttributeName;
- ParseState gContainerItemState;
-};
-
-// GContainer XMP constants - URI and namespace prefix
-const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
-const string kContainerPrefix = "GContainer";
-
-// GContainer XMP constants - element and attribute names
-const string kConDirectory = Name(kContainerPrefix, "Directory");
-const string kConItem = Name(kContainerPrefix, "Item");
-const string kConItemLength = Name(kContainerPrefix, "ItemLength");
-const string kConItemMime = Name(kContainerPrefix, "ItemMime");
-const string kConItemSemantic = Name(kContainerPrefix, "ItemSemantic");
-const string kConVersion = Name(kContainerPrefix, "Version");
-
-// GContainer XMP constants - element and attribute values
-const string kSemanticPrimary = "Primary";
-const string kSemanticRecoveryMap = "RecoveryMap";
-const string kMimeImageJpeg = "image/jpeg";
-
-const int kGContainerVersion = 1;
-
-// GContainer XMP constants - names for XMP handlers
-const string XMPXmlHandler::gContainerItemName = kConItem;
-
-// RecoveryMap XMP constants - URI and namespace prefix
-const string kRecoveryMapUri = "http://ns.google.com/photos/1.0/recoverymap/";
-const string kRecoveryMapPrefix = "RecoveryMap";
-
-// RecoveryMap XMP constants - element and attribute names
-const string kMapRangeScalingFactor = Name(kRecoveryMapPrefix, "RangeScalingFactor");
-const string kMapTransferFunction = Name(kRecoveryMapPrefix, "TransferFunction");
-const string kMapVersion = Name(kRecoveryMapPrefix, "Version");
-
-const string kMapHdr10Metadata = Name(kRecoveryMapPrefix, "HDR10Metadata");
-const string kMapHdr10MaxFall = Name(kRecoveryMapPrefix, "HDR10MaxFALL");
-const string kMapHdr10MaxCll = Name(kRecoveryMapPrefix, "HDR10MaxCLL");
-
-const string kMapSt2086Metadata = Name(kRecoveryMapPrefix, "ST2086Metadata");
-const string kMapSt2086MaxLum = Name(kRecoveryMapPrefix, "ST2086MaxLuminance");
-const string kMapSt2086MinLum = Name(kRecoveryMapPrefix, "ST2086MinLuminance");
-const string kMapSt2086Primary = Name(kRecoveryMapPrefix, "ST2086Primary");
-const string kMapSt2086Coordinate = Name(kRecoveryMapPrefix, "ST2086Coordinate");
-const string kMapSt2086CoordinateX = Name(kRecoveryMapPrefix, "ST2086CoordinateX");
-const string kMapSt2086CoordinateY = Name(kRecoveryMapPrefix, "ST2086CoordinateY");
-
-// RecoveryMap XMP constants - element and attribute values
-const int kSt2086PrimaryRed = 0;
-const int kSt2086PrimaryGreen = 1;
-const int kSt2086PrimaryBlue = 2;
-const int kSt2086PrimaryWhite = 3;
-
-// RecoveryMap XMP constants - names for XMP handlers
-const string XMPXmlHandler::rangeScalingFactorAttrName = kMapRangeScalingFactor;
-const string XMPXmlHandler::transferFunctionAttrName = kMapTransferFunction;
-
-bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
- string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
-
- 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;
-}
-
-string generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
- const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
- const vector<string> kLiItem({string("rdf:li"), kConItem});
-
- std::stringstream ss;
- photos_editing_formats::image_io::XmlWriter writer(ss);
- writer.StartWritingElement("x:xmpmeta");
- writer.WriteXmlns("x", "adobe:ns:meta/");
- writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
- writer.StartWritingElement("rdf:RDF");
- writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
- writer.StartWritingElement("rdf:Description");
- writer.WriteXmlns(kContainerPrefix, kContainerUri);
- writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri);
- writer.WriteElementAndContent(kConVersion, kGContainerVersion);
- writer.StartWritingElements(kConDirSeq);
- size_t item_depth = writer.StartWritingElements(kLiItem);
- writer.WriteAttributeNameAndValue(kConItemSemantic, kSemanticPrimary);
- writer.WriteAttributeNameAndValue(kConItemMime, kMimeImageJpeg);
- writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
- writer.WriteAttributeNameAndValue(kMapRangeScalingFactor, metadata.rangeScalingFactor);
- writer.WriteAttributeNameAndValue(kMapTransferFunction, metadata.transferFunction);
- if (metadata.transferFunction == JPEGR_TF_PQ) {
- writer.StartWritingElement(kMapHdr10Metadata);
- writer.WriteAttributeNameAndValue(kMapHdr10MaxFall, metadata.hdr10Metadata.maxFALL);
- writer.WriteAttributeNameAndValue(kMapHdr10MaxCll, metadata.hdr10Metadata.maxCLL);
- writer.StartWritingElement(kMapSt2086Metadata);
- writer.WriteAttributeNameAndValue(
- kMapSt2086MaxLum, metadata.hdr10Metadata.st2086Metadata.maxLuminance);
- writer.WriteAttributeNameAndValue(
- kMapSt2086MinLum, metadata.hdr10Metadata.st2086Metadata.minLuminance);
-
- // red
- writer.StartWritingElement(kMapSt2086Coordinate);
- writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryRed);
- writer.WriteAttributeNameAndValue(
- kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.redPrimary.x);
- writer.WriteAttributeNameAndValue(
- kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.redPrimary.y);
- writer.FinishWritingElement();
-
- // green
- writer.StartWritingElement(kMapSt2086Coordinate);
- writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryGreen);
- writer.WriteAttributeNameAndValue(
- kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.greenPrimary.x);
- writer.WriteAttributeNameAndValue(
- kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.greenPrimary.y);
- writer.FinishWritingElement();
-
- // blue
- writer.StartWritingElement(kMapSt2086Coordinate);
- writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryBlue);
- writer.WriteAttributeNameAndValue(
- kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.bluePrimary.x);
- writer.WriteAttributeNameAndValue(
- kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.bluePrimary.y);
- writer.FinishWritingElement();
-
- // white
- writer.StartWritingElement(kMapSt2086Coordinate);
- writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryWhite);
- writer.WriteAttributeNameAndValue(
- kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.whitePoint.x);
- writer.WriteAttributeNameAndValue(
- kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.whitePoint.y);
- writer.FinishWritingElement();
- }
- writer.FinishWritingElementsToDepth(item_depth);
- writer.StartWritingElements(kLiItem);
- writer.WriteAttributeNameAndValue(kConItemSemantic, kSemanticRecoveryMap);
- writer.WriteAttributeNameAndValue(kConItemMime, kMimeImageJpeg);
- writer.WriteAttributeNameAndValue(kConItemLength, secondary_image_length);
- writer.FinishWriting();
-
- return ss.str();
-}
-
-/*
- * Helper function
- * Add J R entry to existing exif, or create a new one with J R entry if it's null.
- */
-status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest) {
- if (exif == nullptr || exif->data == nullptr) {
- uint8_t data[PSEUDO_EXIF_PACKAGE_LENGTH] = {
- 0x45, 0x78, 0x69, 0x66, 0x00, 0x00,
- 0x49, 0x49, 0x2A, 0x00,
- 0x08, 0x00, 0x00, 0x00,
- 0x01, 0x00,
- 0x4A, 0x52,
- 0x07, 0x00,
- 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00};
- int pos = 0;
- Write(dest, data, PSEUDO_EXIF_PACKAGE_LENGTH, pos);
- return NO_ERROR;
- }
-
- int num_entry = 0;
- uint8_t num_entry_low = 0;
- uint8_t num_entry_high = 0;
- bool use_big_endian = false;
- if (reinterpret_cast<uint16_t*>(exif->data)[3] == 0x4949) {
- num_entry_low = reinterpret_cast<uint8_t*>(exif->data)[14];
- num_entry_high = reinterpret_cast<uint8_t*>(exif->data)[15];
- } else if (reinterpret_cast<uint16_t*>(exif->data)[3] == 0x4d4d) {
- use_big_endian = true;
- num_entry_high = reinterpret_cast<uint8_t*>(exif->data)[14];
- num_entry_low = reinterpret_cast<uint8_t*>(exif->data)[15];
- } else {
- return ERROR_JPEGR_METADATA_ERROR;
- }
- num_entry = (num_entry_high << 8) | num_entry_low;
- num_entry += 1;
- num_entry_low = num_entry & 0xff;
- num_entry_high = (num_entry >> 8) & 0xff;
-
- int pos = 0;
- Write(dest, (uint8_t*)exif->data, 14, pos);
-
- if (use_big_endian) {
- Write(dest, &num_entry_high, 1, pos);
- Write(dest, &num_entry_low, 1, pos);
- uint8_t data[EXIF_J_R_ENTRY_LENGTH] = {
- 0x4A, 0x52,
- 0x00, 0x07,
- 0x00, 0x00, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x00};
- Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos);
- } else {
- Write(dest, &num_entry_low, 1, pos);
- Write(dest, &num_entry_high, 1, pos);
- uint8_t data[EXIF_J_R_ENTRY_LENGTH] = {
- 0x4A, 0x52,
- 0x07, 0x00,
- 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00};
- Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos);
- }
-
- Write(dest, (uint8_t*)exif->data + 16, exif->length - 16, pos);
-
- updateExifOffsets(dest,
- 28, // start from the second tag, skip the "JR" tag
- num_entry - 1,
- use_big_endian);
-
- return NO_ERROR;
-}
-
-/*
- * Helper function
- * Modify offsets in EXIF in place.
- */
-void updateExifOffsets(jr_exif_ptr exif, int pos, bool use_big_endian) {
- int num_entry = readValue(reinterpret_cast<uint8_t*>(exif->data), pos, 2, use_big_endian);
- updateExifOffsets(exif, pos + 2, num_entry, use_big_endian);
-}
-
-void updateExifOffsets(jr_exif_ptr exif, int pos, int num_entry, bool use_big_endian) {
- for (int i = 0; i < num_entry; pos += EXIF_J_R_ENTRY_LENGTH, i++) {
- int tag = readValue(reinterpret_cast<uint8_t*>(exif->data), pos, 2, use_big_endian);
- bool need_to_update_offset = false;
- if (tag == 0x8769) {
- need_to_update_offset = true;
- int sub_ifd_offset =
- readValue(reinterpret_cast<uint8_t*>(exif->data), pos + 8, 4, use_big_endian)
- + 6 // "Exif\0\0";
- + EXIF_J_R_ENTRY_LENGTH;
- updateExifOffsets(exif, sub_ifd_offset, use_big_endian);
- } else {
- int data_format =
- readValue(reinterpret_cast<uint8_t*>(exif->data), pos + 2, 2, use_big_endian);
- int num_of_components =
- readValue(reinterpret_cast<uint8_t*>(exif->data), pos + 4, 4, use_big_endian);
- int data_length = findFormatLengthInBytes(data_format) * num_of_components;
- if (data_length > 4) {
- need_to_update_offset = true;
- }
- }
-
- if (!need_to_update_offset) {
- continue;
- }
-
- int offset = readValue(reinterpret_cast<uint8_t*>(exif->data), pos + 8, 4, use_big_endian);
-
- offset += EXIF_J_R_ENTRY_LENGTH;
-
- if (use_big_endian) {
- reinterpret_cast<uint8_t*>(exif->data)[pos + 11] = offset & 0xff;
- reinterpret_cast<uint8_t*>(exif->data)[pos + 10] = (offset >> 8) & 0xff;
- reinterpret_cast<uint8_t*>(exif->data)[pos + 9] = (offset >> 16) & 0xff;
- reinterpret_cast<uint8_t*>(exif->data)[pos + 8] = (offset >> 24) & 0xff;
- } else {
- reinterpret_cast<uint8_t*>(exif->data)[pos + 8] = offset & 0xff;
- reinterpret_cast<uint8_t*>(exif->data)[pos + 9] = (offset >> 8) & 0xff;
- reinterpret_cast<uint8_t*>(exif->data)[pos + 10] = (offset >> 16) & 0xff;
- reinterpret_cast<uint8_t*>(exif->data)[pos + 11] = (offset >> 24) & 0xff;
- }
- }
-}
-
-/*
- * Read data from the target position and target length in bytes;
- */
-int readValue(uint8_t* data, int pos, int length, bool use_big_endian) {
- if (length == 2) {
- if (use_big_endian) {
- return (data[pos] << 8) | data[pos + 1];
- } else {
- return (data[pos + 1] << 8) | data[pos];
- }
- } else if (length == 4) {
- if (use_big_endian) {
- return (data[pos] << 24) | (data[pos + 1] << 16) | (data[pos + 2] << 8) | data[pos + 3];
- } else {
- return (data[pos + 3] << 24) | (data[pos + 2] << 16) | (data[pos + 1] << 8) | data[pos];
- }
- } else {
- // Not support for now.
- ALOGE("Error in readValue(): pos=%d, length=%d", pos, length);
- return -1;
- }
-}
-
-/*
- * Helper function
- * Returns the length of data format in bytes
- */
-int findFormatLengthInBytes(int data_format) {
- switch (data_format) {
- case 1: // unsigned byte
- case 2: // ascii strings
- case 6: // signed byte
- case 7: // undefined
- return 1;
-
- case 3: // unsigned short
- case 8: // signed short
- return 2;
-
- case 4: // unsigned long
- case 9: // signed long
- case 11: // single float
- return 4;
-
- case 5: // unsigned rational
- case 10: // signed rational
- case 12: // double float
- return 8;
-
- default:
- // should not hit here
- ALOGE("Error in findFormatLengthInBytes(): data_format=%d", data_format);
- return -1;
- }
-}
-
-} // namespace android::recoverymap
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp
index 39445f8..5a4edb2 100644
--- a/libs/jpegrecoverymap/tests/Android.bp
+++ b/libs/jpegrecoverymap/tests/Android.bp
@@ -25,12 +25,12 @@
name: "libjpegrecoverymap_test",
test_suites: ["device-tests"],
srcs: [
- "recoverymap_test.cpp",
+ "jpegr_test.cpp",
"recoverymapmath_test.cpp",
],
shared_libs: [
- "libjpeg",
"libimage_io",
+ "libjpeg",
"liblog",
],
static_libs: [
@@ -39,37 +39,38 @@
"libjpegdecoder",
"libjpegencoder",
"libjpegrecoverymap",
+ "libskia",
],
}
cc_test {
- name: "libjpegencoder_test",
+ name: "libjpegencoderhelper_test",
test_suites: ["device-tests"],
srcs: [
- "jpegencoder_test.cpp",
+ "jpegencoderhelper_test.cpp",
],
shared_libs: [
"libjpeg",
"liblog",
],
static_libs: [
+ "libgtest",
"libjpegencoder",
- "libgtest",
],
}
cc_test {
- name: "libjpegdecoder_test",
+ name: "libjpegdecoderhelper_test",
test_suites: ["device-tests"],
srcs: [
- "jpegdecoder_test.cpp",
+ "jpegdecoderhelper_test.cpp",
],
shared_libs: [
"libjpeg",
"liblog",
],
static_libs: [
- "libjpegdecoder",
"libgtest",
+ "libjpegdecoder",
],
}
diff --git a/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp b/libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp
similarity index 77%
rename from libs/jpegrecoverymap/tests/jpegdecoder_test.cpp
rename to libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp
index 8e01351..2f32a56 100644
--- a/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp
+++ b/libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp
@@ -14,27 +14,27 @@
* limitations under the License.
*/
-#include <jpegrecoverymap/jpegdecoder.h>
+#include <jpegrecoverymap/jpegdecoderhelper.h>
#include <gtest/gtest.h>
#include <utils/Log.h>
#include <fcntl.h>
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
#define YUV_IMAGE "/sdcard/Documents/minnie-320x240-yuv.jpg"
#define YUV_IMAGE_SIZE 20193
#define GREY_IMAGE "/sdcard/Documents/minnie-320x240-y.jpg"
#define GREY_IMAGE_SIZE 20193
-class JpegDecoderTest : public testing::Test {
+class JpegDecoderHelperTest : public testing::Test {
public:
struct Image {
std::unique_ptr<uint8_t[]> buffer;
size_t size;
};
- JpegDecoderTest();
- ~JpegDecoderTest();
+ JpegDecoderHelperTest();
+ ~JpegDecoderHelperTest();
protected:
virtual void SetUp();
virtual void TearDown();
@@ -42,9 +42,9 @@
Image mYuvImage, mGreyImage;
};
-JpegDecoderTest::JpegDecoderTest() {}
+JpegDecoderHelperTest::JpegDecoderHelperTest() {}
-JpegDecoderTest::~JpegDecoderTest() {}
+JpegDecoderHelperTest::~JpegDecoderHelperTest() {}
static size_t getFileSize(int fd) {
struct stat st;
@@ -55,7 +55,7 @@
return st.st_size; // bytes
}
-static bool loadFile(const char filename[], JpegDecoderTest::Image* result) {
+static bool loadFile(const char filename[], JpegDecoderHelperTest::Image* result) {
int fd = open(filename, O_CLOEXEC);
if (fd < 0) {
return false;
@@ -74,7 +74,7 @@
return true;
}
-void JpegDecoderTest::SetUp() {
+void JpegDecoderHelperTest::SetUp() {
if (!loadFile(YUV_IMAGE, &mYuvImage)) {
FAIL() << "Load file " << YUV_IMAGE << " failed";
}
@@ -85,18 +85,18 @@
mGreyImage.size = GREY_IMAGE_SIZE;
}
-void JpegDecoderTest::TearDown() {}
+void JpegDecoderHelperTest::TearDown() {}
-TEST_F(JpegDecoderTest, decodeYuvImage) {
- JpegDecoder decoder;
+TEST_F(JpegDecoderHelperTest, decodeYuvImage) {
+ JpegDecoderHelper decoder;
EXPECT_TRUE(decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size));
ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
}
-TEST_F(JpegDecoderTest, decodeGreyImage) {
- JpegDecoder decoder;
+TEST_F(JpegDecoderHelperTest, decodeGreyImage) {
+ JpegDecoderHelper decoder;
EXPECT_TRUE(decoder.decompressImage(mGreyImage.buffer.get(), mGreyImage.size));
ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
}
-}
\ No newline at end of file
+} // namespace android::jpegrecoverymap
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/tests/jpegencoder_test.cpp b/libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp
similarity index 82%
rename from libs/jpegrecoverymap/tests/jpegencoder_test.cpp
rename to libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp
index 4cd2a5e..095ac2f 100644
--- a/libs/jpegrecoverymap/tests/jpegencoder_test.cpp
+++ b/libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-#include <jpegrecoverymap/jpegencoder.h>
+#include <jpegrecoverymap/jpegencoderhelper.h>
#include <gtest/gtest.h>
#include <utils/Log.h>
#include <fcntl.h>
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
#define VALID_IMAGE "/sdcard/Documents/minnie-320x240.yu12"
#define VALID_IMAGE_WIDTH 320
@@ -33,15 +33,15 @@
#define INVALID_SIZE_IMAGE_HEIGHT 240
#define JPEG_QUALITY 90
-class JpegEncoderTest : public testing::Test {
+class JpegEncoderHelperTest : public testing::Test {
public:
struct Image {
std::unique_ptr<uint8_t[]> buffer;
size_t width;
size_t height;
};
- JpegEncoderTest();
- ~JpegEncoderTest();
+ JpegEncoderHelperTest();
+ ~JpegEncoderHelperTest();
protected:
virtual void SetUp();
virtual void TearDown();
@@ -49,9 +49,9 @@
Image mValidImage, mInvalidSizeImage, mSingleChannelImage;
};
-JpegEncoderTest::JpegEncoderTest() {}
+JpegEncoderHelperTest::JpegEncoderHelperTest() {}
-JpegEncoderTest::~JpegEncoderTest() {}
+JpegEncoderHelperTest::~JpegEncoderHelperTest() {}
static size_t getFileSize(int fd) {
struct stat st;
@@ -62,7 +62,7 @@
return st.st_size; // bytes
}
-static bool loadFile(const char filename[], JpegEncoderTest::Image* result) {
+static bool loadFile(const char filename[], JpegEncoderHelperTest::Image* result) {
int fd = open(filename, O_CLOEXEC);
if (fd < 0) {
return false;
@@ -81,7 +81,7 @@
return true;
}
-void JpegEncoderTest::SetUp() {
+void JpegEncoderHelperTest::SetUp() {
if (!loadFile(VALID_IMAGE, &mValidImage)) {
FAIL() << "Load file " << VALID_IMAGE << " failed";
}
@@ -99,27 +99,27 @@
mSingleChannelImage.height = SINGLE_CHANNEL_IMAGE_HEIGHT;
}
-void JpegEncoderTest::TearDown() {}
+void JpegEncoderHelperTest::TearDown() {}
-TEST_F(JpegEncoderTest, validImage) {
- JpegEncoder encoder;
+TEST_F(JpegEncoderHelperTest, validImage) {
+ JpegEncoderHelper encoder;
EXPECT_TRUE(encoder.compressImage(mValidImage.buffer.get(), mValidImage.width,
mValidImage.height, JPEG_QUALITY, NULL, 0));
ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
}
-TEST_F(JpegEncoderTest, invalidSizeImage) {
- JpegEncoder encoder;
+TEST_F(JpegEncoderHelperTest, invalidSizeImage) {
+ JpegEncoderHelper encoder;
EXPECT_FALSE(encoder.compressImage(mInvalidSizeImage.buffer.get(), mInvalidSizeImage.width,
mInvalidSizeImage.height, JPEG_QUALITY, NULL, 0));
}
-TEST_F(JpegEncoderTest, singleChannelImage) {
- JpegEncoder encoder;
+TEST_F(JpegEncoderHelperTest, singleChannelImage) {
+ JpegEncoderHelper encoder;
EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), mSingleChannelImage.width,
mSingleChannelImage.height, JPEG_QUALITY, NULL, 0, true));
ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
}
-}
+} // namespace android::jpegrecoverymap
diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/jpegr_test.cpp
similarity index 66%
rename from libs/jpegrecoverymap/tests/recoverymap_test.cpp
rename to libs/jpegrecoverymap/tests/jpegr_test.cpp
index dfab76a..c0347e3 100644
--- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp
+++ b/libs/jpegrecoverymap/tests/jpegr_test.cpp
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-#include <jpegrecoverymap/recoverymap.h>
+#include <jpegrecoverymap/jpegr.h>
+#include <jpegrecoverymap/jpegrutils.h>
#include <jpegrecoverymap/recoverymapmath.h>
-#include <jpegrecoverymap/recoverymaputils.h>
#include <fcntl.h>
#include <fstream>
#include <gtest/gtest.h>
+#include <sys/time.h>
#include <utils/Log.h>
#define RAW_P010_IMAGE "/sdcard/Documents/raw_p010_image.p010"
@@ -33,29 +34,26 @@
#define SAVE_DECODING_RESULT true
#define SAVE_INPUT_RGBA true
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
-class RecoveryMapTest : public testing::Test {
-public:
- RecoveryMapTest();
- ~RecoveryMapTest();
-protected:
- virtual void SetUp();
- virtual void TearDown();
-
- struct jpegr_uncompressed_struct mRawP010Image;
- struct jpegr_uncompressed_struct mRawYuv420Image;
- struct jpegr_compressed_struct mJpegImage;
+struct Timer {
+ struct timeval StartingTime;
+ struct timeval EndingTime;
+ struct timeval ElapsedMicroseconds;
};
-RecoveryMapTest::RecoveryMapTest() {}
-RecoveryMapTest::~RecoveryMapTest() {}
+void timerStart(Timer *t) {
+ gettimeofday(&t->StartingTime, nullptr);
+}
-void RecoveryMapTest::SetUp() {}
-void RecoveryMapTest::TearDown() {
- free(mRawP010Image.data);
- free(mRawYuv420Image.data);
- free(mJpegImage.data);
+void timerStop(Timer *t) {
+ gettimeofday(&t->EndingTime, nullptr);
+}
+
+int64_t elapsedTime(Timer *t) {
+ t->ElapsedMicroseconds.tv_sec = t->EndingTime.tv_sec - t->StartingTime.tv_sec;
+ t->ElapsedMicroseconds.tv_usec = t->EndingTime.tv_usec - t->StartingTime.tv_usec;
+ return t->ElapsedMicroseconds.tv_sec * 1000000 + t->ElapsedMicroseconds.tv_usec;
}
static size_t getFileSize(int fd) {
@@ -89,22 +87,95 @@
return true;
}
-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, static_cast<jpegr_transfer_function>(0), nullptr, 0, 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);
+class JpegRTest : public testing::Test {
+public:
+ JpegRTest();
+ ~JpegRTest();
+
+protected:
+ virtual void SetUp();
+ virtual void TearDown();
+
+ struct jpegr_uncompressed_struct mRawP010Image;
+ struct jpegr_uncompressed_struct mRawYuv420Image;
+ struct jpegr_compressed_struct mJpegImage;
+};
+
+JpegRTest::JpegRTest() {}
+JpegRTest::~JpegRTest() {}
+
+void JpegRTest::SetUp() {}
+void JpegRTest::TearDown() {
+ free(mRawP010Image.data);
+ free(mRawYuv420Image.data);
+ free(mJpegImage.data);
}
-TEST_F(RecoveryMapTest, writeXmpThenRead) {
+class JpegRBenchmark : public JpegR {
+public:
+ void BenchmarkGenerateRecoveryMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image,
+ jr_metadata_ptr metadata, jr_uncompressed_ptr map);
+ void BenchmarkApplyRecoveryMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
+ jr_metadata_ptr metadata, jr_uncompressed_ptr dest);
+private:
+ const int kProfileCount = 10;
+};
+
+void JpegRBenchmark::BenchmarkGenerateRecoveryMap(jr_uncompressed_ptr yuv420Image,
+ jr_uncompressed_ptr p010Image,
+ jr_metadata_ptr metadata,
+ jr_uncompressed_ptr map) {
+ ASSERT_EQ(yuv420Image->width, p010Image->width);
+ ASSERT_EQ(yuv420Image->height, p010Image->height);
+
+ Timer genRecMapTime;
+
+ timerStart(&genRecMapTime);
+ for (auto i = 0; i < kProfileCount; i++) {
+ ASSERT_EQ(OK, generateRecoveryMap(
+ yuv420Image, p010Image, jpegr_transfer_function::JPEGR_TF_HLG, metadata, map));
+ if (i != kProfileCount - 1) delete[] static_cast<uint8_t *>(map->data);
+ }
+ timerStop(&genRecMapTime);
+
+ ALOGE("Generate Recovery Map:- Res = %i x %i, time = %f ms",
+ yuv420Image->width, yuv420Image->height,
+ elapsedTime(&genRecMapTime) / (kProfileCount * 1000.f));
+
+}
+
+void JpegRBenchmark::BenchmarkApplyRecoveryMap(jr_uncompressed_ptr yuv420Image,
+ jr_uncompressed_ptr map,
+ jr_metadata_ptr metadata,
+ jr_uncompressed_ptr dest) {
+ Timer applyRecMapTime;
+
+ timerStart(&applyRecMapTime);
+ for (auto i = 0; i < kProfileCount; i++) {
+ ASSERT_EQ(OK, applyRecoveryMap(yuv420Image, map, metadata, dest));
+ }
+ timerStop(&applyRecMapTime);
+
+ ALOGE("Apply Recovery Map:- Res = %i x %i, time = %f ms",
+ yuv420Image->width, yuv420Image->height,
+ elapsedTime(&applyRecMapTime) / (kProfileCount * 1000.f));
+}
+
+TEST_F(JpegRTest, build) {
+ // Force all of the recovery map lib to be linked by calling all public functions.
+ JpegR jpegRCodec;
+ jpegRCodec.encodeJPEGR(nullptr, static_cast<jpegr_transfer_function>(0), nullptr, 0, nullptr);
+ jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast<jpegr_transfer_function>(0),
+ nullptr, 0, nullptr);
+ jpegRCodec.encodeJPEGR(nullptr, nullptr, nullptr, static_cast<jpegr_transfer_function>(0),
+ nullptr);
+ jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast<jpegr_transfer_function>(0), nullptr);
+ jpegRCodec.decodeJPEGR(nullptr, nullptr, nullptr, false);
+}
+
+TEST_F(JpegRTest, writeXmpThenRead) {
jpegr_metadata metadata_expected;
- metadata_expected.transferFunction = JPEGR_TF_HLG;
- metadata_expected.rangeScalingFactor = 1.25;
+ metadata_expected.maxContentBoost = 1.25;
int length_expected = 1000;
const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
@@ -120,12 +191,11 @@
jpegr_metadata metadata_read;
EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read));
- ASSERT_EQ(metadata_expected.transferFunction, metadata_read.transferFunction);
- ASSERT_EQ(metadata_expected.rangeScalingFactor, metadata_read.rangeScalingFactor);
+ ASSERT_EQ(metadata_expected.maxContentBoost, metadata_read.maxContentBoost);
}
/* Test Encode API-0 and decode */
-TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) {
+TEST_F(JpegRTest, encodeFromP010ThenDecode) {
int ret;
// Load input files.
@@ -136,12 +206,12 @@
mRawP010Image.height = TEST_IMAGE_HEIGHT;
mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
- RecoveryMap recoveryMap;
+ JpegR jpegRCodec;
jpegr_compressed_struct jpegR;
jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
jpegR.data = malloc(jpegR.maxLength);
- ret = recoveryMap.encodeJPEGR(
+ ret = jpegRCodec.encodeJPEGR(
&mRawP010Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY, nullptr);
if (ret != OK) {
FAIL() << "Error code is " << ret;
@@ -159,7 +229,7 @@
jpegr_uncompressed_struct decodedJpegR;
int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
decodedJpegR.data = malloc(decodedJpegRSize);
- ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
+ ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
if (ret != OK) {
FAIL() << "Error code is " << ret;
}
@@ -178,7 +248,7 @@
}
/* Test Encode API-1 and decode */
-TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrThenDecode) {
+TEST_F(JpegRTest, encodeFromRawHdrAndSdrThenDecode) {
int ret;
// Load input files.
@@ -196,12 +266,12 @@
mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
- RecoveryMap recoveryMap;
+ JpegR jpegRCodec;
jpegr_compressed_struct jpegR;
jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
jpegR.data = malloc(jpegR.maxLength);
- ret = recoveryMap.encodeJPEGR(
+ ret = jpegRCodec.encodeJPEGR(
&mRawP010Image, &mRawYuv420Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR,
DEFAULT_JPEG_QUALITY, nullptr);
if (ret != OK) {
@@ -220,7 +290,7 @@
jpegr_uncompressed_struct decodedJpegR;
int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
decodedJpegR.data = malloc(decodedJpegRSize);
- ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
+ ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
if (ret != OK) {
FAIL() << "Error code is " << ret;
}
@@ -239,7 +309,7 @@
}
/* Test Encode API-2 and decode */
-TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrAndJpegThenDecode) {
+TEST_F(JpegRTest, encodeFromRawHdrAndSdrAndJpegThenDecode) {
int ret;
// Load input files.
@@ -262,12 +332,12 @@
}
mJpegImage.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
- RecoveryMap recoveryMap;
+ JpegR jpegRCodec;
jpegr_compressed_struct jpegR;
jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
jpegR.data = malloc(jpegR.maxLength);
- ret = recoveryMap.encodeJPEGR(
+ ret = jpegRCodec.encodeJPEGR(
&mRawP010Image, &mRawYuv420Image, &mJpegImage, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR);
if (ret != OK) {
FAIL() << "Error code is " << ret;
@@ -285,7 +355,7 @@
jpegr_uncompressed_struct decodedJpegR;
int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
decodedJpegR.data = malloc(decodedJpegRSize);
- ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
+ ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
if (ret != OK) {
FAIL() << "Error code is " << ret;
}
@@ -304,7 +374,7 @@
}
/* Test Encode API-3 and decode */
-TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) {
+TEST_F(JpegRTest, encodeFromJpegThenDecode) {
int ret;
// Load input files.
@@ -343,12 +413,12 @@
}
mJpegImage.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
- RecoveryMap recoveryMap;
+ JpegR jpegRCodec;
jpegr_compressed_struct jpegR;
jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
jpegR.data = malloc(jpegR.maxLength);
- ret = recoveryMap.encodeJPEGR(
+ ret = jpegRCodec.encodeJPEGR(
&mRawP010Image, &mJpegImage, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR);
if (ret != OK) {
FAIL() << "Error code is " << ret;
@@ -366,7 +436,7 @@
jpegr_uncompressed_struct decodedJpegR;
int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
decodedJpegR.data = malloc(decodedJpegRSize);
- ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
+ ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
if (ret != OK) {
FAIL() << "Error code is " << ret;
}
@@ -384,4 +454,46 @@
free(decodedJpegR.data);
}
-} // namespace android::recoverymap
+TEST_F(JpegRTest, ProfileRecoveryMapFuncs) {
+ const size_t kWidth = TEST_IMAGE_WIDTH;
+ const size_t kHeight = TEST_IMAGE_HEIGHT;
+
+ // Load input files.
+ if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
+ FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+ }
+ mRawP010Image.width = kWidth;
+ mRawP010Image.height = kHeight;
+ mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+
+ if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) {
+ FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+ }
+ mRawYuv420Image.width = kWidth;
+ mRawYuv420Image.height = kHeight;
+ mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
+
+ JpegRBenchmark benchmark;
+
+ jpegr_metadata metadata = { .version = 1,
+ .maxContentBoost = 8.0f,
+ .minContentBoost = 1.0f / 8.0f };
+
+ jpegr_uncompressed_struct map = { .data = NULL,
+ .width = 0,
+ .height = 0,
+ .colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED };
+
+ benchmark.BenchmarkGenerateRecoveryMap(&mRawYuv420Image, &mRawP010Image, &metadata, &map);
+
+ const int dstSize = mRawYuv420Image.width * mRawYuv420Image.height * 4;
+ auto bufferDst = std::make_unique<uint8_t[]>(dstSize);
+ jpegr_uncompressed_struct dest = { .data = bufferDst.get(),
+ .width = 0,
+ .height = 0,
+ .colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED };
+
+ benchmark.BenchmarkApplyRecoveryMap(&mRawYuv420Image, &map, &metadata, &dest);
+}
+
+} // namespace android::recoverymap
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
index 1d522d1..6c61ff1 100644
--- a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
@@ -19,7 +19,7 @@
#include <gmock/gmock.h>
#include <jpegrecoverymap/recoverymapmath.h>
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
class RecoveryMapMathTest : public testing::Test {
public:
@@ -42,7 +42,7 @@
}
float Map(uint8_t e) {
- return (static_cast<float>(e) - 127.5f) / 127.5f;
+ return static_cast<float>(e) / 255.0f;
}
Color ColorMin(Color e1, Color e2) {
@@ -88,10 +88,10 @@
return luminance_scaled * scale_factor;
}
- Color Recover(Color yuv_gamma, float recovery, float range_scaling_factor) {
+ Color Recover(Color yuv_gamma, float recovery, jr_metadata_ptr metadata) {
Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
Color rgb = srgbInvOetf(rgb_gamma);
- return applyRecovery(rgb, recovery, range_scaling_factor);
+ return applyRecovery(rgb, recovery, metadata);
}
jpegr_uncompressed_struct Yuv420Image() {
@@ -518,59 +518,95 @@
}
TEST_F(RecoveryMapMathTest, PqInvOetfLUT) {
- float increment = 1.0 / 1024.0;
- float value = 0.0f;
- for (int idx = 0; idx < 1024; idx++, value += increment) {
+ for (int idx = 0; idx < kPqInvOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kPqInvOETFNumEntries - 1);
EXPECT_FLOAT_EQ(pqInvOetf(value), pqInvOetfLUT(value));
}
}
TEST_F(RecoveryMapMathTest, HlgInvOetfLUT) {
- float increment = 1.0 / 1024.0;
- float value = 0.0f;
- for (int idx = 0; idx < 1024; idx++, value += increment) {
+ for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kHlgInvOETFNumEntries - 1);
EXPECT_FLOAT_EQ(hlgInvOetf(value), hlgInvOetfLUT(value));
}
}
TEST_F(RecoveryMapMathTest, pqOetfLUT) {
- float increment = 1.0 / 1024.0;
- float value = 0.0f;
- for (int idx = 0; idx < 1024; idx++, value += increment) {
+ for (int idx = 0; idx < kPqOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kPqOETFNumEntries - 1);
EXPECT_FLOAT_EQ(pqOetf(value), pqOetfLUT(value));
}
}
TEST_F(RecoveryMapMathTest, hlgOetfLUT) {
- float increment = 1.0 / 1024.0;
- float value = 0.0f;
- for (int idx = 0; idx < 1024; idx++, value += increment) {
+ for (int idx = 0; idx < kHlgOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kHlgOETFNumEntries - 1);
EXPECT_FLOAT_EQ(hlgOetf(value), hlgOetfLUT(value));
}
}
TEST_F(RecoveryMapMathTest, srgbInvOetfLUT) {
- float increment = 1.0 / 1024.0;
- float value = 0.0f;
- for (int idx = 0; idx < 1024; idx++, value += increment) {
+ for (int idx = 0; idx < kSrgbInvOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kSrgbInvOETFNumEntries - 1);
EXPECT_FLOAT_EQ(srgbInvOetf(value), srgbInvOetfLUT(value));
}
}
TEST_F(RecoveryMapMathTest, applyRecoveryLUT) {
- float increment = 2.0 / kRecoveryFactorNumEntries;
- for (float hdrRatio = 1.0f; hdrRatio <= 10.0f; hdrRatio += 1.0f) {
- RecoveryLUT recoveryLUT(hdrRatio);
- for (float value = -1.0f; value <= -1.0f; value += increment) {
- EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, hdrRatio),
+ for (int boost = 1; boost <= 10; boost++) {
+ jpegr_metadata metadata = { .maxContentBoost = static_cast<float>(boost),
+ .minContentBoost = 1.0f / static_cast<float>(boost) };
+ RecoveryLUT recoveryLUT(&metadata);
+ for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kRecoveryFactorNumEntries - 1);
+ EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata),
applyRecoveryLUT(RgbBlack(), value, recoveryLUT));
- EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, hdrRatio),
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, &metadata),
applyRecoveryLUT(RgbWhite(), value, recoveryLUT));
- EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, hdrRatio),
+ EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, &metadata),
applyRecoveryLUT(RgbRed(), value, recoveryLUT));
- EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, hdrRatio),
+ EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, &metadata),
applyRecoveryLUT(RgbGreen(), value, recoveryLUT));
- EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, hdrRatio),
+ EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata),
+ applyRecoveryLUT(RgbBlue(), value, recoveryLUT));
+ }
+ }
+
+ for (int boost = 1; boost <= 10; boost++) {
+ jpegr_metadata metadata = { .maxContentBoost = static_cast<float>(boost),
+ .minContentBoost = 1.0f };
+ RecoveryLUT recoveryLUT(&metadata);
+ for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kRecoveryFactorNumEntries - 1);
+ EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata),
+ applyRecoveryLUT(RgbBlack(), value, recoveryLUT));
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, &metadata),
+ applyRecoveryLUT(RgbWhite(), value, recoveryLUT));
+ EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, &metadata),
+ applyRecoveryLUT(RgbRed(), value, recoveryLUT));
+ EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, &metadata),
+ applyRecoveryLUT(RgbGreen(), value, recoveryLUT));
+ EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata),
+ applyRecoveryLUT(RgbBlue(), value, recoveryLUT));
+ }
+ }
+
+ for (int boost = 1; boost <= 10; boost++) {
+ jpegr_metadata metadata = { .maxContentBoost = static_cast<float>(boost),
+ .minContentBoost = 1.0f / pow(static_cast<float>(boost),
+ 1.0f / 3.0f) };
+ RecoveryLUT recoveryLUT(&metadata);
+ for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kRecoveryFactorNumEntries - 1);
+ EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata),
+ applyRecoveryLUT(RgbBlack(), value, recoveryLUT));
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, &metadata),
+ applyRecoveryLUT(RgbWhite(), value, recoveryLUT));
+ EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, &metadata),
+ applyRecoveryLUT(RgbRed(), value, recoveryLUT));
+ EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, &metadata),
+ applyRecoveryLUT(RgbGreen(), value, recoveryLUT));
+ EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata),
applyRecoveryLUT(RgbBlue(), value, recoveryLUT));
}
}
@@ -623,60 +659,121 @@
}
TEST_F(RecoveryMapMathTest, EncodeRecovery) {
- EXPECT_EQ(encodeRecovery(0.0f, 0.0f, 4.0f), 127);
- EXPECT_EQ(encodeRecovery(0.0f, 1.0f, 4.0f), 127);
- EXPECT_EQ(encodeRecovery(1.0f, 0.0f, 4.0f), 0);
- EXPECT_EQ(encodeRecovery(0.5f, 0.0f, 4.0f), 0);
+ jpegr_metadata metadata = { .maxContentBoost = 4.0f,
+ .minContentBoost = 1.0f / 4.0f };
- EXPECT_EQ(encodeRecovery(1.0f, 1.0f, 4.0f), 127);
- EXPECT_EQ(encodeRecovery(1.0f, 4.0f, 4.0f), 255);
- EXPECT_EQ(encodeRecovery(1.0f, 5.0f, 4.0f), 255);
- EXPECT_EQ(encodeRecovery(4.0f, 1.0f, 4.0f), 0);
- EXPECT_EQ(encodeRecovery(4.0f, 0.5f, 4.0f), 0);
- EXPECT_EQ(encodeRecovery(1.0f, 2.0f, 4.0f), 191);
- EXPECT_EQ(encodeRecovery(2.0f, 1.0f, 4.0f), 63);
+ EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 127);
+ EXPECT_EQ(encodeRecovery(0.0f, 1.0f, &metadata), 127);
+ EXPECT_EQ(encodeRecovery(1.0f, 0.0f, &metadata), 0);
+ EXPECT_EQ(encodeRecovery(0.5f, 0.0f, &metadata), 0);
- EXPECT_EQ(encodeRecovery(1.0f, 2.0f, 2.0f), 255);
- EXPECT_EQ(encodeRecovery(2.0f, 1.0f, 2.0f), 0);
- EXPECT_EQ(encodeRecovery(1.0f, 1.41421f, 2.0f), 191);
- EXPECT_EQ(encodeRecovery(1.41421f, 1.0f, 2.0f), 63);
+ EXPECT_EQ(encodeRecovery(1.0f, 1.0f, &metadata), 127);
+ EXPECT_EQ(encodeRecovery(1.0f, 4.0f, &metadata), 255);
+ EXPECT_EQ(encodeRecovery(1.0f, 5.0f, &metadata), 255);
+ EXPECT_EQ(encodeRecovery(4.0f, 1.0f, &metadata), 0);
+ EXPECT_EQ(encodeRecovery(4.0f, 0.5f, &metadata), 0);
+ EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 191);
+ EXPECT_EQ(encodeRecovery(2.0f, 1.0f, &metadata), 63);
- EXPECT_EQ(encodeRecovery(1.0f, 8.0f, 8.0f), 255);
- EXPECT_EQ(encodeRecovery(8.0f, 1.0f, 8.0f), 0);
- EXPECT_EQ(encodeRecovery(1.0f, 2.82843f, 8.0f), 191);
- EXPECT_EQ(encodeRecovery(2.82843f, 1.0f, 8.0f), 63);
+ metadata.maxContentBoost = 2.0f;
+ metadata.minContentBoost = 1.0f / 2.0f;
+
+ EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 255);
+ EXPECT_EQ(encodeRecovery(2.0f, 1.0f, &metadata), 0);
+ EXPECT_EQ(encodeRecovery(1.0f, 1.41421f, &metadata), 191);
+ EXPECT_EQ(encodeRecovery(1.41421f, 1.0f, &metadata), 63);
+
+ metadata.maxContentBoost = 8.0f;
+ metadata.minContentBoost = 1.0f / 8.0f;
+
+ EXPECT_EQ(encodeRecovery(1.0f, 8.0f, &metadata), 255);
+ EXPECT_EQ(encodeRecovery(8.0f, 1.0f, &metadata), 0);
+ EXPECT_EQ(encodeRecovery(1.0f, 2.82843f, &metadata), 191);
+ EXPECT_EQ(encodeRecovery(2.82843f, 1.0f, &metadata), 63);
+
+ metadata.maxContentBoost = 8.0f;
+ metadata.minContentBoost = 1.0f;
+
+ EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 0);
+ EXPECT_EQ(encodeRecovery(1.0f, 0.0f, &metadata), 0);
+
+ EXPECT_EQ(encodeRecovery(1.0f, 1.0f, &metadata), 0);
+ EXPECT_EQ(encodeRecovery(1.0f, 8.0f, &metadata), 255);
+ EXPECT_EQ(encodeRecovery(1.0f, 4.0f, &metadata), 170);
+ EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 85);
+
+ metadata.maxContentBoost = 8.0f;
+ metadata.minContentBoost = 0.5f;
+
+ EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 63);
+ EXPECT_EQ(encodeRecovery(1.0f, 0.0f, &metadata), 0);
+
+ EXPECT_EQ(encodeRecovery(1.0f, 1.0f, &metadata), 63);
+ EXPECT_EQ(encodeRecovery(1.0f, 8.0f, &metadata), 255);
+ EXPECT_EQ(encodeRecovery(1.0f, 4.0f, &metadata), 191);
+ EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 127);
+ EXPECT_EQ(encodeRecovery(1.0f, 0.7071f, &metadata), 31);
+ EXPECT_EQ(encodeRecovery(1.0f, 0.5f, &metadata), 0);
}
TEST_F(RecoveryMapMathTest, ApplyRecovery) {
- EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), -1.0f, 4.0f), RgbBlack());
- EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.0f, 4.0f), RgbBlack());
- EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 1.0f, 4.0f), RgbBlack());
+ jpegr_metadata metadata = { .maxContentBoost = 4.0f,
+ .minContentBoost = 1.0f / 4.0f };
- EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -1.0f, 4.0f), RgbWhite() / 4.0f);
- EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -0.5f, 4.0f), RgbWhite() / 2.0f);
- EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, 4.0f), RgbWhite());
- EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, 4.0f), RgbWhite() * 2.0f);
- EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, 4.0f), RgbWhite() * 4.0f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.0f, &metadata), RgbBlack());
+ EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.5f, &metadata), RgbBlack());
+ EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 1.0f, &metadata), RgbBlack());
- EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -1.0f, 2.0f), RgbWhite() / 2.0f);
- EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -0.5f, 2.0f), RgbWhite() / 1.41421f);
- EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, 2.0f), RgbWhite());
- EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, 2.0f), RgbWhite() * 1.41421f);
- EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, 2.0f), RgbWhite() * 2.0f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 4.0f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.0f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite());
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.0f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 4.0f);
- EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -1.0f, 8.0f), RgbWhite() / 8.0f);
- EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -0.5f, 8.0f), RgbWhite() / 2.82843f);
- EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, 8.0f), RgbWhite());
- EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, 8.0f), RgbWhite() * 2.82843f);
- EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, 8.0f), RgbWhite() * 8.0f);
+ metadata.maxContentBoost = 2.0f;
+ metadata.minContentBoost = 1.0f / 2.0f;
+
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite() / 1.41421f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite());
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 1.41421f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 2.0f);
+
+ metadata.maxContentBoost = 8.0f;
+ metadata.minContentBoost = 1.0f / 8.0f;
+
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 8.0f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.82843f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite());
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.82843f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
+
+ metadata.maxContentBoost = 8.0f;
+ metadata.minContentBoost = 1.0f;
+
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite());
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
+
+ metadata.maxContentBoost = 8.0f;
+ metadata.minContentBoost = 0.5f;
+
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite());
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite() * 2.0f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 4.0f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
Color e = {{{ 0.0f, 0.5f, 1.0f }}};
+ metadata.maxContentBoost = 4.0f;
+ metadata.minContentBoost = 1.0f / 4.0f;
- EXPECT_RGB_NEAR(applyRecovery(e, -1.0f, 4.0f), e / 4.0f);
- EXPECT_RGB_NEAR(applyRecovery(e, -0.5f, 4.0f), e / 2.0f);
- EXPECT_RGB_NEAR(applyRecovery(e, 0.0f, 4.0f), e);
- EXPECT_RGB_NEAR(applyRecovery(e, 0.5f, 4.0f), e * 2.0f);
- EXPECT_RGB_NEAR(applyRecovery(e, 1.0f, 4.0f), e * 4.0f);
+ EXPECT_RGB_NEAR(applyRecovery(e, 0.0f, &metadata), e / 4.0f);
+ EXPECT_RGB_NEAR(applyRecovery(e, 0.25f, &metadata), e / 2.0f);
+ EXPECT_RGB_NEAR(applyRecovery(e, 0.5f, &metadata), e);
+ EXPECT_RGB_NEAR(applyRecovery(e, 0.75f, &metadata), e * 2.0f);
+ EXPECT_RGB_NEAR(applyRecovery(e, 1.0f, &metadata), e * 4.0f);
}
TEST_F(RecoveryMapMathTest, GetYuv420Pixel) {
@@ -785,8 +882,10 @@
// Instead of reimplementing the sampling algorithm, confirm that the
// sample output is within the range of the min and max of the nearest
// points.
- EXPECT_THAT(sampleMap(&image, kMapScaleFactor, x, y, idwTable),
+ EXPECT_THAT(sampleMap(&image, kMapScaleFactor, x, y),
testing::AllOf(testing::Ge(min), testing::Le(max)));
+ EXPECT_EQ(sampleMap(&image, kMapScaleFactor, x, y, idwTable),
+ sampleMap(&image, kMapScaleFactor, x, y));
}
}
}
@@ -882,60 +981,89 @@
}
TEST_F(RecoveryMapMathTest, ApplyMap) {
- EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, 8.0f),
+ jpegr_metadata metadata = { .maxContentBoost = 8.0f,
+ .minContentBoost = 1.0f / 8.0f };
+
+ EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata),
RgbWhite() * 8.0f);
- EXPECT_RGB_EQ(Recover(YuvBlack(), 1.0f, 8.0f),
+ EXPECT_RGB_EQ(Recover(YuvBlack(), 1.0f, &metadata),
RgbBlack());
- EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 1.0f, 8.0f),
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 1.0f, &metadata),
RgbRed() * 8.0f);
- EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 1.0f, 8.0f),
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 1.0f, &metadata),
RgbGreen() * 8.0f);
- EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 1.0f, 8.0f),
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 1.0f, &metadata),
RgbBlue() * 8.0f);
- EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, 8.0f),
+ EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75f, &metadata),
RgbWhite() * sqrt(8.0f));
- EXPECT_RGB_EQ(Recover(YuvBlack(), 0.5f, 8.0f),
+ EXPECT_RGB_EQ(Recover(YuvBlack(), 0.75f, &metadata),
RgbBlack());
- EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.5f, 8.0f),
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.75f, &metadata),
RgbRed() * sqrt(8.0f));
- EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.5f, 8.0f),
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.75f, &metadata),
RgbGreen() * sqrt(8.0f));
- EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.5f, 8.0f),
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.75f, &metadata),
RgbBlue() * sqrt(8.0f));
- EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, 8.0f),
+ EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata),
RgbWhite());
- EXPECT_RGB_EQ(Recover(YuvBlack(), 0.0f, 8.0f),
+ EXPECT_RGB_EQ(Recover(YuvBlack(), 0.5f, &metadata),
RgbBlack());
- EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.0f, 8.0f),
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.5f, &metadata),
RgbRed());
- EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, 8.0f),
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.5f, &metadata),
RgbGreen());
- EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.0f, 8.0f),
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.5f, &metadata),
RgbBlue());
- EXPECT_RGB_EQ(Recover(YuvWhite(), -0.5f, 8.0f),
+ EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata),
RgbWhite() / sqrt(8.0f));
- EXPECT_RGB_EQ(Recover(YuvBlack(), -0.5f, 8.0f),
+ EXPECT_RGB_EQ(Recover(YuvBlack(), 0.25f, &metadata),
RgbBlack());
- EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), -0.5f, 8.0f),
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.25f, &metadata),
RgbRed() / sqrt(8.0f));
- EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), -0.5f, 8.0f),
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.25f, &metadata),
RgbGreen() / sqrt(8.0f));
- EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), -0.5f, 8.0f),
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.25f, &metadata),
RgbBlue() / sqrt(8.0f));
- EXPECT_RGB_EQ(Recover(YuvWhite(), -1.0f, 8.0f),
+ EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata),
RgbWhite() / 8.0f);
- EXPECT_RGB_EQ(Recover(YuvBlack(), -1.0f, 8.0f),
+ EXPECT_RGB_EQ(Recover(YuvBlack(), 0.0f, &metadata),
RgbBlack());
- EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), -1.0f, 8.0f),
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.0f, &metadata),
RgbRed() / 8.0f);
- EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), -1.0f, 8.0f),
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, &metadata),
RgbGreen() / 8.0f);
- EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), -1.0f, 8.0f),
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.0f, &metadata),
RgbBlue() / 8.0f);
+
+ metadata.maxContentBoost = 8.0f;
+ metadata.minContentBoost = 1.0f;
+
+ EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata),
+ RgbWhite() * 8.0f);
+ EXPECT_RGB_EQ(Recover(YuvWhite(), 2.0f / 3.0f, &metadata),
+ RgbWhite() * 4.0f);
+ EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f / 3.0f, &metadata),
+ RgbWhite() * 2.0f);
+ EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata),
+ RgbWhite());
+
+ metadata.maxContentBoost = 8.0f;
+ metadata.minContentBoost = 0.5f;;
+
+ EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata),
+ RgbWhite() * 8.0f);
+ EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75, &metadata),
+ RgbWhite() * 4.0f);
+ EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata),
+ RgbWhite() * 2.0f);
+ EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata),
+ RgbWhite());
+ EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata),
+ RgbWhite() / 2.0f);
}
-} // namespace android::recoverymap
+} // namespace android::jpegrecoverymap
diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp
index 750338b..49e1cba 100644
--- a/opengl/libs/Android.bp
+++ b/opengl/libs/Android.bp
@@ -144,6 +144,7 @@
srcs: [
"EGL/BlobCache.cpp",
"EGL/FileBlobCache.cpp",
+ "EGL/MultifileBlobCache.cpp",
],
export_include_dirs: ["EGL"],
}
@@ -160,7 +161,6 @@
srcs: [
"EGL/egl_tls.cpp",
"EGL/egl_cache.cpp",
- "EGL/egl_cache_multifile.cpp",
"EGL/egl_display.cpp",
"EGL/egl_object.cpp",
"EGL/egl_layers.cpp",
@@ -205,6 +205,11 @@
srcs: [
"EGL/BlobCache.cpp",
"EGL/BlobCache_test.cpp",
+ "EGL/MultifileBlobCache.cpp",
+ "EGL/MultifileBlobCache_test.cpp",
+ ],
+ shared_libs: [
+ "libutils",
],
}
diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp
new file mode 100644
index 0000000..99af299
--- /dev/null
+++ b/opengl/libs/EGL/MultifileBlobCache.cpp
@@ -0,0 +1,689 @@
+/*
+ ** Copyright 2022, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ ** http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+// #define LOG_NDEBUG 0
+
+#include "MultifileBlobCache.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <log/log.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <utime.h>
+
+#include <algorithm>
+#include <chrono>
+#include <limits>
+#include <locale>
+
+#include <utils/JenkinsHash.h>
+
+using namespace std::literals;
+
+namespace {
+
+// Open the file and determine the size of the value it contains
+size_t getValueSizeFromFile(int fd, const std::string& entryPath) {
+ // Read the beginning of the file to get header
+ android::MultifileHeader header;
+ size_t result = read(fd, static_cast<void*>(&header), sizeof(android::MultifileHeader));
+ if (result != sizeof(android::MultifileHeader)) {
+ ALOGE("Error reading MultifileHeader from cache entry (%s): %s", entryPath.c_str(),
+ std::strerror(errno));
+ return 0;
+ }
+
+ return header.valueSize;
+}
+
+// Helper function to close entries or free them
+void freeHotCacheEntry(android::MultifileHotCache& entry) {
+ if (entry.entryFd != -1) {
+ // If we have an fd, then this entry was added to hot cache via INIT or GET
+ // We need to unmap and close the entry
+ munmap(entry.entryBuffer, entry.entrySize);
+ close(entry.entryFd);
+ } else {
+ // Otherwise, this was added to hot cache during SET, so it was never mapped
+ // and fd was only on the deferred thread.
+ delete[] entry.entryBuffer;
+ }
+}
+
+} // namespace
+
+namespace android {
+
+MultifileBlobCache::MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize,
+ const std::string& baseDir)
+ : mInitialized(false),
+ mMaxTotalSize(maxTotalSize),
+ mTotalCacheSize(0),
+ mHotCacheLimit(maxHotCacheSize),
+ mHotCacheSize(0),
+ mWorkerThreadIdle(true) {
+ if (baseDir.empty()) {
+ ALOGV("INIT: no baseDir provided in MultifileBlobCache constructor, returning early.");
+ return;
+ }
+
+ // Establish the name of our multifile directory
+ mMultifileDirName = baseDir + ".multifile";
+
+ // Set a limit for max key and value, ensuring at least one entry can always fit in hot cache
+ mMaxKeySize = mHotCacheLimit / 4;
+ mMaxValueSize = mHotCacheLimit / 2;
+
+ ALOGV("INIT: Initializing multifile blobcache with maxKeySize=%zu and maxValueSize=%zu",
+ mMaxKeySize, mMaxValueSize);
+
+ // Initialize our cache with the contents of the directory
+ mTotalCacheSize = 0;
+
+ // Create the worker thread
+ mTaskThread = std::thread(&MultifileBlobCache::processTasks, this);
+
+ // See if the dir exists, and initialize using its contents
+ struct stat st;
+ if (stat(mMultifileDirName.c_str(), &st) == 0) {
+ // Read all the files and gather details, then preload their contents
+ DIR* dir;
+ struct dirent* entry;
+ if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) {
+ while ((entry = readdir(dir)) != nullptr) {
+ if (entry->d_name == "."s || entry->d_name == ".."s) {
+ continue;
+ }
+
+ std::string entryName = entry->d_name;
+ std::string fullPath = mMultifileDirName + "/" + entryName;
+
+ // The filename is the same as the entryHash
+ uint32_t entryHash = static_cast<uint32_t>(strtoul(entry->d_name, nullptr, 10));
+
+ ALOGV("INIT: Checking entry %u", entryHash);
+
+ // Look up the details of the file
+ struct stat st;
+ if (stat(fullPath.c_str(), &st) != 0) {
+ ALOGE("Failed to stat %s", fullPath.c_str());
+ return;
+ }
+
+ // Open the file so we can read its header
+ int fd = open(fullPath.c_str(), O_RDONLY);
+ if (fd == -1) {
+ ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
+ std::strerror(errno));
+ return;
+ }
+
+ // Look up the details we track about each file
+ size_t valueSize = getValueSizeFromFile(fd, fullPath);
+
+ // If the cache entry is damaged or no good, remove it
+ // TODO: Perform any other checks
+ if (valueSize <= 0 || st.st_size <= 0 || st.st_atime <= 0) {
+ ALOGV("INIT: Entry %u has a problem! Removing.", entryHash);
+ if (remove(fullPath.c_str()) != 0) {
+ ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+ }
+ continue;
+ }
+
+ ALOGV("INIT: Entry %u is good, tracking it now.", entryHash);
+
+ // Note: Converting from off_t (signed) to size_t (unsigned)
+ size_t fileSize = static_cast<size_t>(st.st_size);
+ time_t accessTime = st.st_atime;
+
+ // Track details for rapid lookup later
+ trackEntry(entryHash, valueSize, fileSize, accessTime);
+
+ // Track the total size
+ increaseTotalCacheSize(fileSize);
+
+ // Preload the entry for fast retrieval
+ if ((mHotCacheSize + fileSize) < mHotCacheLimit) {
+ // Memory map the file
+ uint8_t* mappedEntry = reinterpret_cast<uint8_t*>(
+ mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
+ if (mappedEntry == MAP_FAILED) {
+ ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
+ }
+
+ ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for "
+ "entryHash %u",
+ fd, mappedEntry, entryHash);
+
+ // Track the details of the preload so they can be retrieved later
+ if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) {
+ ALOGE("INIT Failed to add %u to hot cache", entryHash);
+ munmap(mappedEntry, fileSize);
+ close(fd);
+ return;
+ }
+ } else {
+ close(fd);
+ }
+ }
+ closedir(dir);
+ } else {
+ ALOGE("Unable to open filename: %s", mMultifileDirName.c_str());
+ }
+ } else {
+ // If the multifile directory does not exist, create it and start from scratch
+ if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
+ ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno);
+ }
+ }
+
+ mInitialized = true;
+}
+
+MultifileBlobCache::~MultifileBlobCache() {
+ if (!mInitialized) {
+ return;
+ }
+
+ // Inform the worker thread we're done
+ ALOGV("DESCTRUCTOR: Shutting down worker thread");
+ DeferredTask task(TaskCommand::Exit);
+ queueTask(std::move(task));
+
+ // Wait for it to complete
+ ALOGV("DESCTRUCTOR: Waiting for worker thread to complete");
+ waitForWorkComplete();
+ if (mTaskThread.joinable()) {
+ mTaskThread.join();
+ }
+}
+
+// Set will add the entry to hot cache and start a deferred process to write it to disk
+void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const void* value,
+ EGLsizeiANDROID valueSize) {
+ if (!mInitialized) {
+ return;
+ }
+
+ // Ensure key and value are under their limits
+ if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
+ ALOGV("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
+ valueSize, mMaxValueSize);
+ return;
+ }
+
+ // Generate a hash of the key and use it to track this entry
+ uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
+
+ size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize;
+
+ // If we're going to be over the cache limit, kick off a trim to clear space
+ if (getTotalSize() + fileSize > mMaxTotalSize) {
+ ALOGV("SET: Cache is full, calling trimCache to clear space");
+ trimCache(mMaxTotalSize);
+ }
+
+ ALOGV("SET: Add %u to cache", entryHash);
+
+ uint8_t* buffer = new uint8_t[fileSize];
+
+ // Write the key and value after the header
+ android::MultifileHeader header = {keySize, valueSize};
+ memcpy(static_cast<void*>(buffer), static_cast<const void*>(&header),
+ sizeof(android::MultifileHeader));
+ memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader)), static_cast<const void*>(key),
+ keySize);
+ memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader) + keySize),
+ static_cast<const void*>(value), valueSize);
+
+ std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
+
+ // Track the size and access time for quick recall
+ trackEntry(entryHash, valueSize, fileSize, time(0));
+
+ // Update the overall cache size
+ increaseTotalCacheSize(fileSize);
+
+ // Keep the entry in hot cache for quick retrieval
+ ALOGV("SET: Adding %u to hot cache.", entryHash);
+
+ // Sending -1 as the fd indicates we don't have an fd for this
+ if (!addToHotCache(entryHash, -1, buffer, fileSize)) {
+ ALOGE("GET: Failed to add %u to hot cache", entryHash);
+ return;
+ }
+
+ // Track that we're creating a pending write for this entry
+ // Include the buffer to handle the case when multiple writes are pending for an entry
+ mDeferredWrites.insert(std::make_pair(entryHash, buffer));
+
+ // Create deferred task to write to storage
+ ALOGV("SET: Adding task to queue.");
+ DeferredTask task(TaskCommand::WriteToDisk);
+ task.initWriteToDisk(entryHash, fullPath, buffer, fileSize);
+ queueTask(std::move(task));
+}
+
+// Get will check the hot cache, then load it from disk if needed
+EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize, void* value,
+ EGLsizeiANDROID valueSize) {
+ if (!mInitialized) {
+ return 0;
+ }
+
+ // Ensure key and value are under their limits
+ if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
+ ALOGV("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
+ valueSize, mMaxValueSize);
+ return 0;
+ }
+
+ // Generate a hash of the key and use it to track this entry
+ uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
+
+ // See if we have this file
+ if (!contains(entryHash)) {
+ ALOGV("GET: Cache MISS - cache does not contain entry: %u", entryHash);
+ return 0;
+ }
+
+ // Look up the data for this entry
+ MultifileEntryStats entryStats = getEntryStats(entryHash);
+
+ size_t cachedValueSize = entryStats.valueSize;
+ if (cachedValueSize > valueSize) {
+ ALOGV("GET: Cache MISS - valueSize not large enough (%lu) for entry %u, returning required"
+ "size (%zu)",
+ valueSize, entryHash, cachedValueSize);
+ return cachedValueSize;
+ }
+
+ // We have the file and have enough room to write it out, return the entry
+ ALOGV("GET: Cache HIT - cache contains entry: %u", entryHash);
+
+ // Look up the size of the file
+ size_t fileSize = entryStats.fileSize;
+ if (keySize > fileSize) {
+ ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified "
+ "file",
+ keySize, fileSize);
+ return 0;
+ }
+
+ std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
+
+ // Open the hashed filename path
+ uint8_t* cacheEntry = 0;
+
+ // Check hot cache
+ if (mHotCache.find(entryHash) != mHotCache.end()) {
+ ALOGV("GET: HotCache HIT for entry %u", entryHash);
+ cacheEntry = mHotCache[entryHash].entryBuffer;
+ } else {
+ ALOGV("GET: HotCache MISS for entry: %u", entryHash);
+
+ if (mDeferredWrites.find(entryHash) != mDeferredWrites.end()) {
+ // Wait for writes to complete if there is an outstanding write for this entry
+ ALOGV("GET: Waiting for write to complete for %u", entryHash);
+ waitForWorkComplete();
+ }
+
+ // Open the entry file
+ int fd = open(fullPath.c_str(), O_RDONLY);
+ if (fd == -1) {
+ ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
+ std::strerror(errno));
+ return 0;
+ }
+
+ // Memory map the file
+ cacheEntry =
+ reinterpret_cast<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
+ if (cacheEntry == MAP_FAILED) {
+ ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
+ close(fd);
+ return 0;
+ }
+
+ ALOGV("GET: Adding %u to hot cache", entryHash);
+ if (!addToHotCache(entryHash, fd, cacheEntry, fileSize)) {
+ ALOGE("GET: Failed to add %u to hot cache", entryHash);
+ return 0;
+ }
+
+ cacheEntry = mHotCache[entryHash].entryBuffer;
+ }
+
+ // Ensure the header matches
+ MultifileHeader* header = reinterpret_cast<MultifileHeader*>(cacheEntry);
+ if (header->keySize != keySize || header->valueSize != valueSize) {
+ ALOGW("Mismatch on keySize(%ld vs. cached %ld) or valueSize(%ld vs. cached %ld) compared "
+ "to cache header values for fullPath: %s",
+ keySize, header->keySize, valueSize, header->valueSize, fullPath.c_str());
+ removeFromHotCache(entryHash);
+ return 0;
+ }
+
+ // Compare the incoming key with our stored version (the beginning of the entry)
+ uint8_t* cachedKey = cacheEntry + sizeof(MultifileHeader);
+ int compare = memcmp(cachedKey, key, keySize);
+ if (compare != 0) {
+ ALOGW("Cached key and new key do not match! This is a hash collision or modified file");
+ removeFromHotCache(entryHash);
+ return 0;
+ }
+
+ // Remaining entry following the key is the value
+ uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader));
+ memcpy(value, cachedValue, cachedValueSize);
+
+ return cachedValueSize;
+}
+
+void MultifileBlobCache::finish() {
+ if (!mInitialized) {
+ return;
+ }
+
+ // Wait for all deferred writes to complete
+ ALOGV("FINISH: Waiting for work to complete.");
+ waitForWorkComplete();
+
+ // Close all entries in the hot cache
+ for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
+ uint32_t entryHash = hotCacheIter->first;
+ MultifileHotCache entry = hotCacheIter->second;
+
+ ALOGV("FINISH: Closing hot cache entry for %u", entryHash);
+ freeHotCacheEntry(entry);
+
+ mHotCache.erase(hotCacheIter++);
+ }
+}
+
+void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
+ time_t accessTime) {
+ mEntries.insert(entryHash);
+ mEntryStats[entryHash] = {valueSize, fileSize, accessTime};
+}
+
+bool MultifileBlobCache::contains(uint32_t hashEntry) const {
+ return mEntries.find(hashEntry) != mEntries.end();
+}
+
+MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) {
+ return mEntryStats[entryHash];
+}
+
+void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) {
+ mTotalCacheSize += fileSize;
+}
+
+void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) {
+ mTotalCacheSize -= fileSize;
+}
+
+bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer,
+ size_t newEntrySize) {
+ ALOGV("HOTCACHE(ADD): Adding %u to hot cache", newEntryHash);
+
+ // Clear space if we need to
+ if ((mHotCacheSize + newEntrySize) > mHotCacheLimit) {
+ ALOGV("HOTCACHE(ADD): mHotCacheSize (%zu) + newEntrySize (%zu) is to big for "
+ "mHotCacheLimit "
+ "(%zu), freeing up space for %u",
+ mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash);
+
+ // Wait for all the files to complete writing so our hot cache is accurate
+ waitForWorkComplete();
+
+ // Free up old entries until under the limit
+ for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
+ uint32_t oldEntryHash = hotCacheIter->first;
+ MultifileHotCache oldEntry = hotCacheIter->second;
+
+ // Move our iterator before deleting the entry
+ hotCacheIter++;
+ if (!removeFromHotCache(oldEntryHash)) {
+ ALOGE("HOTCACHE(ADD): Unable to remove entry %u", oldEntryHash);
+ return false;
+ }
+
+ // Clear at least half the hot cache
+ if ((mHotCacheSize + newEntrySize) <= mHotCacheLimit / 2) {
+ ALOGV("HOTCACHE(ADD): Freed enough space for %zu", mHotCacheSize);
+ break;
+ }
+ }
+ }
+
+ // Track it
+ mHotCache[newEntryHash] = {newFd, newEntryBuffer, newEntrySize};
+ mHotCacheSize += newEntrySize;
+
+ ALOGV("HOTCACHE(ADD): New hot cache size: %zu", mHotCacheSize);
+
+ return true;
+}
+
+bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) {
+ if (mHotCache.find(entryHash) != mHotCache.end()) {
+ ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash);
+
+ // Wait for all the files to complete writing so our hot cache is accurate
+ waitForWorkComplete();
+
+ ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash);
+ MultifileHotCache entry = mHotCache[entryHash];
+ freeHotCacheEntry(entry);
+
+ // Delete the entry from our tracking
+ mHotCacheSize -= entry.entrySize;
+ mHotCache.erase(entryHash);
+
+ return true;
+ }
+
+ return false;
+}
+
+bool MultifileBlobCache::applyLRU(size_t cacheLimit) {
+ // Walk through our map of sorted last access times and remove files until under the limit
+ for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) {
+ uint32_t entryHash = cacheEntryIter->first;
+
+ ALOGV("LRU: Removing entryHash %u", entryHash);
+
+ // Track the overall size
+ MultifileEntryStats entryStats = getEntryStats(entryHash);
+ decreaseTotalCacheSize(entryStats.fileSize);
+
+ // Remove it from hot cache if present
+ removeFromHotCache(entryHash);
+
+ // Remove it from the system
+ std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash);
+ if (remove(entryPath.c_str()) != 0) {
+ ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
+ return false;
+ }
+
+ // Increment the iterator before clearing the entry
+ cacheEntryIter++;
+
+ // Delete the entry from our tracking
+ size_t count = mEntryStats.erase(entryHash);
+ if (count != 1) {
+ ALOGE("LRU: Failed to remove entryHash (%u) from mEntryStats", entryHash);
+ return false;
+ }
+
+ // See if it has been reduced enough
+ size_t totalCacheSize = getTotalSize();
+ if (totalCacheSize <= cacheLimit) {
+ // Success
+ ALOGV("LRU: Reduced cache to %zu", totalCacheSize);
+ return true;
+ }
+ }
+
+ ALOGV("LRU: Cache is emptry");
+ return false;
+}
+
+// When removing files, what fraction of the overall limit should be reached when removing files
+// A divisor of two will decrease the cache to 50%, four to 25% and so on
+constexpr uint32_t kCacheLimitDivisor = 2;
+
+// Calculate the cache size and remove old entries until under the limit
+void MultifileBlobCache::trimCache(size_t cacheByteLimit) {
+ // Start with the value provided by egl_cache
+ size_t limit = cacheByteLimit;
+
+ // Wait for all deferred writes to complete
+ waitForWorkComplete();
+
+ size_t size = getTotalSize();
+
+ // If size is larger than the threshold, remove files using LRU
+ if (size > limit) {
+ ALOGV("TRIM: Multifile cache size is larger than %zu, removing old entries",
+ cacheByteLimit);
+ if (!applyLRU(limit / kCacheLimitDivisor)) {
+ ALOGE("Error when clearing multifile shader cache");
+ return;
+ }
+ }
+}
+
+// This function performs a task. It only knows how to write files to disk,
+// but it could be expanded if needed.
+void MultifileBlobCache::processTask(DeferredTask& task) {
+ switch (task.getTaskCommand()) {
+ case TaskCommand::Exit: {
+ ALOGV("DEFERRED: Shutting down");
+ return;
+ }
+ case TaskCommand::WriteToDisk: {
+ uint32_t entryHash = task.getEntryHash();
+ std::string& fullPath = task.getFullPath();
+ uint8_t* buffer = task.getBuffer();
+ size_t bufferSize = task.getBufferSize();
+
+ // Create the file or reset it if already present, read+write for user only
+ int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+ if (fd == -1) {
+ ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s",
+ fullPath.c_str(), std::strerror(errno));
+ return;
+ }
+
+ ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str());
+
+ ssize_t result = write(fd, buffer, bufferSize);
+ if (result != bufferSize) {
+ ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(),
+ std::strerror(errno));
+ return;
+ }
+
+ ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str());
+ close(fd);
+
+ // Erase the entry from mDeferredWrites
+ // Since there could be multiple outstanding writes for an entry, find the matching one
+ typedef std::multimap<uint32_t, uint8_t*>::iterator entryIter;
+ std::pair<entryIter, entryIter> iterPair = mDeferredWrites.equal_range(entryHash);
+ for (entryIter it = iterPair.first; it != iterPair.second; ++it) {
+ if (it->second == buffer) {
+ ALOGV("DEFERRED: Marking write complete for %u at %p", it->first, it->second);
+ mDeferredWrites.erase(it);
+ break;
+ }
+ }
+
+ return;
+ }
+ default: {
+ ALOGE("DEFERRED: Unhandled task type");
+ return;
+ }
+ }
+}
+
+// This function will wait until tasks arrive, then execute them
+// If the exit command is submitted, the loop will terminate
+void MultifileBlobCache::processTasksImpl(bool* exitThread) {
+ while (true) {
+ std::unique_lock<std::mutex> lock(mWorkerMutex);
+ if (mTasks.empty()) {
+ ALOGV("WORKER: No tasks available, waiting");
+ mWorkerThreadIdle = true;
+ mWorkerIdleCondition.notify_all();
+ // Only wake if notified and command queue is not empty
+ mWorkAvailableCondition.wait(lock, [this] { return !mTasks.empty(); });
+ }
+
+ ALOGV("WORKER: Task available, waking up.");
+ mWorkerThreadIdle = false;
+ DeferredTask task = std::move(mTasks.front());
+ mTasks.pop();
+
+ if (task.getTaskCommand() == TaskCommand::Exit) {
+ ALOGV("WORKER: Exiting work loop.");
+ *exitThread = true;
+ mWorkerThreadIdle = true;
+ mWorkerIdleCondition.notify_one();
+ return;
+ }
+
+ lock.unlock();
+ processTask(task);
+ }
+}
+
+// Process tasks until the exit task is submitted
+void MultifileBlobCache::processTasks() {
+ while (true) {
+ bool exitThread = false;
+ processTasksImpl(&exitThread);
+ if (exitThread) {
+ break;
+ }
+ }
+}
+
+// Add a task to the queue to be processed by the worker thread
+void MultifileBlobCache::queueTask(DeferredTask&& task) {
+ std::lock_guard<std::mutex> queueLock(mWorkerMutex);
+ mTasks.emplace(std::move(task));
+ mWorkAvailableCondition.notify_one();
+}
+
+// Wait until all tasks have been completed
+void MultifileBlobCache::waitForWorkComplete() {
+ std::unique_lock<std::mutex> lock(mWorkerMutex);
+ mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); });
+}
+
+}; // namespace android
\ No newline at end of file
diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h
new file mode 100644
index 0000000..c0cc9dc
--- /dev/null
+++ b/opengl/libs/EGL/MultifileBlobCache.h
@@ -0,0 +1,167 @@
+/*
+ ** 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_MULTIFILE_BLOB_CACHE_H
+#define ANDROID_MULTIFILE_BLOB_CACHE_H
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <future>
+#include <map>
+#include <queue>
+#include <string>
+#include <thread>
+#include <unordered_map>
+#include <unordered_set>
+
+namespace android {
+
+struct MultifileHeader {
+ EGLsizeiANDROID keySize;
+ EGLsizeiANDROID valueSize;
+};
+
+struct MultifileEntryStats {
+ EGLsizeiANDROID valueSize;
+ size_t fileSize;
+ time_t accessTime;
+};
+
+struct MultifileHotCache {
+ int entryFd;
+ uint8_t* entryBuffer;
+ size_t entrySize;
+};
+
+enum class TaskCommand {
+ Invalid = 0,
+ WriteToDisk,
+ Exit,
+};
+
+class DeferredTask {
+public:
+ DeferredTask(TaskCommand command)
+ : mCommand(command), mEntryHash(0), mBuffer(nullptr), mBufferSize(0) {}
+
+ TaskCommand getTaskCommand() { return mCommand; }
+
+ void initWriteToDisk(uint32_t entryHash, std::string fullPath, uint8_t* buffer,
+ size_t bufferSize) {
+ mCommand = TaskCommand::WriteToDisk;
+ mEntryHash = entryHash;
+ mFullPath = std::move(fullPath);
+ mBuffer = buffer;
+ mBufferSize = bufferSize;
+ }
+
+ uint32_t getEntryHash() { return mEntryHash; }
+ std::string& getFullPath() { return mFullPath; }
+ uint8_t* getBuffer() { return mBuffer; }
+ size_t getBufferSize() { return mBufferSize; };
+
+private:
+ TaskCommand mCommand;
+
+ // Parameters for WriteToDisk
+ uint32_t mEntryHash;
+ std::string mFullPath;
+ uint8_t* mBuffer;
+ size_t mBufferSize;
+};
+
+class MultifileBlobCache {
+public:
+ MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize, const std::string& baseDir);
+ ~MultifileBlobCache();
+
+ void set(const void* key, EGLsizeiANDROID keySize, const void* value,
+ EGLsizeiANDROID valueSize);
+ EGLsizeiANDROID get(const void* key, EGLsizeiANDROID keySize, void* value,
+ EGLsizeiANDROID valueSize);
+
+ void finish();
+
+ size_t getTotalSize() const { return mTotalCacheSize; }
+
+private:
+ void trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
+ time_t accessTime);
+ bool contains(uint32_t entryHash) const;
+ bool removeEntry(uint32_t entryHash);
+ MultifileEntryStats getEntryStats(uint32_t entryHash);
+
+ size_t getFileSize(uint32_t entryHash);
+ size_t getValueSize(uint32_t entryHash);
+
+ void increaseTotalCacheSize(size_t fileSize);
+ void decreaseTotalCacheSize(size_t fileSize);
+
+ bool addToHotCache(uint32_t entryHash, int fd, uint8_t* entryBufer, size_t entrySize);
+ bool removeFromHotCache(uint32_t entryHash);
+
+ void trimCache(size_t cacheByteLimit);
+ bool applyLRU(size_t cacheLimit);
+
+ bool mInitialized;
+ std::string mMultifileDirName;
+
+ std::unordered_set<uint32_t> mEntries;
+ std::unordered_map<uint32_t, MultifileEntryStats> mEntryStats;
+ std::unordered_map<uint32_t, MultifileHotCache> mHotCache;
+
+ size_t mMaxKeySize;
+ size_t mMaxValueSize;
+ size_t mMaxTotalSize;
+ size_t mTotalCacheSize;
+ size_t mHotCacheLimit;
+ size_t mHotCacheEntryLimit;
+ size_t mHotCacheSize;
+
+ // Below are the components used for deferred writes
+
+ // Track whether we have pending writes for an entry
+ std::multimap<uint32_t, uint8_t*> mDeferredWrites;
+
+ // Functions to work through tasks in the queue
+ void processTasks();
+ void processTasksImpl(bool* exitThread);
+ void processTask(DeferredTask& task);
+
+ // Used by main thread to create work for the worker thread
+ void queueTask(DeferredTask&& task);
+
+ // Used by main thread to wait for worker thread to complete all outstanding work.
+ void waitForWorkComplete();
+
+ std::thread mTaskThread;
+ std::queue<DeferredTask> mTasks;
+ std::mutex mWorkerMutex;
+
+ // This condition will block the worker thread until a task is queued
+ std::condition_variable mWorkAvailableCondition;
+
+ // This condition will block the main thread while the worker thread still has tasks
+ std::condition_variable mWorkerIdleCondition;
+
+ // This bool will track whether all tasks have been completed
+ bool mWorkerThreadIdle;
+};
+
+}; // namespace android
+
+#endif // ANDROID_MULTIFILE_BLOB_CACHE_H
diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp
new file mode 100644
index 0000000..1a55a4f
--- /dev/null
+++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp
@@ -0,0 +1,200 @@
+/*
+ ** Copyright 2023, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ ** http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+#include "MultifileBlobCache.h"
+
+#include <android-base/test_utils.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+
+#include <memory>
+
+namespace android {
+
+template <typename T>
+using sp = std::shared_ptr<T>;
+
+constexpr size_t kMaxTotalSize = 32 * 1024;
+constexpr size_t kMaxPreloadSize = 8 * 1024;
+
+constexpr size_t kMaxKeySize = kMaxPreloadSize / 4;
+constexpr size_t kMaxValueSize = kMaxPreloadSize / 2;
+
+class MultifileBlobCacheTest : public ::testing::Test {
+protected:
+ virtual void SetUp() {
+ mTempFile.reset(new TemporaryFile());
+ mMBC.reset(new MultifileBlobCache(kMaxTotalSize, kMaxPreloadSize, &mTempFile->path[0]));
+ }
+
+ virtual void TearDown() { mMBC.reset(); }
+
+ std::unique_ptr<TemporaryFile> mTempFile;
+ std::unique_ptr<MultifileBlobCache> mMBC;
+};
+
+TEST_F(MultifileBlobCacheTest, CacheSingleValueSucceeds) {
+ unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
+ mMBC->set("abcd", 4, "efgh", 4);
+ ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4));
+ ASSERT_EQ('e', buf[0]);
+ ASSERT_EQ('f', buf[1]);
+ ASSERT_EQ('g', buf[2]);
+ ASSERT_EQ('h', buf[3]);
+}
+
+TEST_F(MultifileBlobCacheTest, CacheTwoValuesSucceeds) {
+ unsigned char buf[2] = {0xee, 0xee};
+ mMBC->set("ab", 2, "cd", 2);
+ mMBC->set("ef", 2, "gh", 2);
+ ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
+ ASSERT_EQ('c', buf[0]);
+ ASSERT_EQ('d', buf[1]);
+ ASSERT_EQ(size_t(2), mMBC->get("ef", 2, buf, 2));
+ ASSERT_EQ('g', buf[0]);
+ ASSERT_EQ('h', buf[1]);
+}
+
+TEST_F(MultifileBlobCacheTest, GetSetTwiceSucceeds) {
+ unsigned char buf[2] = {0xee, 0xee};
+ mMBC->set("ab", 2, "cd", 2);
+ ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
+ ASSERT_EQ('c', buf[0]);
+ ASSERT_EQ('d', buf[1]);
+ // Use the same key, but different value
+ mMBC->set("ab", 2, "ef", 2);
+ ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
+ ASSERT_EQ('e', buf[0]);
+ ASSERT_EQ('f', buf[1]);
+}
+
+TEST_F(MultifileBlobCacheTest, GetOnlyWritesInsideBounds) {
+ unsigned char buf[6] = {0xee, 0xee, 0xee, 0xee, 0xee, 0xee};
+ mMBC->set("abcd", 4, "efgh", 4);
+ ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf + 1, 4));
+ ASSERT_EQ(0xee, buf[0]);
+ ASSERT_EQ('e', buf[1]);
+ ASSERT_EQ('f', buf[2]);
+ ASSERT_EQ('g', buf[3]);
+ ASSERT_EQ('h', buf[4]);
+ ASSERT_EQ(0xee, buf[5]);
+}
+
+TEST_F(MultifileBlobCacheTest, GetOnlyWritesIfBufferIsLargeEnough) {
+ unsigned char buf[3] = {0xee, 0xee, 0xee};
+ mMBC->set("abcd", 4, "efgh", 4);
+ ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 3));
+ ASSERT_EQ(0xee, buf[0]);
+ ASSERT_EQ(0xee, buf[1]);
+ ASSERT_EQ(0xee, buf[2]);
+}
+
+TEST_F(MultifileBlobCacheTest, GetDoesntAccessNullBuffer) {
+ mMBC->set("abcd", 4, "efgh", 4);
+ ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, nullptr, 0));
+}
+
+TEST_F(MultifileBlobCacheTest, MultipleSetsCacheLatestValue) {
+ unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
+ mMBC->set("abcd", 4, "efgh", 4);
+ mMBC->set("abcd", 4, "ijkl", 4);
+ ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4));
+ ASSERT_EQ('i', buf[0]);
+ ASSERT_EQ('j', buf[1]);
+ ASSERT_EQ('k', buf[2]);
+ ASSERT_EQ('l', buf[3]);
+}
+
+TEST_F(MultifileBlobCacheTest, SecondSetKeepsFirstValueIfTooLarge) {
+ unsigned char buf[kMaxValueSize + 1] = {0xee, 0xee, 0xee, 0xee};
+ mMBC->set("abcd", 4, "efgh", 4);
+ mMBC->set("abcd", 4, buf, kMaxValueSize + 1);
+ ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4));
+ ASSERT_EQ('e', buf[0]);
+ ASSERT_EQ('f', buf[1]);
+ ASSERT_EQ('g', buf[2]);
+ ASSERT_EQ('h', buf[3]);
+}
+
+TEST_F(MultifileBlobCacheTest, DoesntCacheIfKeyIsTooBig) {
+ char key[kMaxKeySize + 1];
+ unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
+ for (int i = 0; i < kMaxKeySize + 1; i++) {
+ key[i] = 'a';
+ }
+ mMBC->set(key, kMaxKeySize + 1, "bbbb", 4);
+ ASSERT_EQ(size_t(0), mMBC->get(key, kMaxKeySize + 1, buf, 4));
+ ASSERT_EQ(0xee, buf[0]);
+ ASSERT_EQ(0xee, buf[1]);
+ ASSERT_EQ(0xee, buf[2]);
+ ASSERT_EQ(0xee, buf[3]);
+}
+
+TEST_F(MultifileBlobCacheTest, DoesntCacheIfValueIsTooBig) {
+ char buf[kMaxValueSize + 1];
+ for (int i = 0; i < kMaxValueSize + 1; i++) {
+ buf[i] = 'b';
+ }
+ mMBC->set("abcd", 4, buf, kMaxValueSize + 1);
+ for (int i = 0; i < kMaxValueSize + 1; i++) {
+ buf[i] = 0xee;
+ }
+ ASSERT_EQ(size_t(0), mMBC->get("abcd", 4, buf, kMaxValueSize + 1));
+ for (int i = 0; i < kMaxValueSize + 1; i++) {
+ SCOPED_TRACE(i);
+ ASSERT_EQ(0xee, buf[i]);
+ }
+}
+
+TEST_F(MultifileBlobCacheTest, CacheMaxKeySizeSucceeds) {
+ char key[kMaxKeySize];
+ unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
+ for (int i = 0; i < kMaxKeySize; i++) {
+ key[i] = 'a';
+ }
+ mMBC->set(key, kMaxKeySize, "wxyz", 4);
+ ASSERT_EQ(size_t(4), mMBC->get(key, kMaxKeySize, buf, 4));
+ ASSERT_EQ('w', buf[0]);
+ ASSERT_EQ('x', buf[1]);
+ ASSERT_EQ('y', buf[2]);
+ ASSERT_EQ('z', buf[3]);
+}
+
+TEST_F(MultifileBlobCacheTest, CacheMaxValueSizeSucceeds) {
+ char buf[kMaxValueSize];
+ for (int i = 0; i < kMaxValueSize; i++) {
+ buf[i] = 'b';
+ }
+ mMBC->set("abcd", 4, buf, kMaxValueSize);
+ for (int i = 0; i < kMaxValueSize; i++) {
+ buf[i] = 0xee;
+ }
+ mMBC->get("abcd", 4, buf, kMaxValueSize);
+ for (int i = 0; i < kMaxValueSize; i++) {
+ SCOPED_TRACE(i);
+ ASSERT_EQ('b', buf[i]);
+ }
+}
+
+TEST_F(MultifileBlobCacheTest, CacheMinKeyAndValueSizeSucceeds) {
+ unsigned char buf[1] = {0xee};
+ mMBC->set("x", 1, "y", 1);
+ ASSERT_EQ(size_t(1), mMBC->get("x", 1, buf, 1));
+ ASSERT_EQ('y', buf[0]);
+}
+
+} // namespace android
diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp
index 1e8a348..b00ee33 100644
--- a/opengl/libs/EGL/egl_cache.cpp
+++ b/opengl/libs/EGL/egl_cache.cpp
@@ -14,6 +14,8 @@
** limitations under the License.
*/
+// #define LOG_NDEBUG 0
+
#include "egl_cache.h"
#include <android-base/properties.h>
@@ -25,22 +27,19 @@
#include <thread>
#include "../egl_impl.h"
-#include "egl_cache_multifile.h"
#include "egl_display.h"
// Monolithic cache size limits.
-static const size_t maxKeySize = 12 * 1024;
-static const size_t maxValueSize = 64 * 1024;
-static const size_t maxTotalSize = 32 * 1024 * 1024;
+static const size_t kMaxMonolithicKeySize = 12 * 1024;
+static const size_t kMaxMonolithicValueSize = 64 * 1024;
+static const size_t kMaxMonolithicTotalSize = 2 * 1024 * 1024;
// The time in seconds to wait before saving newly inserted monolithic cache entries.
-static const unsigned int deferredSaveDelay = 4;
+static const unsigned int kDeferredMonolithicSaveDelay = 4;
-// Multifile cache size limit
-constexpr size_t kMultifileCacheByteLimit = 64 * 1024 * 1024;
-
-// Delay before cleaning up multifile cache entries
-static const unsigned int deferredMultifileCleanupDelaySeconds = 1;
+// Multifile cache size limits
+constexpr uint32_t kMultifileHotCacheLimit = 8 * 1024 * 1024;
+constexpr uint32_t kMultifileCacheByteLimit = 32 * 1024 * 1024;
namespace android {
@@ -68,10 +67,7 @@
// egl_cache_t definition
//
egl_cache_t::egl_cache_t()
- : mInitialized(false),
- mMultifileMode(false),
- mCacheByteLimit(maxTotalSize),
- mMultifileCleanupPending(false) {}
+ : mInitialized(false), mMultifileMode(false), mCacheByteLimit(kMaxMonolithicTotalSize) {}
egl_cache_t::~egl_cache_t() {}
@@ -85,7 +81,7 @@
std::lock_guard<std::mutex> lock(mMutex);
egl_connection_t* const cnx = &gEGLImpl;
- if (cnx->dso && cnx->major >= 0 && cnx->minor >= 0) {
+ if (display && cnx->dso && cnx->major >= 0 && cnx->minor >= 0) {
const char* exts = display->disp.queryString.extensions;
size_t bcExtLen = strlen(BC_EXT_STR);
size_t extsLen = strlen(exts);
@@ -114,14 +110,36 @@
}
}
- // Allow forcing monolithic cache for debug purposes
- if (base::GetProperty("debug.egl.blobcache.multifilemode", "") == "false") {
- ALOGD("Forcing monolithic cache due to debug.egl.blobcache.multifilemode == \"false\"");
+ // Check the device config to decide whether multifile should be used
+ if (base::GetBoolProperty("ro.egl.blobcache.multifile", false)) {
+ mMultifileMode = true;
+ ALOGV("Using multifile EGL blobcache");
+ }
+
+ // Allow forcing the mode for debug purposes
+ std::string mode = base::GetProperty("debug.egl.blobcache.multifile", "");
+ if (mode == "true") {
+ ALOGV("Forcing multifile cache due to debug.egl.blobcache.multifile == %s", mode.c_str());
+ mMultifileMode = true;
+ } else if (mode == "false") {
+ ALOGV("Forcing monolithic cache due to debug.egl.blobcache.multifile == %s", mode.c_str());
mMultifileMode = false;
}
if (mMultifileMode) {
- mCacheByteLimit = kMultifileCacheByteLimit;
+ mCacheByteLimit = static_cast<size_t>(
+ base::GetUintProperty<uint32_t>("ro.egl.blobcache.multifile_limit",
+ kMultifileCacheByteLimit));
+
+ // Check for a debug value
+ int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.multifile_limit", -1);
+ if (debugCacheSize >= 0) {
+ ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.multifile_limit",
+ mCacheByteLimit, debugCacheSize);
+ mCacheByteLimit = debugCacheSize;
+ }
+
+ ALOGV("Using multifile EGL blobcache limit of %zu bytes", mCacheByteLimit);
}
mInitialized = true;
@@ -133,10 +151,10 @@
mBlobCache->writeToFile();
}
mBlobCache = nullptr;
- if (mMultifileMode) {
- checkMultifileCacheSize(mCacheByteLimit);
+ if (mMultifileBlobCache) {
+ mMultifileBlobCache->finish();
}
- mMultifileMode = false;
+ mMultifileBlobCache = nullptr;
mInitialized = false;
}
@@ -151,20 +169,8 @@
if (mInitialized) {
if (mMultifileMode) {
- setBlobMultifile(key, keySize, value, valueSize, mFilename);
-
- if (!mMultifileCleanupPending) {
- mMultifileCleanupPending = true;
- // Kick off a thread to cull cache files below limit
- std::thread deferredMultifileCleanupThread([this]() {
- sleep(deferredMultifileCleanupDelaySeconds);
- std::lock_guard<std::mutex> lock(mMutex);
- // Check the size of cache and remove entries to stay under limit
- checkMultifileCacheSize(mCacheByteLimit);
- mMultifileCleanupPending = false;
- });
- deferredMultifileCleanupThread.detach();
- }
+ MultifileBlobCache* mbc = getMultifileBlobCacheLocked();
+ mbc->set(key, keySize, value, valueSize);
} else {
BlobCache* bc = getBlobCacheLocked();
bc->set(key, keySize, value, valueSize);
@@ -172,7 +178,7 @@
if (!mSavePending) {
mSavePending = true;
std::thread deferredSaveThread([this]() {
- sleep(deferredSaveDelay);
+ sleep(kDeferredMonolithicSaveDelay);
std::lock_guard<std::mutex> lock(mMutex);
if (mInitialized && mBlobCache) {
mBlobCache->writeToFile();
@@ -196,15 +202,21 @@
if (mInitialized) {
if (mMultifileMode) {
- return getBlobMultifile(key, keySize, value, valueSize, mFilename);
+ MultifileBlobCache* mbc = getMultifileBlobCacheLocked();
+ return mbc->get(key, keySize, value, valueSize);
} else {
BlobCache* bc = getBlobCacheLocked();
return bc->get(key, keySize, value, valueSize);
}
}
+
return 0;
}
+void egl_cache_t::setCacheMode(EGLCacheMode cacheMode) {
+ mMultifileMode = (cacheMode == EGLCacheMode::Multifile);
+}
+
void egl_cache_t::setCacheFilename(const char* filename) {
std::lock_guard<std::mutex> lock(mMutex);
mFilename = filename;
@@ -216,7 +228,7 @@
if (!mMultifileMode) {
// If we're not in multifile mode, ensure the cache limit is only being lowered,
// not increasing above the hard coded platform limit
- if (cacheByteLimit > maxTotalSize) {
+ if (cacheByteLimit > kMaxMonolithicTotalSize) {
return;
}
}
@@ -226,8 +238,8 @@
size_t egl_cache_t::getCacheSize() {
std::lock_guard<std::mutex> lock(mMutex);
- if (mMultifileMode) {
- return getMultifileCacheSize();
+ if (mMultifileBlobCache) {
+ return mMultifileBlobCache->getTotalSize();
}
if (mBlobCache) {
return mBlobCache->getSize();
@@ -237,9 +249,18 @@
BlobCache* egl_cache_t::getBlobCacheLocked() {
if (mBlobCache == nullptr) {
- mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, mCacheByteLimit, mFilename));
+ mBlobCache.reset(new FileBlobCache(kMaxMonolithicKeySize, kMaxMonolithicValueSize,
+ mCacheByteLimit, mFilename));
}
return mBlobCache.get();
}
+MultifileBlobCache* egl_cache_t::getMultifileBlobCacheLocked() {
+ if (mMultifileBlobCache == nullptr) {
+ mMultifileBlobCache.reset(
+ new MultifileBlobCache(mCacheByteLimit, kMultifileHotCacheLimit, mFilename));
+ }
+ return mMultifileBlobCache.get();
+}
+
}; // namespace android
diff --git a/opengl/libs/EGL/egl_cache.h b/opengl/libs/EGL/egl_cache.h
index 2dcd803..1399368 100644
--- a/opengl/libs/EGL/egl_cache.h
+++ b/opengl/libs/EGL/egl_cache.h
@@ -25,6 +25,7 @@
#include <string>
#include "FileBlobCache.h"
+#include "MultifileBlobCache.h"
namespace android {
@@ -32,6 +33,11 @@
class EGLAPI egl_cache_t {
public:
+ enum class EGLCacheMode {
+ Monolithic,
+ Multifile,
+ };
+
// get returns a pointer to the singleton egl_cache_t object. This
// singleton object will never be destroyed.
static egl_cache_t* get();
@@ -64,6 +70,9 @@
// cache contents from one program invocation to another.
void setCacheFilename(const char* filename);
+ // Allow setting monolithic or multifile modes
+ void setCacheMode(EGLCacheMode cacheMode);
+
// Allow the fixed cache limit to be overridden
void setCacheLimit(int64_t cacheByteLimit);
@@ -85,6 +94,9 @@
// possible.
BlobCache* getBlobCacheLocked();
+ // Get or create the multifile blobcache
+ MultifileBlobCache* getMultifileBlobCacheLocked();
+
// mInitialized indicates whether the egl_cache_t is in the initialized
// state. It is initialized to false at construction time, and gets set to
// true when initialize is called. It is set back to false when terminate
@@ -98,6 +110,9 @@
// first time it's needed.
std::unique_ptr<FileBlobCache> mBlobCache;
+ // The multifile version of blobcache allowing larger contents to be stored
+ std::unique_ptr<MultifileBlobCache> mMultifileBlobCache;
+
// mFilename is the name of the file for storing cache contents in between
// program invocations. It is initialized to an empty string at
// construction time, and can be set with the setCacheFilename method. An
@@ -123,11 +138,7 @@
bool mMultifileMode;
// Cache limit
- int64_t mCacheByteLimit;
-
- // Whether we've kicked off a side thread that will check the multifile
- // cache size and remove entries if needed.
- bool mMultifileCleanupPending;
+ size_t mCacheByteLimit;
};
}; // namespace android
diff --git a/opengl/libs/EGL/egl_cache_multifile.cpp b/opengl/libs/EGL/egl_cache_multifile.cpp
deleted file mode 100644
index 48e557f..0000000
--- a/opengl/libs/EGL/egl_cache_multifile.cpp
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- ** Copyright 2022, The Android Open Source Project
- **
- ** Licensed under the Apache License, Version 2.0 (the "License");
- ** you may not use this file except in compliance with the License.
- ** You may obtain a copy of the License at
- **
- ** http://www.apache.org/licenses/LICENSE-2.0
- **
- ** Unless required by applicable law or agreed to in writing, software
- ** distributed under the License is distributed on an "AS IS" BASIS,
- ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ** See the License for the specific language governing permissions and
- ** limitations under the License.
- */
-
-// #define LOG_NDEBUG 0
-
-#include "egl_cache_multifile.h"
-
-#include <android-base/properties.h>
-#include <dirent.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <log/log.h>
-#include <stdio.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <utime.h>
-
-#include <algorithm>
-#include <chrono>
-#include <fstream>
-#include <limits>
-#include <locale>
-#include <map>
-#include <sstream>
-#include <unordered_map>
-
-#include <utils/JenkinsHash.h>
-
-static std::string multifileDirName = "";
-
-using namespace std::literals;
-
-namespace {
-
-// Create a directory for tracking multiple files
-void setupMultifile(const std::string& baseDir) {
- // If we've already set up the multifile dir in this base directory, we're done
- if (!multifileDirName.empty() && multifileDirName.find(baseDir) != std::string::npos) {
- return;
- }
-
- // Otherwise, create it
- multifileDirName = baseDir + ".multifile";
- if (mkdir(multifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
- ALOGW("Unable to create directory (%s), errno (%i)", multifileDirName.c_str(), errno);
- }
-}
-
-// Create a filename that is based on the hash of the key
-std::string getCacheEntryFilename(const void* key, EGLsizeiANDROID keySize,
- const std::string& baseDir) {
- // Hash the key into a string
- std::stringstream keyName;
- keyName << android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
-
- // Build a filename using dir and hash
- return baseDir + "/" + keyName.str();
-}
-
-// Determine file age based on stat modification time
-// Newer files have a higher age (time since epoch)
-time_t getFileAge(const std::string& filePath) {
- struct stat st;
- if (stat(filePath.c_str(), &st) == 0) {
- ALOGD("getFileAge returning %" PRId64 " for file age", static_cast<uint64_t>(st.st_mtime));
- return st.st_mtime;
- } else {
- ALOGW("Failed to stat %s", filePath.c_str());
- return 0;
- }
-}
-
-size_t getFileSize(const std::string& filePath) {
- struct stat st;
- if (stat(filePath.c_str(), &st) != 0) {
- ALOGE("Unable to stat %s", filePath.c_str());
- return 0;
- }
- return st.st_size;
-}
-
-// Walk through directory entries and track age and size
-// Then iterate through the entries, oldest first, and remove them until under the limit.
-// This will need to be updated if we move to a multilevel cache dir.
-bool applyLRU(size_t cacheLimit) {
- // Build a multimap of files indexed by age.
- // They will be automatically sorted smallest (oldest) to largest (newest)
- std::multimap<time_t, std::string> agesToFiles;
-
- // Map files to sizes
- std::unordered_map<std::string, size_t> filesToSizes;
-
- size_t totalCacheSize = 0;
-
- DIR* dir;
- struct dirent* entry;
- if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
- while ((entry = readdir(dir)) != nullptr) {
- if (entry->d_name == "."s || entry->d_name == ".."s) {
- continue;
- }
-
- // Look up each file age
- std::string fullPath = multifileDirName + "/" + entry->d_name;
- time_t fileAge = getFileAge(fullPath);
-
- // Track the files, sorted by age
- agesToFiles.insert(std::make_pair(fileAge, fullPath));
-
- // Also track the size so we know how much room we have freed
- size_t fileSize = getFileSize(fullPath);
- filesToSizes[fullPath] = fileSize;
- totalCacheSize += fileSize;
- }
- closedir(dir);
- } else {
- ALOGE("Unable to open filename: %s", multifileDirName.c_str());
- return false;
- }
-
- if (totalCacheSize <= cacheLimit) {
- // If LRU was called on a sufficiently small cache, no need to remove anything
- return true;
- }
-
- // Walk through the map of files until we're under the cache size
- for (const auto& cacheEntryIter : agesToFiles) {
- time_t entryAge = cacheEntryIter.first;
- const std::string entryPath = cacheEntryIter.second;
-
- ALOGD("Removing %s with age %ld", entryPath.c_str(), entryAge);
- if (std::remove(entryPath.c_str()) != 0) {
- ALOGE("Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
- return false;
- }
-
- totalCacheSize -= filesToSizes[entryPath];
- if (totalCacheSize <= cacheLimit) {
- // Success
- ALOGV("Reduced cache to %zu", totalCacheSize);
- return true;
- } else {
- ALOGD("Cache size is still too large (%zu), removing more files", totalCacheSize);
- }
- }
-
- // Should never reach this return
- return false;
-}
-
-} // namespace
-
-namespace android {
-
-void setBlobMultifile(const void* key, EGLsizeiANDROID keySize, const void* value,
- EGLsizeiANDROID valueSize, const std::string& baseDir) {
- if (baseDir.empty()) {
- return;
- }
-
- setupMultifile(baseDir);
- std::string filename = getCacheEntryFilename(key, keySize, multifileDirName);
-
- ALOGD("Attempting to open filename for set: %s", filename.c_str());
- std::ofstream outfile(filename, std::ofstream::binary);
- if (outfile.fail()) {
- ALOGW("Unable to open filename: %s", filename.c_str());
- return;
- }
-
- // First write the key
- outfile.write(static_cast<const char*>(key), keySize);
- if (outfile.bad()) {
- ALOGW("Unable to write key to filename: %s", filename.c_str());
- outfile.close();
- return;
- }
- ALOGD("Wrote %i bytes to out file for key", static_cast<int>(outfile.tellp()));
-
- // Then write the value
- outfile.write(static_cast<const char*>(value), valueSize);
- if (outfile.bad()) {
- ALOGW("Unable to write value to filename: %s", filename.c_str());
- outfile.close();
- return;
- }
- ALOGD("Wrote %i bytes to out file for full entry", static_cast<int>(outfile.tellp()));
-
- outfile.close();
-}
-
-EGLsizeiANDROID getBlobMultifile(const void* key, EGLsizeiANDROID keySize, void* value,
- EGLsizeiANDROID valueSize, const std::string& baseDir) {
- if (baseDir.empty()) {
- return 0;
- }
-
- setupMultifile(baseDir);
- std::string filename = getCacheEntryFilename(key, keySize, multifileDirName);
-
- // Open the hashed filename path
- ALOGD("Attempting to open filename for get: %s", filename.c_str());
- int fd = open(filename.c_str(), O_RDONLY);
-
- // File doesn't exist, this is a MISS, return zero bytes read
- if (fd == -1) {
- ALOGD("Cache MISS - failed to open filename: %s, error: %s", filename.c_str(),
- std::strerror(errno));
- return 0;
- }
-
- ALOGD("Cache HIT - opened filename: %s", filename.c_str());
-
- // Get the size of the file
- size_t entrySize = getFileSize(filename);
- if (keySize > entrySize) {
- ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified "
- "file",
- keySize, entrySize);
- close(fd);
- return 0;
- }
-
- // Memory map the file
- uint8_t* cacheEntry =
- reinterpret_cast<uint8_t*>(mmap(nullptr, entrySize, PROT_READ, MAP_PRIVATE, fd, 0));
- if (cacheEntry == MAP_FAILED) {
- ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
- close(fd);
- return 0;
- }
-
- // Compare the incoming key with our stored version (the beginning of the entry)
- int compare = memcmp(cacheEntry, key, keySize);
- if (compare != 0) {
- ALOGW("Cached key and new key do not match! This is a hash collision or modified file");
- munmap(cacheEntry, entrySize);
- close(fd);
- return 0;
- }
-
- // Keys matched, so remaining cache is value size
- size_t cachedValueSize = entrySize - keySize;
-
- // Return actual value size if valueSize is not large enough
- if (cachedValueSize > valueSize) {
- ALOGD("Skipping file read, not enough room provided (valueSize): %lu, "
- "returning required space as %zu",
- valueSize, cachedValueSize);
- munmap(cacheEntry, entrySize);
- close(fd);
- return cachedValueSize;
- }
-
- // Remaining entry following the key is the value
- uint8_t* cachedValue = cacheEntry + keySize;
- memcpy(value, cachedValue, cachedValueSize);
- munmap(cacheEntry, entrySize);
- close(fd);
-
- ALOGD("Read %zu bytes from %s", cachedValueSize, filename.c_str());
- return cachedValueSize;
-}
-
-// Walk through the files in our flat directory, checking the size of each one.
-// Return the total size of normal files in the directory.
-// This will need to be updated if we move to a multilevel cache dir.
-size_t getMultifileCacheSize() {
- if (multifileDirName.empty()) {
- return 0;
- }
-
- DIR* dir;
- struct dirent* entry;
- size_t size = 0;
-
- ALOGD("Using %s as the multifile cache dir ", multifileDirName.c_str());
-
- if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
- while ((entry = readdir(dir)) != nullptr) {
- if (entry->d_name == "."s || entry->d_name == ".."s) {
- continue;
- }
-
- // Add up the size of all files in the dir
- std::string fullPath = multifileDirName + "/" + entry->d_name;
- size += getFileSize(fullPath);
- }
- closedir(dir);
- } else {
- ALOGW("Unable to open filename: %s", multifileDirName.c_str());
- return 0;
- }
-
- return size;
-}
-
-// When removing files, what fraction of the overall limit should be reached when removing files
-// A divisor of two will decrease the cache to 50%, four to 25% and so on
-constexpr uint32_t kCacheLimitDivisor = 2;
-
-// Calculate the cache size and remove old entries until under the limit
-void checkMultifileCacheSize(size_t cacheByteLimit) {
- // Start with the value provided by egl_cache
- size_t limit = cacheByteLimit;
-
- // Check for a debug value
- int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.bytelimit", -1);
- if (debugCacheSize >= 0) {
- ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.bytelimit", limit,
- debugCacheSize);
- limit = debugCacheSize;
- }
-
- // Tally up the initial amount of cache in use
- size_t size = getMultifileCacheSize();
- ALOGD("Multifile cache dir size: %zu", size);
-
- // If size is larger than the threshold, remove files using LRU
- if (size > limit) {
- ALOGV("Multifile cache size is larger than %zu, removing old entries", cacheByteLimit);
- if (!applyLRU(limit / kCacheLimitDivisor)) {
- ALOGE("Error when clearing multifile shader cache");
- return;
- }
- }
- ALOGD("Multifile cache size after reduction: %zu", getMultifileCacheSize());
-}
-
-}; // namespace android
\ No newline at end of file
diff --git a/opengl/libs/EGL/egl_cache_multifile.h b/opengl/libs/EGL/egl_cache_multifile.h
deleted file mode 100644
index ee5fe81..0000000
--- a/opengl/libs/EGL/egl_cache_multifile.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- ** Copyright 2022, The Android Open Source Project
- **
- ** Licensed under the Apache License, Version 2.0 (the "License");
- ** you may not use this file except in compliance with the License.
- ** You may obtain a copy of the License at
- **
- ** http://www.apache.org/licenses/LICENSE-2.0
- **
- ** Unless required by applicable law or agreed to in writing, software
- ** distributed under the License is distributed on an "AS IS" BASIS,
- ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ** See the License for the specific language governing permissions and
- ** limitations under the License.
- */
-
-#ifndef ANDROID_EGL_CACHE_MULTIFILE_H
-#define ANDROID_EGL_CACHE_MULTIFILE_H
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-
-#include <string>
-
-namespace android {
-
-void setBlobMultifile(const void* key, EGLsizeiANDROID keySize, const void* value,
- EGLsizeiANDROID valueSize, const std::string& baseDir);
-EGLsizeiANDROID getBlobMultifile(const void* key, EGLsizeiANDROID keySize, void* value,
- EGLsizeiANDROID valueSize, const std::string& baseDir);
-size_t getMultifileCacheSize();
-void checkMultifileCacheSize(size_t cacheByteLimit);
-
-}; // namespace android
-
-#endif // ANDROID_EGL_CACHE_MULTIFILE_H
diff --git a/opengl/tests/EGLTest/egl_cache_test.cpp b/opengl/tests/EGLTest/egl_cache_test.cpp
index 265bec4..2b3e3a4 100644
--- a/opengl/tests/EGLTest/egl_cache_test.cpp
+++ b/opengl/tests/EGLTest/egl_cache_test.cpp
@@ -24,7 +24,7 @@
#include <android-base/test_utils.h>
#include "egl_cache.h"
-#include "egl_cache_multifile.h"
+#include "MultifileBlobCache.h"
#include "egl_display.h"
#include <memory>
@@ -33,12 +33,16 @@
namespace android {
-class EGLCacheTest : public ::testing::Test {
+class EGLCacheTest : public ::testing::TestWithParam<egl_cache_t::EGLCacheMode> {
protected:
virtual void SetUp() {
- mCache = egl_cache_t::get();
+ // Terminate to clean up any previous cache in this process
+ mCache->terminate();
+
mTempFile.reset(new TemporaryFile());
mCache->setCacheFilename(&mTempFile->path[0]);
+ mCache->setCacheLimit(1024);
+ mCache->setCacheMode(mCacheMode);
}
virtual void TearDown() {
@@ -49,11 +53,12 @@
std::string getCachefileName();
- egl_cache_t* mCache;
+ egl_cache_t* mCache = egl_cache_t::get();
std::unique_ptr<TemporaryFile> mTempFile;
+ egl_cache_t::EGLCacheMode mCacheMode = GetParam();
};
-TEST_F(EGLCacheTest, UninitializedCacheAlwaysMisses) {
+TEST_P(EGLCacheTest, UninitializedCacheAlwaysMisses) {
uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
mCache->setBlob("abcd", 4, "efgh", 4);
ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4));
@@ -63,7 +68,7 @@
ASSERT_EQ(0xee, buf[3]);
}
-TEST_F(EGLCacheTest, InitializedCacheAlwaysHits) {
+TEST_P(EGLCacheTest, InitializedCacheAlwaysHits) {
uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
mCache->setBlob("abcd", 4, "efgh", 4);
@@ -74,7 +79,7 @@
ASSERT_EQ('h', buf[3]);
}
-TEST_F(EGLCacheTest, TerminatedCacheAlwaysMisses) {
+TEST_P(EGLCacheTest, TerminatedCacheAlwaysMisses) {
uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
mCache->setBlob("abcd", 4, "efgh", 4);
@@ -86,7 +91,7 @@
ASSERT_EQ(0xee, buf[3]);
}
-TEST_F(EGLCacheTest, ReinitializedCacheContainsValues) {
+TEST_P(EGLCacheTest, ReinitializedCacheContainsValues) {
uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
mCache->setBlob("abcd", 4, "efgh", 4);
@@ -101,12 +106,12 @@
std::string EGLCacheTest::getCachefileName() {
// Return the monolithic filename unless we find the multifile dir
- std::string cachefileName = &mTempFile->path[0];
- std::string multifileDirName = cachefileName + ".multifile";
+ std::string cachePath = &mTempFile->path[0];
+ std::string multifileDirName = cachePath + ".multifile";
+ std::string cachefileName = "";
struct stat info;
if (stat(multifileDirName.c_str(), &info) == 0) {
-
// Ensure we only have one file to manage
int realFileCount = 0;
@@ -121,6 +126,9 @@
cachefileName = multifileDirName + "/" + entry->d_name;
realFileCount++;
}
+ } else {
+ printf("Unable to open %s, error: %s\n",
+ multifileDirName.c_str(), std::strerror(errno));
}
if (realFileCount != 1) {
@@ -128,14 +136,19 @@
// violates test assumptions
cachefileName = "";
}
+ } else {
+ printf("Unable to stat %s, error: %s\n",
+ multifileDirName.c_str(), std::strerror(errno));
}
return cachefileName;
}
-TEST_F(EGLCacheTest, ModifiedCacheMisses) {
- // Turn this back on if multifile becomes the default
- GTEST_SKIP() << "Skipping test designed for multifile, see b/263574392 and b/246966894";
+TEST_P(EGLCacheTest, ModifiedCacheMisses) {
+ // Skip if not in multifile mode
+ if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) {
+ GTEST_SKIP() << "Skipping test designed for multifile";
+ }
uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
@@ -147,13 +160,13 @@
ASSERT_EQ('g', buf[2]);
ASSERT_EQ('h', buf[3]);
+ // Ensure the cache file is written to disk
+ mCache->terminate();
+
// Depending on the cache mode, the file will be in different locations
std::string cachefileName = getCachefileName();
ASSERT_TRUE(cachefileName.length() > 0);
- // Ensure the cache file is written to disk
- mCache->terminate();
-
// Stomp on the beginning of the cache file, breaking the key match
const long stomp = 0xbadf00d;
FILE *file = fopen(cachefileName.c_str(), "w");
@@ -164,14 +177,15 @@
// Ensure no cache hit
mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
uint8_t buf2[4] = { 0xee, 0xee, 0xee, 0xee };
- ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf2, 4));
+ // getBlob may return junk for required size, but should not return a cache hit
+ mCache->getBlob("abcd", 4, buf2, 4);
ASSERT_EQ(0xee, buf2[0]);
ASSERT_EQ(0xee, buf2[1]);
ASSERT_EQ(0xee, buf2[2]);
ASSERT_EQ(0xee, buf2[3]);
}
-TEST_F(EGLCacheTest, TerminatedCacheBelowCacheLimit) {
+TEST_P(EGLCacheTest, TerminatedCacheBelowCacheLimit) {
uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
@@ -204,4 +218,8 @@
ASSERT_LE(mCache->getCacheSize(), 4);
}
+INSTANTIATE_TEST_CASE_P(MonolithicCacheTests,
+ EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Monolithic));
+INSTANTIATE_TEST_CASE_P(MultifileCacheTests,
+ EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Multifile));
}
diff --git a/services/inputflinger/InputProcessor.cpp b/services/inputflinger/InputProcessor.cpp
index 02d62bf..a98b383 100644
--- a/services/inputflinger/InputProcessor.cpp
+++ b/services/inputflinger/InputProcessor.cpp
@@ -402,7 +402,7 @@
{ // acquire lock
std::scoped_lock threadLock(mLock);
mHalDeathRecipient =
- std::make_unique<ScopedDeathRecipient>(onBinderDied, this /*cookie*/);
+ std::make_unique<ScopedDeathRecipient>(onBinderDied, /*cookie=*/this);
mHalDeathRecipient->linkToDeath(service->asBinder().get());
setMotionClassifierLocked(MotionClassifier::create(std::move(service)));
} // release lock
diff --git a/services/inputflinger/InputReaderBase.cpp b/services/inputflinger/InputReaderBase.cpp
index a864cf8..2450235 100644
--- a/services/inputflinger/InputReaderBase.cpp
+++ b/services/inputflinger/InputReaderBase.cpp
@@ -73,6 +73,9 @@
if (changes & CHANGE_ENABLED_STATE) {
result += "ENABLED_STATE | ";
}
+ if (changes & CHANGE_TOUCHPAD_SETTINGS) {
+ result += "TOUCHPAD_SETTINGS | ";
+ }
if (changes & CHANGE_MUST_REOPEN) {
result += "MUST_REOPEN | ";
}
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index cacf30b..2a3dba7 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -42,9 +42,11 @@
"options": [
{
"include-filter": "android.view.cts.input",
+ "include-filter": "android.view.cts.HoverTest",
"include-filter": "android.view.cts.MotionEventTest",
"include-filter": "android.view.cts.PointerCaptureTest",
"include-filter": "android.view.cts.TooltipTest",
+ "include-filter": "android.view.cts.VelocityTrackerTest",
"include-filter": "android.view.cts.VerifyInputEventTest"
}
]
@@ -131,9 +133,12 @@
"name": "CtsViewTestCases",
"options": [
{
+ "include-filter": "android.view.cts.input",
+ "include-filter": "android.view.cts.HoverTest",
"include-filter": "android.view.cts.MotionEventTest",
"include-filter": "android.view.cts.PointerCaptureTest",
"include-filter": "android.view.cts.TooltipTest",
+ "include-filter": "android.view.cts.VelocityTrackerTest",
"include-filter": "android.view.cts.VerifyInputEventTest"
}
]
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 3d4dd7e..f03c837 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -141,7 +141,7 @@
ALOGE("Waited too long for consumer to produce an event, giving up");
break;
}
- result = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq,
+ result = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
&event);
}
if (result != OK) {
@@ -308,13 +308,13 @@
for (auto _ : state) {
MotionEvent event = generateMotionEvent();
// Send ACTION_DOWN
- dispatcher.injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+ dispatcher.injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
INJECT_EVENT_TIMEOUT,
POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
// Send ACTION_UP
event.setAction(AMOTION_EVENT_ACTION_UP);
- dispatcher.injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+ dispatcher.injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
INJECT_EVENT_TIMEOUT,
POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
@@ -344,7 +344,7 @@
for (auto _ : state) {
dispatcher.onWindowInfosChanged(windowInfos, displayInfos);
- dispatcher.onWindowInfosChanged({} /*windowInfos*/, {} /*displayInfos*/);
+ dispatcher.onWindowInfosChanged(/*windowInfos=*/{}, /*displayInfos=*/{});
}
dispatcher.stop();
}
diff --git a/services/inputflinger/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h
index 512cb6e..83e6a60 100644
--- a/services/inputflinger/dispatcher/CancelationOptions.h
+++ b/services/inputflinger/dispatcher/CancelationOptions.h
@@ -16,7 +16,8 @@
#pragma once
-#include <utils/BitSet.h>
+#include <input/Input.h>
+#include <bitset>
#include <optional>
namespace android {
@@ -29,6 +30,7 @@
CANCEL_POINTER_EVENTS = 1,
CANCEL_NON_POINTER_EVENTS = 2,
CANCEL_FALLBACK_EVENTS = 3,
+ ftl_last = CANCEL_FALLBACK_EVENTS,
};
// The criterion to use to determine which events should be canceled.
@@ -47,7 +49,7 @@
std::optional<int32_t> displayId = std::nullopt;
// The specific pointers to cancel, or nullopt to cancel all pointer events
- std::optional<BitSet32> pointerIds = std::nullopt;
+ std::optional<std::bitset<MAX_POINTER_ID + 1>> pointerIds = std::nullopt;
CancelationOptions(Mode mode, const char* reason) : mode(mode), reason(reason) {}
};
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index dc9f02a..9d5bbbd 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -190,7 +190,7 @@
pointerCount, MAX_POINTERS);
return false;
}
- BitSet32 pointerIdBits;
+ std::bitset<MAX_POINTER_ID + 1> pointerIdBits;
for (size_t i = 0; i < pointerCount; i++) {
int32_t id = pointerProperties[i].id;
if (id < 0 || id > MAX_POINTER_ID) {
@@ -198,11 +198,11 @@
MAX_POINTER_ID);
return false;
}
- if (pointerIdBits.hasBit(id)) {
+ if (pointerIdBits.test(id)) {
ALOGE("Motion event has duplicate pointer id %d", id);
return false;
}
- pointerIdBits.markBit(id);
+ pointerIdBits.set(id);
}
return true;
}
@@ -292,6 +292,17 @@
first->applicationInfo.token == second->applicationInfo.token;
}
+template <typename T>
+size_t firstMarkedBit(T set) {
+ // TODO: replace with std::countr_zero from <bit> when that's available
+ LOG_ALWAYS_FATAL_IF(set.none());
+ size_t i = 0;
+ while (!set.test(i)) {
+ i++;
+ }
+ return i;
+}
+
std::unique_ptr<DispatchEntry> createDispatchEntry(
const InputTarget& inputTarget, std::shared_ptr<EventEntry> eventEntry,
ftl::Flags<InputTarget::Flags> inputTargetFlags) {
@@ -312,7 +323,7 @@
// as long as all other pointers are normalized to the same value and the final DispatchEntry
// uses the transform for the normalized pointer.
const ui::Transform& firstPointerTransform =
- inputTarget.pointerTransforms[inputTarget.pointerIds.firstMarkedBit()];
+ inputTarget.pointerTransforms[firstMarkedBit(inputTarget.pointerIds)];
ui::Transform inverseFirstTransform = firstPointerTransform.inverse();
// Iterate through all pointers in the event to normalize against the first.
@@ -591,7 +602,7 @@
TouchedWindow touchedWindow;
touchedWindow.windowHandle = oldWindow;
touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_HOVER_EXIT;
- touchedWindow.pointerIds.markBit(pointerId);
+ touchedWindow.pointerIds.set(pointerId);
out.push_back(touchedWindow);
}
}
@@ -608,7 +619,7 @@
LOG_ALWAYS_FATAL_IF(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE);
touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_IS;
}
- touchedWindow.pointerIds.markBit(pointerId);
+ touchedWindow.pointerIds.set(pointerId);
if (canReceiveForegroundTouches(*newWindow->getInfo())) {
touchedWindow.targetFlags |= InputTarget::Flags::FOREGROUND;
}
@@ -670,8 +681,7 @@
while (!mConnectionsByToken.empty()) {
sp<Connection> connection = mConnectionsByToken.begin()->second;
- removeInputChannelLocked(connection->inputChannel->getConnectionToken(),
- false /* notify */);
+ removeInputChannelLocked(connection->inputChannel->getConnectionToken(), /*notify=*/false);
}
}
@@ -1023,7 +1033,7 @@
if (isPointerDownEvent && mAwaitedFocusedApplication != nullptr) {
const int32_t displayId = motionEntry.displayId;
const auto [x, y] = resolveTouchedPosition(motionEntry);
- const bool isStylus = isPointerFromStylus(motionEntry, 0 /*pointerIndex*/);
+ const bool isStylus = isPointerFromStylus(motionEntry, /*pointerIndex=*/0);
auto [touchedWindowHandle, _] = findTouchedWindowAtLocked(displayId, x, y, isStylus);
if (touchedWindowHandle != nullptr &&
@@ -1166,7 +1176,7 @@
if (info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) {
addWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE,
- BitSet32(0), /*firstDownTimeInTarget=*/std::nullopt,
+ /*pointerIds=*/{}, /*firstDownTimeInTarget=*/std::nullopt,
outsideTargets);
}
}
@@ -1663,7 +1673,7 @@
std::vector<InputTarget> inputTargets;
addWindowTargetLocked(focusedWindow,
InputTarget::Flags::FOREGROUND | InputTarget::Flags::DISPATCH_AS_IS,
- BitSet32(0), getDownTime(*entry), inputTargets);
+ /*pointerIds=*/{}, getDownTime(*entry), inputTargets);
// Add monitor channels from event's or focused display.
addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
@@ -1772,7 +1782,7 @@
addWindowTargetLocked(focusedWindow,
InputTarget::Flags::FOREGROUND |
InputTarget::Flags::DISPATCH_AS_IS,
- BitSet32(0), getDownTime(*entry), inputTargets);
+ /*pointerIds=*/{}, getDownTime(*entry), inputTargets);
}
}
if (injectionResult == InputEventInjectionResult::PENDING) {
@@ -2127,7 +2137,7 @@
// Eventually, touchedWindow will contain the deviceId of each pointer that's currently
// being sent there. For now, use deviceId from touch state.
- if (entry.deviceId == touchState.deviceId && !touchedWindow.pointerIds.isEmpty()) {
+ if (entry.deviceId == touchState.deviceId && touchedWindow.pointerIds.any()) {
return false;
}
}
@@ -2175,8 +2185,9 @@
const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE);
if (newGesture) {
- bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN;
- if (switchedDevice && tempTouchState.isDown() && !down && !isHoverAction) {
+ // If pointers are already down, let's finish the current gesture and ignore the new events
+ // from another device.
+ if (switchedDevice && wasDown) {
ALOGI("Dropping event because a pointer for a different device is already down "
"in display %" PRId32,
displayId);
@@ -2298,13 +2309,18 @@
}
// Update the temporary touch state.
- BitSet32 pointerIds;
+ std::bitset<MAX_POINTER_ID + 1> pointerIds;
if (!isHoverAction) {
- pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
+ pointerIds.set(entry.pointerProperties[pointerIndex].id);
}
+ const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
+ maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN;
+
tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds,
- entry.eventTime);
+ isDownOrPointerDown
+ ? std::make_optional(entry.eventTime)
+ : std::nullopt);
// If this is the pointer going down and the touched window has a wallpaper
// then also add the touched wallpaper windows so they are locked in for the duration
@@ -2312,8 +2328,7 @@
// We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper
// engine only supports touch events. We would need to add a mechanism similar
// to View.onGenericMotionEvent to enable wallpapers to handle these events.
- if (maskedAction == AMOTION_EVENT_ACTION_DOWN ||
- maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+ if (isDownOrPointerDown) {
if (targetFlags.test(InputTarget::Flags::FOREGROUND) &&
windowHandle->getInfo()->inputConfig.test(
gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
@@ -2333,14 +2348,22 @@
}
}
- // If any existing window is pilfering pointers from newly added window, remove it
- BitSet32 canceledPointers = BitSet32(0);
- for (const TouchedWindow& window : tempTouchState.windows) {
- if (window.isPilferingPointers) {
- canceledPointers |= window.pointerIds;
+ // If a window is already pilfering some pointers, give it this new pointer as well and
+ // make it pilfering. This will prevent other non-spy windows from getting this pointer,
+ // which is a specific behaviour that we want.
+ const int32_t pointerId = entry.pointerProperties[pointerIndex].id;
+ for (TouchedWindow& touchedWindow : tempTouchState.windows) {
+ if (touchedWindow.pointerIds.test(pointerId) &&
+ touchedWindow.pilferedPointerIds.count() > 0) {
+ // This window is already pilfering some pointers, and this new pointer is also
+ // going to it. Therefore, take over this pointer and don't give it to anyone
+ // else.
+ touchedWindow.pilferedPointerIds.set(pointerId);
}
}
- tempTouchState.cancelPointersForNonPilferingWindows(canceledPointers);
+
+ // Restrict all pilfered pointers to the pilfering windows.
+ tempTouchState.cancelPointersForNonPilferingWindows();
} else {
/* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
@@ -2360,7 +2383,7 @@
if (maskedAction == AMOTION_EVENT_ACTION_MOVE && entry.pointerCount == 1 &&
tempTouchState.isSlippery()) {
const auto [x, y] = resolveTouchedPosition(entry);
- const bool isStylus = isPointerFromStylus(entry, 0 /*pointerIndex*/);
+ const bool isStylus = isPointerFromStylus(entry, /*pointerIndex=*/0);
sp<WindowInfoHandle> oldTouchedWindowHandle =
tempTouchState.getFirstForegroundWindowHandle();
auto [newTouchedWindowHandle, _] = findTouchedWindowAtLocked(displayId, x, y, isStylus);
@@ -2386,9 +2409,9 @@
newTouchedWindowHandle->getName().c_str(), displayId);
}
// Make a slippery exit from the old window.
- BitSet32 pointerIds;
+ std::bitset<MAX_POINTER_ID + 1> pointerIds;
const int32_t pointerId = entry.pointerProperties[0].id;
- pointerIds.markBit(pointerId);
+ pointerIds.set(pointerId);
const TouchedWindow& touchedWindow =
tempTouchState.getTouchedWindow(oldTouchedWindowHandle);
@@ -2435,7 +2458,7 @@
if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) {
continue;
}
- touchedWindow.pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
+ touchedWindow.pointerIds.set(entry.pointerProperties[pointerIndex].id);
}
}
}
@@ -2509,6 +2532,11 @@
// Success! Output targets from the touch state.
tempTouchState.clearWindowsWithoutPointers();
for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
+ if (touchedWindow.pointerIds.none() && !touchedWindow.hasHoveringPointers(entry.deviceId)) {
+ // Windows with hovering pointers are getting persisted inside TouchState.
+ // Do not send this event to those windows.
+ continue;
+ }
addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget,
targets);
@@ -2559,8 +2587,8 @@
for (size_t i = 0; i < tempTouchState.windows.size();) {
TouchedWindow& touchedWindow = tempTouchState.windows[i];
- touchedWindow.pointerIds.clearBit(pointerId);
- if (touchedWindow.pointerIds.isEmpty()) {
+ touchedWindow.pointerIds.reset(pointerId);
+ if (touchedWindow.pointerIds.none()) {
tempTouchState.windows.erase(tempTouchState.windows.begin() + i);
continue;
}
@@ -2591,7 +2619,7 @@
constexpr bool isStylus = false;
auto [dropWindow, _] =
- findTouchedWindowAtLocked(displayId, x, y, isStylus, true /*ignoreDragWindow*/);
+ findTouchedWindowAtLocked(displayId, x, y, isStylus, /*ignoreDragWindow=*/true);
if (dropWindow) {
vec2 local = dropWindow->getInfo()->transform.transform(x, y);
sendDropWindowCommandLocked(dropWindow->getToken(), local.x, local.y);
@@ -2648,19 +2676,19 @@
constexpr bool isStylus = false;
auto [hoverWindowHandle, _] = findTouchedWindowAtLocked(entry.displayId, x, y, isStylus,
- true /*ignoreDragWindow*/);
+ /*ignoreDragWindow=*/true);
// enqueue drag exit if needed.
if (hoverWindowHandle != mDragState->dragHoverWindowHandle &&
!haveSameToken(hoverWindowHandle, mDragState->dragHoverWindowHandle)) {
if (mDragState->dragHoverWindowHandle != nullptr) {
- enqueueDragEventLocked(mDragState->dragHoverWindowHandle, true /*isExiting*/, x,
+ enqueueDragEventLocked(mDragState->dragHoverWindowHandle, /*isExiting=*/true, x,
y);
}
mDragState->dragHoverWindowHandle = hoverWindowHandle;
}
// enqueue drag location if needed.
if (hoverWindowHandle != nullptr) {
- enqueueDragEventLocked(hoverWindowHandle, false /*isExiting*/, x, y);
+ enqueueDragEventLocked(hoverWindowHandle, /*isExiting=*/false, x, y);
}
break;
}
@@ -2685,7 +2713,7 @@
void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHandle,
ftl::Flags<InputTarget::Flags> targetFlags,
- BitSet32 pointerIds,
+ std::bitset<MAX_POINTER_ID + 1> pointerIds,
std::optional<nsecs_t> firstDownTimeInTarget,
std::vector<InputTarget>& inputTargets) const {
std::vector<InputTarget>::iterator it =
@@ -2995,9 +3023,9 @@
}
if (DEBUG_DISPATCH_CYCLE) {
ALOGD("channel '%s' ~ prepareDispatchCycle - flags=%s, "
- "globalScaleFactor=%f, pointerIds=0x%x %s",
+ "globalScaleFactor=%f, pointerIds=%s %s",
connection->getInputChannelName().c_str(), inputTarget.flags.string().c_str(),
- inputTarget.globalScaleFactor, inputTarget.pointerIds.value,
+ inputTarget.globalScaleFactor, bitsetToString(inputTarget.pointerIds).c_str(),
inputTarget.getPointerInfoString().c_str());
}
@@ -3165,6 +3193,9 @@
}
dispatchEntry->resolvedFlags = motionEntry.flags;
+ if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_CANCEL) {
+ dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_CANCELED;
+ }
if (dispatchEntry->targetFlags.test(InputTarget::Flags::WINDOW_IS_OBSCURED)) {
dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
}
@@ -3335,8 +3366,7 @@
// Don't apply window scale here since we don't want scale to affect raw
// coordinates. The scale will be sent back to the client and applied
// later when requesting relative coordinates.
- scaledCoords[i].scale(globalScaleFactor, 1 /* windowXScale */,
- 1 /* windowYScale */);
+ scaledCoords[i].scale(globalScaleFactor, /*windowXScale=*/1, /*windowYScale=*/1);
}
usingCoords = scaledCoords;
}
@@ -3472,7 +3502,7 @@
"event to it, status=%s(%d)",
connection->getInputChannelName().c_str(), statusToString(status).c_str(),
status);
- abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
+ abortBrokenDispatchCycleLocked(currentTime, connection, /*notify=*/true);
} else {
// Pipe is full and we are waiting for the app to finish process some events
// before sending more events to it.
@@ -3487,7 +3517,7 @@
"status=%s(%d)",
connection->getInputChannelName().c_str(), statusToString(status).c_str(),
status);
- abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
+ abortBrokenDispatchCycleLocked(currentTime, connection, /*notify=*/true);
}
return;
}
@@ -3732,9 +3762,9 @@
}
if (DEBUG_OUTBOUND_EVENT_DETAILS) {
ALOGD("channel '%s' ~ Synthesized %zu cancelation events to bring channel back in sync "
- "with reality: %s, mode=%d.",
+ "with reality: %s, mode=%s.",
connection->getInputChannelName().c_str(), cancelationEvents.size(), options.reason,
- options.mode);
+ ftl::enum_string(options.mode).c_str());
}
std::string reason = std::string("reason=").append(options.reason);
@@ -3868,8 +3898,9 @@
}
std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent(
- const MotionEntry& originalMotionEntry, BitSet32 pointerIds, nsecs_t splitDownTime) {
- ALOG_ASSERT(pointerIds.value != 0);
+ const MotionEntry& originalMotionEntry, std::bitset<MAX_POINTER_ID + 1> pointerIds,
+ nsecs_t splitDownTime) {
+ ALOG_ASSERT(pointerIds.any());
uint32_t splitPointerIndexMap[MAX_POINTERS];
PointerProperties splitPointerProperties[MAX_POINTERS];
@@ -3883,7 +3914,7 @@
const PointerProperties& pointerProperties =
originalMotionEntry.pointerProperties[originalPointerIndex];
uint32_t pointerId = uint32_t(pointerProperties.id);
- if (pointerIds.hasBit(pointerId)) {
+ if (pointerIds.test(pointerId)) {
splitPointerIndexMap[splitPointerCount] = originalPointerIndex;
splitPointerProperties[splitPointerCount].copyFrom(pointerProperties);
splitPointerCoords[splitPointerCount].copyFrom(
@@ -3899,7 +3930,7 @@
// or ACTION_POINTER_DOWN events that caused us to decide to split the pointers
// in this way.
ALOGW("Dropping split motion event because the pointer count is %d but "
- "we expected there to be %d pointers. This probably means we received "
+ "we expected there to be %zu pointers. This probably means we received "
"a broken sequence of pointer ids from the input device.",
splitPointerCount, pointerIds.count());
return nullptr;
@@ -3913,7 +3944,7 @@
const PointerProperties& pointerProperties =
originalMotionEntry.pointerProperties[originalPointerIndex];
uint32_t pointerId = uint32_t(pointerProperties.id);
- if (pointerIds.hasBit(pointerId)) {
+ if (pointerIds.test(pointerId)) {
if (pointerIds.count() == 1) {
// The first/last pointer went down/up.
action = maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
@@ -3939,8 +3970,8 @@
if (action == AMOTION_EVENT_ACTION_DOWN) {
LOG_ALWAYS_FATAL_IF(splitDownTime != originalMotionEntry.eventTime,
"Split motion event has mismatching downTime and eventTime for "
- "ACTION_DOWN, motionEntry=%s, splitDownTime=%" PRId64 "ms",
- originalMotionEntry.getDescription().c_str(), ns2ms(splitDownTime));
+ "ACTION_DOWN, motionEntry=%s, splitDownTime=%" PRId64,
+ originalMotionEntry.getDescription().c_str(), splitDownTime);
}
int32_t newId = mIdGenerator.nextId();
@@ -4155,6 +4186,17 @@
bool needWake = false;
{ // acquire lock
mLock.lock();
+ if (!(policyFlags & POLICY_FLAG_PASS_TO_USER)) {
+ // Set the flag anyway if we already have an ongoing gesture. That would allow us to
+ // complete the processing of the current stroke.
+ const auto touchStateIt = mTouchStatesByDisplay.find(args->displayId);
+ if (touchStateIt != mTouchStatesByDisplay.end()) {
+ const TouchState& touchState = touchStateIt->second;
+ if (touchState.deviceId == args->deviceId && touchState.isDown()) {
+ policyFlags |= POLICY_FLAG_PASS_TO_USER;
+ }
+ }
+ }
if (shouldSendMotionToInputFilterLocked(args)) {
ui::Transform displayTransform;
@@ -4224,7 +4266,7 @@
// Just enqueue a new sensor event.
std::unique_ptr<SensorEntry> newEntry =
std::make_unique<SensorEntry>(args->id, args->eventTime, args->deviceId,
- args->source, 0 /* policyFlags*/, args->hwTimestamp,
+ args->source, /* policyFlags=*/0, args->hwTimestamp,
args->sensorType, args->accuracy,
args->accuracyChanged, args->values);
@@ -5224,7 +5266,7 @@
// Erase old window.
ftl::Flags<InputTarget::Flags> oldTargetFlags = touchedWindow->targetFlags;
- BitSet32 pointerIds = touchedWindow->pointerIds;
+ std::bitset<MAX_POINTER_ID + 1> pointerIds = touchedWindow->pointerIds;
sp<WindowInfoHandle> fromWindowHandle = touchedWindow->windowHandle;
state->removeWindowByToken(fromToken);
@@ -5245,7 +5287,7 @@
return false;
}
// Track the pointer id for drag window and generate the drag state.
- const int32_t id = pointerIds.firstMarkedBit();
+ const size_t id = firstMarkedBit(pointerIds);
mDragState = std::make_unique<DragState>(toWindowHandle, id);
}
@@ -5618,7 +5660,7 @@
const sp<IBinder>& token = serverChannel->getConnectionToken();
int fd = serverChannel->getFd();
sp<Connection> connection =
- sp<Connection>::make(std::move(serverChannel), false /*monitor*/, mIdGenerator);
+ sp<Connection>::make(std::move(serverChannel), /*monitor=*/false, mIdGenerator);
if (mConnectionsByToken.find(token) != mConnectionsByToken.end()) {
ALOGE("Created a new connection, but the token %p is already known", token.get());
@@ -5656,7 +5698,7 @@
}
sp<Connection> connection =
- sp<Connection>::make(serverChannel, true /*monitor*/, mIdGenerator);
+ sp<Connection>::make(serverChannel, /*monitor=*/true, mIdGenerator);
const sp<IBinder>& token = serverChannel->getConnectionToken();
const int fd = serverChannel->getFd();
@@ -5682,7 +5724,7 @@
{ // acquire lock
std::scoped_lock _l(mLock);
- status_t status = removeInputChannelLocked(connectionToken, false /*notify*/);
+ status_t status = removeInputChannelLocked(connectionToken, /*notify=*/false);
if (status) {
return status;
}
@@ -5745,7 +5787,7 @@
}
auto [statePtr, windowPtr, displayId] = findTouchStateWindowAndDisplayLocked(token);
- if (statePtr == nullptr || windowPtr == nullptr || windowPtr->pointerIds.isEmpty()) {
+ if (statePtr == nullptr || windowPtr == nullptr || windowPtr->pointerIds.none()) {
ALOGW("Attempted to pilfer points from a channel without any on-going pointer streams."
" Ignoring.");
return BAD_VALUE;
@@ -5775,7 +5817,7 @@
// Prevent the gesture from being sent to any other windows.
// This only blocks relevant pointers to be sent to other windows
- window.isPilferingPointers = true;
+ window.pilferedPointerIds |= window.pointerIds;
state.cancelPointersForWindowsExcept(window.pointerIds, token);
return OK;
@@ -6387,11 +6429,11 @@
CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
"focus left window");
synthesizeCancelationEventsForInputChannelLocked(focusedInputChannel, options);
- enqueueFocusEventLocked(changes.oldFocus, false /*hasFocus*/, changes.reason);
+ enqueueFocusEventLocked(changes.oldFocus, /*hasFocus=*/false, changes.reason);
}
}
if (changes.newFocus) {
- enqueueFocusEventLocked(changes.newFocus, true /*hasFocus*/, changes.reason);
+ enqueueFocusEventLocked(changes.newFocus, /*hasFocus=*/true, changes.reason);
}
// If a window has pointer capture, then it must have focus. We need to ensure that this
@@ -6544,8 +6586,8 @@
const sp<WindowInfoHandle>& newWindowHandle,
TouchState& state, int32_t pointerId,
std::vector<InputTarget>& targets) {
- BitSet32 pointerIds;
- pointerIds.markBit(pointerId);
+ std::bitset<MAX_POINTER_ID + 1> pointerIds;
+ pointerIds.set(pointerId);
const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test(
gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
const bool newHasWallpaper = targetFlags.test(InputTarget::Flags::FOREGROUND) &&
@@ -6581,7 +6623,8 @@
ftl::Flags<InputTarget::Flags> newTargetFlags,
const sp<WindowInfoHandle> fromWindowHandle,
const sp<WindowInfoHandle> toWindowHandle,
- TouchState& state, const BitSet32& pointerIds) {
+ TouchState& state,
+ std::bitset<MAX_POINTER_ID + 1> pointerIds) {
const bool oldHasWallpaper = oldTargetFlags.test(InputTarget::Flags::FOREGROUND) &&
fromWindowHandle->getInfo()->inputConfig.test(
gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 81f8de8..b94858b 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -46,6 +46,7 @@
#include <utils/Looper.h>
#include <utils/Timers.h>
#include <utils/threads.h>
+#include <bitset>
#include <condition_variable>
#include <deque>
#include <optional>
@@ -550,7 +551,8 @@
const std::vector<Monitor>& gestureMonitors) const REQUIRES(mLock);
void addWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
- ftl::Flags<InputTarget::Flags> targetFlags, BitSet32 pointerIds,
+ ftl::Flags<InputTarget::Flags> targetFlags,
+ std::bitset<MAX_POINTER_ID + 1> pointerIds,
std::optional<nsecs_t> firstDownTimeInTarget,
std::vector<InputTarget>& inputTargets) const REQUIRES(mLock);
void addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets, int32_t displayId)
@@ -636,7 +638,8 @@
// Splitting motion events across windows. When splitting motion event for a target,
// splitDownTime refers to the time of first 'down' event on that particular target
std::unique_ptr<MotionEntry> splitMotionEvent(const MotionEntry& originalMotionEntry,
- BitSet32 pointerIds, nsecs_t splitDownTime);
+ std::bitset<MAX_POINTER_ID + 1> pointerIds,
+ nsecs_t splitDownTime);
// Reset and drop everything the dispatcher is doing.
void resetAndDropEverythingLocked(const char* reason) REQUIRES(mLock);
@@ -703,7 +706,8 @@
ftl::Flags<InputTarget::Flags> newTargetFlags,
const sp<android::gui::WindowInfoHandle> fromWindowHandle,
const sp<android::gui::WindowInfoHandle> toWindowHandle,
- TouchState& state, const BitSet32& pointerIds) REQUIRES(mLock);
+ TouchState& state, std::bitset<MAX_POINTER_ID + 1> pointerIds)
+ REQUIRES(mLock);
sp<android::gui::WindowInfoHandle> findWallpaperWindowBelow(
const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 563868d..ad5a7fd 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -97,7 +97,7 @@
switch (actionMasked) {
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_CANCEL: {
- ssize_t index = findMotionMemento(entry, false /*hovering*/);
+ ssize_t index = findMotionMemento(entry, /*hovering=*/false);
if (index >= 0) {
mMotionMementos.erase(mMotionMementos.begin() + index);
return true;
@@ -111,11 +111,11 @@
}
case AMOTION_EVENT_ACTION_DOWN: {
- ssize_t index = findMotionMemento(entry, false /*hovering*/);
+ ssize_t index = findMotionMemento(entry, /*hovering=*/false);
if (index >= 0) {
mMotionMementos.erase(mMotionMementos.begin() + index);
}
- addMotionMemento(entry, flags, false /*hovering*/);
+ addMotionMemento(entry, flags, /*hovering=*/false);
return true;
}
@@ -129,7 +129,7 @@
return true;
}
- ssize_t index = findMotionMemento(entry, false /*hovering*/);
+ ssize_t index = findMotionMemento(entry, /*hovering=*/false);
if (entry.source & AINPUT_SOURCE_CLASS_JOYSTICK) {
// Joysticks can send MOVE events without a corresponding DOWN or UP. Since all
@@ -145,7 +145,7 @@
memento.setPointers(entry);
}
} else if (!entry.pointerCoords[0].isEmpty()) {
- addMotionMemento(entry, flags, false /*hovering*/);
+ addMotionMemento(entry, flags, /*hovering=*/false);
}
// Joysticks and trackballs can send MOVE events without corresponding DOWN or UP.
@@ -168,7 +168,7 @@
}
case AMOTION_EVENT_ACTION_HOVER_EXIT: {
- ssize_t index = findMotionMemento(entry, true /*hovering*/);
+ ssize_t index = findMotionMemento(entry, /*hovering=*/true);
if (index >= 0) {
mMotionMementos.erase(mMotionMementos.begin() + index);
return true;
@@ -183,11 +183,11 @@
case AMOTION_EVENT_ACTION_HOVER_ENTER:
case AMOTION_EVENT_ACTION_HOVER_MOVE: {
- ssize_t index = findMotionMemento(entry, true /*hovering*/);
+ ssize_t index = findMotionMemento(entry, /*hovering=*/true);
if (index >= 0) {
mMotionMementos.erase(mMotionMementos.begin() + index);
}
- addMotionMemento(entry, flags, true /*hovering*/);
+ addMotionMemento(entry, flags, /*hovering=*/true);
return true;
}
@@ -280,7 +280,7 @@
memento.policyFlags, AKEY_EVENT_ACTION_UP,
memento.flags | AKEY_EVENT_FLAG_CANCELED,
memento.keyCode, memento.scanCode, memento.metaState,
- 0 /*repeatCount*/, memento.downTime));
+ /*repeatCount=*/0, memento.downTime));
}
}
@@ -289,13 +289,16 @@
if (options.pointerIds == std::nullopt) {
const int32_t action = memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT
: AMOTION_EVENT_ACTION_CANCEL;
+ int32_t flags = memento.flags;
+ if (action == AMOTION_EVENT_ACTION_CANCEL) {
+ flags |= AMOTION_EVENT_FLAG_CANCELED;
+ }
events.push_back(
std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime,
memento.deviceId, memento.source,
memento.displayId, memento.policyFlags,
- action, 0 /*actionButton*/, memento.flags,
- AMETA_NONE, 0 /*buttonState*/,
- MotionClassification::NONE,
+ action, /*actionButton=*/0, flags, AMETA_NONE,
+ /*buttonState=*/0, MotionClassification::NONE,
AMOTION_EVENT_EDGE_FLAG_NONE,
memento.xPrecision, memento.yPrecision,
memento.xCursorPosition,
@@ -356,8 +359,8 @@
std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime,
memento.deviceId, memento.source,
memento.displayId, memento.policyFlags, action,
- 0 /*actionButton*/, memento.flags, AMETA_NONE,
- 0 /*buttonState*/, MotionClassification::NONE,
+ /*actionButton=*/0, memento.flags, AMETA_NONE,
+ /*buttonState=*/0, MotionClassification::NONE,
AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
memento.yPrecision, memento.xCursorPosition,
memento.yCursorPosition, memento.downTime,
@@ -371,7 +374,8 @@
}
std::vector<std::unique_ptr<MotionEntry>> InputState::synthesizeCancelationEventsForPointers(
- const MotionMemento& memento, const BitSet32 pointerIds, nsecs_t currentTime) {
+ const MotionMemento& memento, std::bitset<MAX_POINTER_ID + 1> pointerIds,
+ nsecs_t currentTime) {
std::vector<std::unique_ptr<MotionEntry>> events;
std::vector<uint32_t> canceledPointerIndices;
std::vector<PointerProperties> pointerProperties(MAX_POINTERS);
@@ -380,7 +384,7 @@
uint32_t pointerId = uint32_t(memento.pointerProperties[pointerIdx].id);
pointerProperties[pointerIdx].copyFrom(memento.pointerProperties[pointerIdx]);
pointerCoords[pointerIdx].copyFrom(memento.pointerCoords[pointerIdx]);
- if (pointerIds.hasBit(pointerId)) {
+ if (pointerIds.test(pointerId)) {
canceledPointerIndices.push_back(pointerIdx);
}
}
@@ -388,11 +392,15 @@
if (canceledPointerIndices.size() == memento.pointerCount) {
const int32_t action =
memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT : AMOTION_EVENT_ACTION_CANCEL;
+ int32_t flags = memento.flags;
+ if (action == AMOTION_EVENT_ACTION_CANCEL) {
+ flags |= AMOTION_EVENT_FLAG_CANCELED;
+ }
events.push_back(
std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime, memento.deviceId,
memento.source, memento.displayId,
- memento.policyFlags, action, 0 /*actionButton*/,
- memento.flags, AMETA_NONE, 0 /*buttonState*/,
+ memento.policyFlags, action, /*actionButton=*/0,
+ flags, AMETA_NONE, /*buttonState=*/0,
MotionClassification::NONE,
AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
memento.yPrecision, memento.xCursorPosition,
@@ -400,7 +408,7 @@
memento.pointerCount, memento.pointerProperties,
memento.pointerCoords));
} else {
- // If we aren't canceling all pointers, we need to generated ACTION_POINTER_UP with
+ // If we aren't canceling all pointers, we need to generate ACTION_POINTER_UP with
// FLAG_CANCELED for each of the canceled pointers. For each event, we must remove the
// previously canceled pointers from PointerProperties and PointerCoords, and update
// pointerCount appropriately. For convenience, sort the canceled pointer indices so that we
@@ -418,9 +426,9 @@
std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime,
memento.deviceId, memento.source,
memento.displayId, memento.policyFlags, action,
- 0 /*actionButton*/,
+ /*actionButton=*/0,
memento.flags | AMOTION_EVENT_FLAG_CANCELED,
- AMETA_NONE, 0 /*buttonState*/,
+ AMETA_NONE, /*buttonState=*/0,
MotionClassification::NONE,
AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
memento.yPrecision, memento.xCursorPosition,
diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h
index 6ab48c9..42d8cc6 100644
--- a/services/inputflinger/dispatcher/InputState.h
+++ b/services/inputflinger/dispatcher/InputState.h
@@ -20,6 +20,7 @@
#include "Entry.h"
#include <utils/Timers.h>
+#include <bitset>
namespace android {
namespace inputdispatcher {
@@ -128,7 +129,8 @@
// Synthesizes pointer cancel events for a particular set of pointers.
std::vector<std::unique_ptr<MotionEntry>> synthesizeCancelationEventsForPointers(
- const MotionMemento& memento, const BitSet32 pointerIds, nsecs_t currentTime);
+ const MotionMemento& memento, std::bitset<MAX_POINTER_ID + 1> pointerIds,
+ nsecs_t currentTime);
};
} // namespace inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputTarget.cpp b/services/inputflinger/dispatcher/InputTarget.cpp
index 2f39480..fc8b785 100644
--- a/services/inputflinger/dispatcher/InputTarget.cpp
+++ b/services/inputflinger/dispatcher/InputTarget.cpp
@@ -24,31 +24,34 @@
namespace android::inputdispatcher {
-void InputTarget::addPointers(BitSet32 newPointerIds, const ui::Transform& transform) {
+void InputTarget::addPointers(std::bitset<MAX_POINTER_ID + 1> newPointerIds,
+ const ui::Transform& transform) {
// The pointerIds can be empty, but still a valid InputTarget. This can happen when there is no
// valid pointer property from the input event.
- if (newPointerIds.isEmpty()) {
+ if (newPointerIds.none()) {
setDefaultPointerTransform(transform);
return;
}
// Ensure that the new set of pointers doesn't overlap with the current set of pointers.
- ALOG_ASSERT((pointerIds & newPointerIds) == 0);
+ LOG_ALWAYS_FATAL_IF((pointerIds & newPointerIds).any());
pointerIds |= newPointerIds;
- while (!newPointerIds.isEmpty()) {
- int32_t pointerId = newPointerIds.clearFirstMarkedBit();
- pointerTransforms[pointerId] = transform;
+ for (size_t i = 0; i < newPointerIds.size(); i++) {
+ if (!newPointerIds.test(i)) {
+ continue;
+ }
+ pointerTransforms[i] = transform;
}
}
void InputTarget::setDefaultPointerTransform(const ui::Transform& transform) {
- pointerIds.clear();
+ pointerIds.reset();
pointerTransforms[0] = transform;
}
bool InputTarget::useDefaultPointerTransform() const {
- return pointerIds.isEmpty();
+ return pointerIds.none();
}
const ui::Transform& InputTarget::getDefaultPointerTransform() const {
@@ -63,8 +66,8 @@
return out;
}
- for (uint32_t i = pointerIds.firstMarkedBit(); i <= pointerIds.lastMarkedBit(); i++) {
- if (!pointerIds.hasBit(i)) {
+ for (uint32_t i = 0; i < pointerIds.size(); i++) {
+ if (!pointerIds.test(i)) {
continue;
}
diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index 61b07fe..7b12f81 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -21,6 +21,7 @@
#include <input/InputTransport.h>
#include <ui/Transform.h>
#include <utils/BitSet.h>
+#include <bitset>
namespace android::inputdispatcher {
@@ -105,7 +106,7 @@
// The subset of pointer ids to include in motion events dispatched to this input target
// if FLAG_SPLIT is set.
- BitSet32 pointerIds;
+ std::bitset<MAX_POINTER_ID + 1> pointerIds;
// Event time for the first motion event (ACTION_DOWN) dispatched to this input target if
// FLAG_SPLIT is set.
std::optional<nsecs_t> firstDownTimeInTarget;
@@ -113,7 +114,7 @@
// Transform per pointerId.
ui::Transform pointerTransforms[MAX_POINTERS];
- void addPointers(BitSet32 pointerIds, const ui::Transform& transform);
+ void addPointers(std::bitset<MAX_POINTER_ID + 1> pointerIds, const ui::Transform& transform);
void setDefaultPointerTransform(const ui::Transform& transform);
/**
diff --git a/services/inputflinger/dispatcher/LatencyTracker.cpp b/services/inputflinger/dispatcher/LatencyTracker.cpp
index 52f189c..2dd7d3f 100644
--- a/services/inputflinger/dispatcher/LatencyTracker.cpp
+++ b/services/inputflinger/dispatcher/LatencyTracker.cpp
@@ -148,7 +148,7 @@
void LatencyTracker::reportAndPruneMatureRecords(nsecs_t newEventTime) {
while (!mEventTimes.empty()) {
const auto& [oldestEventTime, oldestInputEventId] = *mEventTimes.begin();
- if (isMatureEvent(oldestEventTime, newEventTime /*now*/)) {
+ if (isMatureEvent(oldestEventTime, /*now=*/newEventTime)) {
// Report and drop this event
const auto it = mTimelines.find(oldestInputEventId);
LOG_ALWAYS_FATAL_IF(it == mTimelines.end(),
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index ad37d02..4258471 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -33,7 +33,7 @@
void TouchState::removeTouchedPointer(int32_t pointerId) {
for (TouchedWindow& touchedWindow : windows) {
- touchedWindow.pointerIds.clearBit(pointerId);
+ touchedWindow.removeTouchingPointer(pointerId);
}
}
@@ -41,7 +41,7 @@
int32_t pointerId, const sp<android::gui::WindowInfoHandle>& windowHandle) {
for (TouchedWindow& touchedWindow : windows) {
if (touchedWindow.windowHandle == windowHandle) {
- touchedWindow.pointerIds.clearBit(pointerId);
+ touchedWindow.removeTouchingPointer(pointerId);
return;
}
}
@@ -55,13 +55,14 @@
void TouchState::clearWindowsWithoutPointers() {
std::erase_if(windows, [](const TouchedWindow& w) {
- return w.pointerIds.isEmpty() && !w.hasHoveringPointers();
+ return w.pointerIds.none() && !w.hasHoveringPointers();
});
}
void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle,
- ftl::Flags<InputTarget::Flags> targetFlags, BitSet32 pointerIds,
- std::optional<nsecs_t> eventTime) {
+ ftl::Flags<InputTarget::Flags> targetFlags,
+ std::bitset<MAX_POINTER_ID + 1> pointerIds,
+ std::optional<nsecs_t> firstDownTimeInTarget) {
for (TouchedWindow& touchedWindow : windows) {
// We do not compare windows by token here because two windows that share the same token
// may have a different transform
@@ -73,9 +74,9 @@
// For cases like hover enter/exit or DISPATCH_AS_OUTSIDE a touch window might not have
// downTime set initially. Need to update existing window when an pointer is down for
// the window.
- touchedWindow.pointerIds.value |= pointerIds.value;
+ touchedWindow.pointerIds |= pointerIds;
if (!touchedWindow.firstDownTimeInTarget.has_value()) {
- touchedWindow.firstDownTimeInTarget = eventTime;
+ touchedWindow.firstDownTimeInTarget = firstDownTimeInTarget;
}
return;
}
@@ -84,7 +85,7 @@
touchedWindow.windowHandle = windowHandle;
touchedWindow.targetFlags = targetFlags;
touchedWindow.pointerIds = pointerIds;
- touchedWindow.firstDownTimeInTarget = eventTime;
+ touchedWindow.firstDownTimeInTarget = firstDownTimeInTarget;
windows.push_back(touchedWindow);
}
@@ -126,25 +127,44 @@
}
}
-void TouchState::cancelPointersForWindowsExcept(const BitSet32 pointerIds,
+void TouchState::cancelPointersForWindowsExcept(std::bitset<MAX_POINTER_ID + 1> pointerIds,
const sp<IBinder>& token) {
- if (pointerIds.isEmpty()) return;
+ if (pointerIds.none()) return;
std::for_each(windows.begin(), windows.end(), [&pointerIds, &token](TouchedWindow& w) {
if (w.windowHandle->getToken() != token) {
- w.pointerIds &= BitSet32(~pointerIds.value);
+ w.pointerIds &= ~pointerIds;
}
});
- std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.isEmpty(); });
+ std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.none(); });
}
-void TouchState::cancelPointersForNonPilferingWindows(const BitSet32 pointerIds) {
- if (pointerIds.isEmpty()) return;
- std::for_each(windows.begin(), windows.end(), [&pointerIds](TouchedWindow& w) {
- if (!w.isPilferingPointers) {
- w.pointerIds &= BitSet32(~pointerIds.value);
- }
+/**
+ * For any pointer that's being pilfered, remove it from all of the other windows that currently
+ * aren't pilfering it. For example, if we determined that pointer 1 is going to both window A and
+ * window B, but window A is currently pilfering pointer 1, then pointer 1 should not go to window
+ * B.
+ */
+void TouchState::cancelPointersForNonPilferingWindows() {
+ // First, find all pointers that are being pilfered, across all windows
+ std::bitset<MAX_POINTER_ID + 1> allPilferedPointerIds;
+ std::for_each(windows.begin(), windows.end(), [&allPilferedPointerIds](const TouchedWindow& w) {
+ allPilferedPointerIds |= w.pilferedPointerIds;
});
- std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.isEmpty(); });
+
+ // Optimization: most of the time, pilfering does not occur
+ if (allPilferedPointerIds.none()) return;
+
+ // Now, remove all pointers from every window that's being pilfered by other windows.
+ // For example, if window A is pilfering pointer 1 (only), and window B is pilfering pointer 2
+ // (only), the remove pointer 2 from window A and pointer 1 from window B. Usually, the set of
+ // pilfered pointers will be disjoint across all windows, but there's no reason to cause that
+ // limitation here.
+ std::for_each(windows.begin(), windows.end(), [&allPilferedPointerIds](TouchedWindow& w) {
+ std::bitset<MAX_POINTER_ID + 1> pilferedByOtherWindows =
+ w.pilferedPointerIds ^ allPilferedPointerIds;
+ w.pointerIds &= ~pilferedByOtherWindows;
+ });
+ std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.none(); });
}
sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle() const {
@@ -193,7 +213,7 @@
bool TouchState::isDown() const {
return std::any_of(windows.begin(), windows.end(),
- [](const TouchedWindow& window) { return !window.pointerIds.isEmpty(); });
+ [](const TouchedWindow& window) { return window.pointerIds.any(); });
}
std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(int32_t hoveringDeviceId,
@@ -212,7 +232,7 @@
window.removeHoveringPointer(hoveringDeviceId, hoveringPointerId);
}
std::erase_if(windows, [](const TouchedWindow& w) {
- return w.pointerIds.isEmpty() && !w.hasHoveringPointers();
+ return w.pointerIds.none() && !w.hasHoveringPointers();
});
}
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 4025db8..6e965d8 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -16,6 +16,7 @@
#pragma once
+#include <bitset>
#include <set>
#include "TouchedWindow.h"
@@ -46,8 +47,9 @@
void removeTouchedPointerFromWindow(int32_t pointerId,
const sp<android::gui::WindowInfoHandle>& windowHandle);
void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
- ftl::Flags<InputTarget::Flags> targetFlags, BitSet32 pointerIds,
- std::optional<nsecs_t> eventTime = std::nullopt);
+ ftl::Flags<InputTarget::Flags> targetFlags,
+ std::bitset<MAX_POINTER_ID + 1> pointerIds,
+ std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt);
void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
int32_t deviceId, int32_t hoveringPointerId);
void removeHoveringPointer(int32_t deviceId, int32_t hoveringPointerId);
@@ -56,10 +58,11 @@
void filterNonAsIsTouchWindows();
// Cancel pointers for current set of windows except the window with particular binder token.
- void cancelPointersForWindowsExcept(const BitSet32 pointerIds, const sp<IBinder>& token);
+ void cancelPointersForWindowsExcept(std::bitset<MAX_POINTER_ID + 1> pointerIds,
+ const sp<IBinder>& token);
// Cancel pointers for current set of non-pilfering windows i.e. windows with isPilferingWindow
// set to false.
- void cancelPointersForNonPilferingWindows(const BitSet32 pointerIds);
+ void cancelPointersForNonPilferingWindows();
sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle() const;
bool isSlippery() const;
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index 3704edd..99c4769 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -29,6 +29,10 @@
return !mHoveringPointerIdsByDevice.empty();
}
+bool TouchedWindow::hasHoveringPointers(int32_t deviceId) const {
+ return mHoveringPointerIdsByDevice.find(deviceId) != mHoveringPointerIdsByDevice.end();
+}
+
void TouchedWindow::clearHoveringPointers() {
mHoveringPointerIdsByDevice.clear();
}
@@ -46,6 +50,14 @@
it->second.set(pointerId);
}
+void TouchedWindow::removeTouchingPointer(int32_t pointerId) {
+ pointerIds.reset(pointerId);
+ pilferedPointerIds.reset(pointerId);
+ if (pointerIds.none()) {
+ firstDownTimeInTarget.reset();
+ }
+}
+
void TouchedWindow::removeHoveringPointer(int32_t deviceId, int32_t pointerId) {
const auto it = mHoveringPointerIdsByDevice.find(deviceId);
if (it == mHoveringPointerIdsByDevice.end()) {
@@ -62,11 +74,11 @@
std::string out;
std::string hoveringPointers =
dumpMap(mHoveringPointerIdsByDevice, constToString, bitsetToString);
- out += StringPrintf("name='%s', pointerIds=0x%0x, targetFlags=%s, firstDownTimeInTarget=%s, "
- "mHoveringPointerIdsByDevice=%s\n",
- windowHandle->getName().c_str(), pointerIds.value,
+ out += StringPrintf("name='%s', pointerIds=%s, targetFlags=%s, firstDownTimeInTarget=%s, "
+ "mHoveringPointerIdsByDevice=%s, pilferedPointerIds=%s\n",
+ windowHandle->getName().c_str(), bitsetToString(pointerIds).c_str(),
targetFlags.string().c_str(), toString(firstDownTimeInTarget).c_str(),
- hoveringPointers.c_str());
+ hoveringPointers.c_str(), bitsetToString(pilferedPointerIds).c_str());
return out;
}
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index add6b61..aa2e9dd 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -30,22 +30,25 @@
struct TouchedWindow {
sp<gui::WindowInfoHandle> windowHandle;
ftl::Flags<InputTarget::Flags> targetFlags;
- BitSet32 pointerIds;
- bool isPilferingPointers = false;
+ std::bitset<MAX_POINTER_ID + 1> pointerIds;
+ // The pointer ids of the pointers that this window is currently pilfering
+ std::bitset<MAX_POINTER_ID + 1> pilferedPointerIds;
// Time at which the first action down occurred on this window.
// NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE scenario.
std::optional<nsecs_t> firstDownTimeInTarget;
bool hasHoveringPointers() const;
+ bool hasHoveringPointers(int32_t deviceId) const;
bool hasHoveringPointer(int32_t deviceId, int32_t pointerId) const;
void addHoveringPointer(int32_t deviceId, int32_t pointerId);
void removeHoveringPointer(int32_t deviceId, int32_t pointerId);
+ void removeTouchingPointer(int32_t pointerId);
void clearHoveringPointers();
std::string dump() const;
private:
- std::map<int32_t /*deviceId*/, std::bitset<MAX_POINTERS>> mHoveringPointerIdsByDevice;
+ std::map<int32_t /*deviceId*/, std::bitset<MAX_POINTER_ID + 1>> mHoveringPointerIdsByDevice;
};
} // namespace inputdispatcher
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index d55ab28..841c914 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -161,7 +161,7 @@
struct InputReaderConfiguration {
// Describes changes that have occurred.
enum {
- // The pointer speed changed.
+ // The mouse pointer speed changed.
CHANGE_POINTER_SPEED = 1 << 0,
// The pointer gesture control changed.
@@ -200,6 +200,9 @@
// The stylus button reporting configurations has changed.
CHANGE_STYLUS_BUTTON_REPORTING = 1 << 12,
+ // The touchpad settings changed.
+ CHANGE_TOUCHPAD_SETTINGS = 1 << 13,
+
// All devices must be reopened.
CHANGE_MUST_REOPEN = 1 << 31,
};
@@ -309,6 +312,20 @@
// The latest request to enable or disable Pointer Capture.
PointerCaptureRequest pointerCaptureRequest;
+ // The touchpad pointer speed, as a number from -7 (slowest) to 7 (fastest).
+ int32_t touchpadPointerSpeed;
+
+ // True to invert the touchpad scrolling direction, so that moving two fingers downwards on the
+ // touchpad scrolls the content upwards.
+ bool touchpadNaturalScrollingEnabled;
+
+ // True to enable tap-to-click on touchpads.
+ bool touchpadTapToClickEnabled;
+
+ // True to enable a zone on the right-hand side of touchpads where clicks will be turned into
+ // context (a.k.a. "right") clicks.
+ bool touchpadRightClickZoneEnabled;
+
// The set of currently disabled input devices.
std::set<int32_t> disabledDevices;
@@ -316,6 +333,9 @@
// stylus button state changes are reported through motion events.
bool stylusButtonMotionEventsEnabled;
+ // True if a pointer icon should be shown for direct stylus pointers.
+ bool stylusPointerIconEnabled;
+
InputReaderConfiguration()
: virtualKeyQuietTime(0),
pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f,
@@ -337,7 +357,12 @@
pointerGestureZoomSpeedRatio(0.3f),
showTouches(false),
pointerCaptureRequest(),
- stylusButtonMotionEventsEnabled(true) {}
+ touchpadPointerSpeed(0),
+ touchpadNaturalScrollingEnabled(true),
+ touchpadTapToClickEnabled(true),
+ touchpadRightClickZoneEnabled(false),
+ stylusButtonMotionEventsEnabled(true),
+ stylusPointerIconEnabled(false) {}
static std::string changesToString(uint32_t changes);
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index 7e0c1c7..9dbdd5a 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -79,8 +79,10 @@
POINTER,
// Show spots and a spot anchor in place of the mouse pointer.
SPOT,
+ // Show the stylus hover pointer.
+ STYLUS_HOVER,
- ftl_last = SPOT,
+ ftl_last = STYLUS_HOVER,
};
/* Sets the mode of the pointer controller. */
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index f7b38a1..d3d8021 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -220,7 +220,7 @@
* directly from /dev.
*/
static bool isV4lScanningEnabled() {
- return property_get_bool("ro.input.video_enabled", true /* default_value */);
+ return property_get_bool("ro.input.video_enabled", /*default_value=*/true);
}
static nsecs_t processEventTimestamp(const struct input_event& event) {
@@ -1006,7 +1006,7 @@
}
int32_t outKeyCode;
status_t mapKeyRes =
- device->getKeyCharacterMap()->mapKey(scanCodes[0], 0 /*usageCode*/, &outKeyCode);
+ device->getKeyCharacterMap()->mapKey(scanCodes[0], /*usageCode=*/0, &outKeyCode);
switch (mapKeyRes) {
case OK:
break;
@@ -2544,7 +2544,7 @@
std::unique_ptr<Device> device =
std::make_unique<Device>(-1, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, "<virtual>",
- identifier, nullptr /*associatedDevice*/);
+ identifier, /*associatedDevice=*/nullptr);
device->classes = InputDeviceClass::KEYBOARD | InputDeviceClass::ALPHAKEY |
InputDeviceClass::DPAD | InputDeviceClass::VIRTUAL;
device->loadKeyMapLocked();
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index bd41fa5..c598c0a 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -209,10 +209,10 @@
// Touchscreens and touchpad devices.
static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY =
- sysprop::InputProperties::enable_touchpad_gestures_library().value_or(false);
+ sysprop::InputProperties::enable_touchpad_gestures_library().value_or(true);
// TODO(b/246587538): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) and DualSense
// (ce6) touchpads, or at least load this setting from the IDC file.
- const InputDeviceIdentifier& identifier = contextPtr->getDeviceIdentifier();
+ const InputDeviceIdentifier identifier = contextPtr->getDeviceIdentifier();
const bool isSonyGamepadTouchpad = identifier.vendor == 0x054c &&
(identifier.product == 0x05c4 || identifier.product == 0x09cc ||
identifier.product == 0x0ce6);
diff --git a/services/inputflinger/reader/controller/PeripheralController.cpp b/services/inputflinger/reader/controller/PeripheralController.cpp
index cedbacb..a380b5e 100644
--- a/services/inputflinger/reader/controller/PeripheralController.cpp
+++ b/services/inputflinger/reader/controller/PeripheralController.cpp
@@ -152,7 +152,7 @@
return std::nullopt;
}
- return toArgb(brightness.value(), 0 /* red */, 0 /* green */, 0 /* blue */);
+ return toArgb(brightness.value(), /*red=*/0, /*green=*/0, /*blue=*/0);
}
std::optional<int32_t> PeripheralController::RgbLight::getLightColor() {
@@ -197,13 +197,12 @@
}
std::unordered_map<LightColor, int32_t> intensities = ret.value();
// Get red, green, blue colors
- int32_t color = toArgb(0 /* brightness */, intensities.at(LightColor::RED) /* red */,
- intensities.at(LightColor::GREEN) /* green */,
- intensities.at(LightColor::BLUE) /* blue */);
+ int32_t color = toArgb(/*brightness=*/0, intensities.at(LightColor::RED),
+ intensities.at(LightColor::GREEN), intensities.at(LightColor::BLUE));
// Get brightness
std::optional<int32_t> brightness = getRawLightBrightness(rawId);
if (brightness.has_value()) {
- return toArgb(brightness.value() /* A */, 0, 0, 0) | color;
+ return toArgb(/*brightness=*/brightness.value(), 0, 0, 0) | color;
}
return std::nullopt;
}
@@ -273,7 +272,7 @@
for (const auto& [lightId, light] : mLights) {
// Input device light doesn't support ordinal, always pass 1.
InputDeviceLightInfo lightInfo(light->name, light->id, light->type, light->capabilityFlags,
- 1 /* ordinal */);
+ /*ordinal=*/1);
deviceInfo->addLightInfo(lightInfo);
}
}
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
index 929bf18..d7f8b17 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
@@ -301,7 +301,7 @@
case EV_SYN:
switch (rawEvent->code) {
case SYN_REPORT:
- out += sync(rawEvent->when, rawEvent->readTime, false /*force*/);
+ out += sync(rawEvent->when, rawEvent->readTime, /*force=*/false);
break;
}
break;
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index d147d60..0361bc6 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -440,7 +440,7 @@
for (size_t i = 0; i < n; i++) {
out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when,
systemTime(SYSTEM_TIME_MONOTONIC), getDeviceId(), mSource,
- getDisplayId(), 0 /*policyFlags*/, AKEY_EVENT_ACTION_UP,
+ getDisplayId(), /*policyFlags=*/0, AKEY_EVENT_ACTION_UP,
AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED,
mKeyDowns[i].keyCode, mKeyDowns[i].scanCode, AMETA_NONE,
mKeyDowns[i].downTime));
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index 633efc6..33e72c7 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -202,10 +202,10 @@
slotCount = MAX_SLOTS;
}
mMultiTouchMotionAccumulator.configure(getDeviceContext(), slotCount,
- true /*usingSlotsProtocol*/);
+ /*usingSlotsProtocol=*/true);
} else {
mMultiTouchMotionAccumulator.configure(getDeviceContext(), MAX_POINTERS,
- false /*usingSlotsProtocol*/);
+ /*usingSlotsProtocol=*/false);
}
}
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
index d81022f..f797bc9 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
@@ -208,10 +208,10 @@
InputDeviceSensorInfo sensorInfo(identifier.name, std::to_string(identifier.vendor),
identifier.version, sensorType,
InputDeviceSensorAccuracy::ACCURACY_HIGH,
- axis.max /* maxRange */, axis.scale /* resolution */,
- 0.0f /* power */, 0 /* minDelay */,
- 0 /* fifoReservedEventCount */, 0 /* fifoMaxEventCount */,
- ftl::enum_string(sensorType), 0 /* maxDelay */, 0 /* flags */,
+ /*maxRange=*/axis.max, /*resolution=*/axis.scale,
+ /*power=*/0.0f, /*minDelay=*/0,
+ /*fifoReservedEventCount=*/0, /*fifoMaxEventCount=*/0,
+ ftl::enum_string(sensorType), /*maxDelay=*/0, /*flags=*/0,
getDeviceId());
std::string prefix = "sensor." + ftl::enum_string(sensorType);
@@ -277,7 +277,7 @@
Axis& axis = pair.second;
axis.currentValue = axis.newValue;
}
- out += sync(rawEvent->when, false /*force*/);
+ out += sync(rawEvent->when, /*force=*/false);
break;
}
break;
@@ -344,7 +344,7 @@
maxBatchReportLatency.count());
}
- if (!setSensorEnabled(sensorType, true /* enabled */)) {
+ if (!setSensorEnabled(sensorType, /*enabled=*/true)) {
return false;
}
@@ -367,7 +367,7 @@
ALOGD("Disable Sensor %s", ftl::enum_string(sensorType).c_str());
}
- if (!setSensorEnabled(sensorType, false /* enabled */)) {
+ if (!setSensorEnabled(sensorType, /*enabled=*/false)) {
return;
}
@@ -413,9 +413,9 @@
out.push_back(NotifySensorArgs(getContext()->getNextId(), when, getDeviceId(),
AINPUT_SOURCE_SENSOR, sensorType,
sensor.sensorInfo.accuracy,
- sensor.accuracy !=
- sensor.sensorInfo.accuracy /* accuracyChanged */,
- timestamp /* hwTimestamp */, values));
+ /*accuracyChanged=*/sensor.accuracy !=
+ sensor.sensorInfo.accuracy,
+ /*hwTimestamp=*/timestamp, values));
sensor.lastSampleTimeNs = timestamp;
sensor.accuracy = sensor.sensorInfo.accuracy;
}
diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
index c9101ca..c4564a4 100644
--- a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
@@ -59,7 +59,7 @@
std::list<NotifyArgs> out;
if (mUpdatedSwitchMask) {
uint32_t updatedSwitchValues = mSwitchValues & mUpdatedSwitchMask;
- out.push_back(NotifySwitchArgs(getContext()->getNextId(), when, 0 /*policyFlags*/,
+ out.push_back(NotifySwitchArgs(getContext()->getNextId(), when, /*policyFlags=*/0,
updatedSwitchValues, mUpdatedSwitchMask));
mUpdatedSwitchMask = 0;
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 0c57628..31fdac9 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -977,12 +977,18 @@
mOrientedRanges.clear();
}
- // Create pointer controller if needed, and keep it around if Pointer Capture is enabled to
- // preserve the cursor position.
- if (mDeviceMode == DeviceMode::POINTER ||
- (mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches) ||
- (mParameters.deviceType == Parameters::DeviceType::POINTER &&
- mConfig.pointerCaptureRequest.enable)) {
+ // Create and preserve the pointer controller in the following cases:
+ const bool isPointerControllerNeeded =
+ // - when the device is in pointer mode, to show the mouse cursor;
+ (mDeviceMode == DeviceMode::POINTER) ||
+ // - when pointer capture is enabled, to preserve the mouse cursor position;
+ (mParameters.deviceType == Parameters::DeviceType::POINTER &&
+ mConfig.pointerCaptureRequest.enable) ||
+ // - when we should be showing touches;
+ (mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches) ||
+ // - when we should be showing a pointer icon for direct styluses.
+ (mDeviceMode == DeviceMode::DIRECT && mConfig.stylusPointerIconEnabled && hasStylus());
+ if (isPointerControllerNeeded) {
if (mPointerController == nullptr) {
mPointerController = getContext()->getPointerController(getDeviceId());
}
@@ -1455,7 +1461,7 @@
next.rawPointerData.hoveringIdBits.value);
}
- out += processRawTouches(false /*timeout*/);
+ out += processRawTouches(/*timeout=*/false);
return out;
}
@@ -1749,11 +1755,11 @@
if (mPointerUsage == PointerUsage::GESTURES) {
// Since this is a synthetic event, we can consider its latency to be zero
const nsecs_t readTime = when;
- out += dispatchPointerGestures(when, readTime, 0 /*policyFlags*/, true /*isTimeout*/);
+ out += dispatchPointerGestures(when, readTime, /*policyFlags=*/0, /*isTimeout=*/true);
}
} else if (mDeviceMode == DeviceMode::DIRECT) {
if (mExternalStylusFusionTimeout <= when) {
- out += processRawTouches(true /*timeout*/);
+ out += processRawTouches(/*timeout=*/true);
} else if (mExternalStylusFusionTimeout != LLONG_MAX) {
getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout);
}
@@ -1772,7 +1778,7 @@
// - Only the button state, which is not reported through a specific pointer, has changed.
// Go ahead and dispatch now that we have fresh stylus data.
mExternalStylusDataPending = true;
- out += processRawTouches(false /*timeout*/);
+ out += processRawTouches(/*timeout=*/false);
}
return out;
}
@@ -2150,6 +2156,53 @@
return out;
}
+std::list<NotifyArgs> TouchInputMapper::dispatchGestureButtonRelease(nsecs_t when,
+ uint32_t policyFlags,
+ BitSet32 idBits,
+ nsecs_t readTime) {
+ std::list<NotifyArgs> out;
+ BitSet32 releasedButtons(mLastCookedState.buttonState & ~mCurrentCookedState.buttonState);
+ const int32_t metaState = getContext()->getGlobalMetaState();
+ int32_t buttonState = mLastCookedState.buttonState;
+
+ while (!releasedButtons.isEmpty()) {
+ int32_t actionButton = BitSet32::valueForBit(releasedButtons.clearFirstMarkedBit());
+ buttonState &= ~actionButton;
+ out.push_back(dispatchMotion(when, readTime, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0,
+ metaState, buttonState, 0,
+ mPointerGesture.lastGestureProperties,
+ mPointerGesture.lastGestureCoords,
+ mPointerGesture.lastGestureIdToIndex, idBits, -1,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerGesture.downTime, MotionClassification::NONE));
+ }
+ return out;
+}
+
+std::list<NotifyArgs> TouchInputMapper::dispatchGestureButtonPress(nsecs_t when,
+ uint32_t policyFlags,
+ BitSet32 idBits,
+ nsecs_t readTime) {
+ std::list<NotifyArgs> out;
+ BitSet32 pressedButtons(mCurrentCookedState.buttonState & ~mLastCookedState.buttonState);
+ const int32_t metaState = getContext()->getGlobalMetaState();
+ int32_t buttonState = mLastCookedState.buttonState;
+
+ while (!pressedButtons.isEmpty()) {
+ int32_t actionButton = BitSet32::valueForBit(pressedButtons.clearFirstMarkedBit());
+ buttonState |= actionButton;
+ out.push_back(dispatchMotion(when, readTime, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0, metaState,
+ buttonState, 0, mPointerGesture.currentGestureProperties,
+ mPointerGesture.currentGestureCoords,
+ mPointerGesture.currentGestureIdToIndex, idBits, -1,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerGesture.downTime, MotionClassification::NONE));
+ }
+ return out;
+}
+
const BitSet32& TouchInputMapper::findActiveIdBits(const CookedPointerData& cookedPointerData) {
if (!cookedPointerData.touchingIdBits.isEmpty()) {
return cookedPointerData.touchingIdBits;
@@ -2373,7 +2426,7 @@
switch (mPointerUsage) {
case PointerUsage::GESTURES:
- out += dispatchPointerGestures(when, readTime, policyFlags, false /*isTimeout*/);
+ out += dispatchPointerGestures(when, readTime, policyFlags, /*isTimeout=*/false);
break;
case PointerUsage::STYLUS:
out += dispatchPointerStylus(when, readTime, policyFlags);
@@ -2534,8 +2587,13 @@
dispatchedGestureIdBits.value & ~mPointerGesture.currentGestureIdBits.value;
}
while (!upGestureIdBits.isEmpty()) {
- uint32_t id = upGestureIdBits.clearFirstMarkedBit();
-
+ if (((mLastCookedState.buttonState & AMOTION_EVENT_BUTTON_PRIMARY) != 0 ||
+ (mLastCookedState.buttonState & AMOTION_EVENT_BUTTON_SECONDARY) != 0) &&
+ mPointerGesture.lastGestureMode == PointerGesture::Mode::BUTTON_CLICK_OR_DRAG) {
+ out += dispatchGestureButtonRelease(when, policyFlags, dispatchedGestureIdBits,
+ readTime);
+ }
+ const uint32_t id = upGestureIdBits.clearFirstMarkedBit();
out.push_back(dispatchMotion(when, readTime, policyFlags, mSource,
AMOTION_EVENT_ACTION_POINTER_UP, 0, flags, metaState,
buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
@@ -2580,6 +2638,12 @@
mPointerGesture.currentGestureIdToIndex,
dispatchedGestureIdBits, id, 0, 0,
mPointerGesture.downTime, classification));
+ if (((buttonState & AMOTION_EVENT_BUTTON_PRIMARY) != 0 ||
+ (buttonState & AMOTION_EVENT_BUTTON_SECONDARY) != 0) &&
+ mPointerGesture.currentGestureMode == PointerGesture::Mode::BUTTON_CLICK_OR_DRAG) {
+ out += dispatchGestureButtonPress(when, policyFlags, dispatchedGestureIdBits,
+ readTime);
+ }
}
}
@@ -3650,6 +3714,14 @@
return out;
}
+static bool isStylusEvent(uint32_t source, int32_t action, const PointerProperties* properties) {
+ if (!isFromSource(source, AINPUT_SOURCE_STYLUS)) {
+ return false;
+ }
+ const auto actionIndex = action >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+ return isStylusToolType(properties[actionIndex].toolType);
+}
+
NotifyMotionArgs TouchInputMapper::dispatchMotion(
nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action,
int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState,
@@ -3691,12 +3763,35 @@
ALOG_ASSERT(false);
}
}
+
+ const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
+ const bool showDirectStylusPointer = mConfig.stylusPointerIconEnabled &&
+ mDeviceMode == DeviceMode::DIRECT && isStylusEvent(source, action, pointerProperties) &&
+ mPointerController && displayId != ADISPLAY_ID_NONE &&
+ displayId == mPointerController->getDisplayId();
+ if (showDirectStylusPointer) {
+ switch (action & AMOTION_EVENT_ACTION_MASK) {
+ case AMOTION_EVENT_ACTION_HOVER_ENTER:
+ case AMOTION_EVENT_ACTION_HOVER_MOVE:
+ mPointerController->setPresentation(
+ PointerControllerInterface::Presentation::STYLUS_HOVER);
+ mPointerController
+ ->setPosition(mCurrentCookedState.cookedPointerData.pointerCoords[0].getX(),
+ mCurrentCookedState.cookedPointerData.pointerCoords[0]
+ .getY());
+ mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
+ break;
+ case AMOTION_EVENT_ACTION_HOVER_EXIT:
+ mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
+ break;
+ }
+ }
+
float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
if (mDeviceMode == DeviceMode::POINTER) {
mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
}
- const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
const int32_t deviceId = getDeviceId();
std::vector<TouchVideoFrame> frames = getDeviceContext().getVideoFrames();
std::for_each(frames.begin(), frames.end(),
@@ -3710,8 +3805,8 @@
std::list<NotifyArgs> TouchInputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) {
std::list<NotifyArgs> out;
- out += abortPointerUsage(when, readTime, 0 /*policyFlags*/);
- out += abortTouches(when, readTime, 0 /* policyFlags*/);
+ out += abortPointerUsage(when, readTime, /*policyFlags=*/0);
+ out += abortTouches(when, readTime, /* policyFlags=*/0);
return out;
}
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 87deb39..7b464ef 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -735,6 +735,14 @@
uint32_t policyFlags);
[[nodiscard]] std::list<NotifyArgs> dispatchButtonPress(nsecs_t when, nsecs_t readTime,
uint32_t policyFlags);
+ [[nodiscard]] std::list<NotifyArgs> dispatchGestureButtonPress(nsecs_t when,
+ uint32_t policyFlags,
+ BitSet32 idBits,
+ nsecs_t readTime);
+ [[nodiscard]] std::list<NotifyArgs> dispatchGestureButtonRelease(nsecs_t when,
+ uint32_t policyFlags,
+ BitSet32 idBits,
+ nsecs_t readTime);
const BitSet32& findActiveIdBits(const CookedPointerData& cookedPointerData);
void cookPointerData();
[[nodiscard]] std::list<NotifyArgs> abortTouches(nsecs_t when, nsecs_t readTime,
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index b6313a1..9f32311 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -146,6 +146,18 @@
}
mGestureConverter.setOrientation(orientation);
}
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_TOUCHPAD_SETTINGS)) {
+ // TODO(b/265798483): load an Android-specific acceleration curve instead of mapping to one
+ // of five ChromeOS curves.
+ const int pointerSensitivity = (config->touchpadPointerSpeed + 7) / 3 + 1;
+ mPropertyProvider.getProperty("Pointer Sensitivity").setIntValues({pointerSensitivity});
+ mPropertyProvider.getProperty("Invert Scrolling")
+ .setBoolValues({config->touchpadNaturalScrollingEnabled});
+ mPropertyProvider.getProperty("Tap Enable")
+ .setBoolValues({config->touchpadTapToClickEnabled});
+ mPropertyProvider.getProperty("Button Right Click Zone Enable")
+ .setBoolValues({config->touchpadRightClickZoneEnabled});
+ }
return {};
}
diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
index 6601702..6b84f32 100644
--- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
@@ -115,7 +115,7 @@
int32_t keyCode, metaState;
uint32_t flags;
if (mDeviceContext.mapKey(scanCode, mHidUsageAccumulator.consumeCurrentHidUsage(),
- 0 /*metaState*/, &keyCode, &metaState, &flags) != OK) {
+ /*metaState=*/0, &keyCode, &metaState, &flags) != OK) {
return;
}
switch (keyCode) {
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 561b1f8..d636d44 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -219,11 +219,11 @@
float deltaY = gesture.details.scroll.dy;
rotateDelta(mOrientation, &deltaX, &deltaY);
- coords.setAxisValue(AMOTION_EVENT_AXIS_X, coords.getAxisValue(AMOTION_EVENT_AXIS_X) - deltaX);
- coords.setAxisValue(AMOTION_EVENT_AXIS_Y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y) - deltaY);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_X, coords.getAxisValue(AMOTION_EVENT_AXIS_X) + deltaX);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_Y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y) + deltaY);
// TODO(b/262876643): set AXIS_GESTURE_{X,Y}_OFFSET.
- coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, gesture.details.scroll.dx);
- coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, gesture.details.scroll.dy);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, -gesture.details.scroll.dx);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, -gesture.details.scroll.dy);
out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index bb8a30e..30c1719 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -205,6 +205,10 @@
mConfig.stylusButtonMotionEventsEnabled = enabled;
}
+void FakeInputReaderPolicy::setStylusPointerIconEnabled(bool enabled) {
+ mConfig.stylusPointerIconEnabled = enabled;
+}
+
void FakeInputReaderPolicy::getReaderConfiguration(InputReaderConfiguration* outConfig) {
*outConfig = mConfig;
}
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index 9ec3217..28ac505 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -76,6 +76,7 @@
float getPointerGestureZoomSpeedRatio();
void setVelocityControlParams(const VelocityControlParameters& params);
void setStylusButtonMotionEventsEnabled(bool enabled);
+ void setStylusPointerIconEnabled(bool enabled);
private:
void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index ab7879f..28dad95 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -65,6 +65,10 @@
ASSERT_NEAR(y, actualY, 1);
}
+bool FakePointerController::isPointerShown() {
+ return mIsPointerShown;
+}
+
bool FakePointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
float* outMaxY) const {
*outMinX = mMinX;
@@ -83,6 +87,13 @@
if (mY > mMaxY) mY = mMaxY;
}
+void FakePointerController::fade(Transition) {
+ mIsPointerShown = false;
+}
+void FakePointerController::unfade(Transition) {
+ mIsPointerShown = true;
+}
+
void FakePointerController::setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,
int32_t displayId) {
std::vector<int32_t> newSpots;
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index d10cbcd..dd56e65 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -39,12 +39,13 @@
void setDisplayViewport(const DisplayViewport& viewport) override;
void assertPosition(float x, float y);
+ bool isPointerShown();
private:
bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override;
void move(float deltaX, float deltaY) override;
- void fade(Transition) override {}
- void unfade(Transition) override {}
+ void fade(Transition) override;
+ void unfade(Transition) override;
void setPresentation(Presentation) override {}
void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,
int32_t displayId) override;
@@ -55,6 +56,7 @@
float mX{0}, mY{0};
int32_t mButtonState{0};
int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+ bool mIsPointerShown{false};
std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay;
};
diff --git a/services/inputflinger/tests/FocusResolver_test.cpp b/services/inputflinger/tests/FocusResolver_test.cpp
index 5d5cf9c..ccdb37a 100644
--- a/services/inputflinger/tests/FocusResolver_test.cpp
+++ b/services/inputflinger/tests/FocusResolver_test.cpp
@@ -55,11 +55,11 @@
sp<IBinder> unfocusableWindowToken = sp<BBinder>::make();
std::vector<sp<WindowInfoHandle>> windows;
windows.push_back(sp<FakeWindowHandle>::make("Focusable", focusableWindowToken,
- true /* focusable */, true /* visible */));
+ /*focusable=*/true, /*visible=*/true));
windows.push_back(sp<FakeWindowHandle>::make("Invisible", invisibleWindowToken,
- true /* focusable */, false /* visible */));
+ /*focusable=*/true, /*visible=*/false));
windows.push_back(sp<FakeWindowHandle>::make("unfocusable", unfocusableWindowToken,
- false /* focusable */, true /* visible */));
+ /*focusable=*/false, /*visible=*/true));
// focusable window can get focused
FocusRequest request;
@@ -88,7 +88,7 @@
sp<IBinder> focusableWindowToken = sp<BBinder>::make();
std::vector<sp<WindowInfoHandle>> windows;
windows.push_back(sp<FakeWindowHandle>::make("Focusable", focusableWindowToken,
- true /* focusable */, true /* visible */));
+ /*focusable=*/true, /*visible=*/true));
FocusRequest request;
request.displayId = 42;
@@ -114,19 +114,19 @@
sp<IBinder> unfocusableWindowToken = sp<BBinder>::make();
std::vector<sp<WindowInfoHandle>> windows;
windows.push_back(sp<FakeWindowHandle>::make("Mirror1", focusableWindowToken,
- true /* focusable */, true /* visible */));
+ /*focusable=*/true, /*visible=*/true));
windows.push_back(sp<FakeWindowHandle>::make("Mirror1", focusableWindowToken,
- true /* focusable */, true /* visible */));
+ /*focusable=*/true, /*visible=*/true));
windows.push_back(sp<FakeWindowHandle>::make("Mirror2Visible", invisibleWindowToken,
- true /* focusable */, true /* visible */));
+ /*focusable=*/true, /*visible=*/true));
windows.push_back(sp<FakeWindowHandle>::make("Mirror2Invisible", invisibleWindowToken,
- true /* focusable */, false /* visible */));
+ /*focusable=*/true, /*visible=*/false));
windows.push_back(sp<FakeWindowHandle>::make("Mirror3Focusable", unfocusableWindowToken,
- true /* focusable */, true /* visible */));
+ /*focusable=*/true, /*visible=*/true));
windows.push_back(sp<FakeWindowHandle>::make("Mirror3Unfocusable", unfocusableWindowToken,
- false /* focusable */, true /* visible */));
+ /*focusable=*/false, /*visible=*/true));
// mirrored window can get focused
FocusRequest request;
@@ -152,8 +152,8 @@
sp<IBinder> focusableWindowToken = sp<BBinder>::make();
std::vector<sp<WindowInfoHandle>> windows;
sp<FakeWindowHandle> window =
- sp<FakeWindowHandle>::make("Focusable", focusableWindowToken, true /* focusable */,
- true /* visible */);
+ sp<FakeWindowHandle>::make("Focusable", focusableWindowToken, /*focusable=*/true,
+ /*visible=*/true);
windows.push_back(window);
// focusable window can get focused
@@ -176,8 +176,8 @@
std::vector<sp<WindowInfoHandle>> windows;
sp<FakeWindowHandle> invisibleWindow =
- sp<FakeWindowHandle>::make("Invisible", invisibleWindowToken, true /* focusable */,
- false /* visible */);
+ sp<FakeWindowHandle>::make("Invisible", invisibleWindowToken, /*focusable=*/true,
+ /*visible=*/false);
windows.push_back(invisibleWindow);
// invisible window cannot get focused
@@ -200,8 +200,8 @@
std::vector<sp<WindowInfoHandle>> windows;
sp<FakeWindowHandle> window =
- sp<FakeWindowHandle>::make("Test Window", windowToken, false /* focusable */,
- true /* visible */);
+ sp<FakeWindowHandle>::make("Test Window", windowToken, /*focusable=*/false,
+ /*visible=*/true);
windows.push_back(window);
// non-focusable window cannot get focused
@@ -242,13 +242,13 @@
std::vector<sp<WindowInfoHandle>> windows;
sp<FakeWindowHandle> hostWindow =
- sp<FakeWindowHandle>::make("Host Window", hostWindowToken, true /* focusable */,
- true /* visible */);
+ sp<FakeWindowHandle>::make("Host Window", hostWindowToken, /*focusable=*/true,
+ /*visible=*/true);
windows.push_back(hostWindow);
sp<IBinder> embeddedWindowToken = sp<BBinder>::make();
sp<FakeWindowHandle> embeddedWindow =
- sp<FakeWindowHandle>::make("Embedded Window", embeddedWindowToken, true /* focusable */,
- true /* visible */);
+ sp<FakeWindowHandle>::make("Embedded Window", embeddedWindowToken, /*focusable=*/true,
+ /*visible=*/true);
windows.push_back(embeddedWindow);
FocusRequest request;
@@ -293,8 +293,8 @@
std::vector<sp<WindowInfoHandle>> windows;
sp<FakeWindowHandle> window =
- sp<FakeWindowHandle>::make("Test Window", windowToken, true /* focusable */,
- true /* visible */);
+ sp<FakeWindowHandle>::make("Test Window", windowToken, /*focusable=*/true,
+ /*visible=*/true);
windows.push_back(window);
FocusRequest request;
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index 36a39bb..9c624ba 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -244,7 +244,7 @@
InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
- Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 10);
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
ASSERT_EQ(2u, args.size());
@@ -261,7 +261,7 @@
WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
- Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 5);
+ Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
ASSERT_EQ(1u, args.size());
ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
@@ -289,7 +289,7 @@
GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
converter.setOrientation(ui::ROTATION_90);
- Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 10);
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
ASSERT_EQ(2u, args.size());
@@ -306,7 +306,7 @@
WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
- Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 5);
+ Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
ASSERT_EQ(1u, args.size());
ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
@@ -332,10 +332,10 @@
InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
- Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 10);
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
- Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 5);
+ Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 3abe43a..e71cdce 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -52,12 +52,14 @@
// An arbitrary device id.
static constexpr int32_t DEVICE_ID = 1;
+static constexpr int32_t SECOND_DEVICE_ID = 2;
// An arbitrary display id.
static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
static constexpr int32_t SECOND_DISPLAY_ID = 1;
static constexpr int32_t ACTION_OUTSIDE = AMOTION_EVENT_ACTION_OUTSIDE;
+static constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL;
static constexpr int32_t POINTER_1_DOWN =
AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
static constexpr int32_t POINTER_2_DOWN =
@@ -139,18 +141,26 @@
return arg.getDownTime() == downTime;
}
+MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") {
+ return arg.getDisplayId() == displayId;
+}
+
MATCHER_P(WithSource, source, "InputEvent with specified source") {
*result_listener << "expected source " << inputEventSourceToString(source) << ", but got "
<< inputEventSourceToString(arg.getSource());
return arg.getSource() == source;
}
+MATCHER_P(WithFlags, flags, "InputEvent with specified flags") {
+ return arg.getFlags() == flags;
+}
+
MATCHER_P2(WithCoords, x, y, "MotionEvent with specified coordinates") {
if (arg.getPointerCount() != 1) {
*result_listener << "Expected 1 pointer, got " << arg.getPointerCount();
return false;
}
- return arg.getX(0 /*pointerIndex*/) == x && arg.getY(0 /*pointerIndex*/) == y;
+ return arg.getX(/*pointerIndex=*/0) == x && arg.getY(/*pointerIndex=*/0) == y;
}
MATCHER_P(WithPointers, pointers, "MotionEvent with specified pointers") {
@@ -536,7 +546,7 @@
/** We simply reconstruct NotifySwitchArgs in policy because InputDispatcher is
* essentially a passthrough for notifySwitch.
*/
- mLastNotifySwitch = NotifySwitchArgs(1 /*id*/, when, policyFlags, switchValues, switchMask);
+ mLastNotifySwitch = NotifySwitchArgs(/*id=*/1, when, policyFlags, switchValues, switchMask);
}
void pokeUserActivity(nsecs_t, int32_t, int32_t) override {}
@@ -625,7 +635,7 @@
/*action*/ -1, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME,
ARBITRARY_TIME);
ASSERT_EQ(InputEventInjectionResult::FAILED,
- mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+ mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
0ms, 0))
<< "Should reject key events with undefined action.";
@@ -634,7 +644,7 @@
INVALID_HMAC, AKEY_EVENT_ACTION_MULTIPLE, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0,
ARBITRARY_TIME, ARBITRARY_TIME);
ASSERT_EQ(InputEventInjectionResult::FAILED,
- mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+ mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
0ms, 0))
<< "Should reject key events with ACTION_MULTIPLE.";
}
@@ -664,7 +674,7 @@
ARBITRARY_TIME,
/*pointerCount*/ 1, pointerProperties, pointerCoords);
ASSERT_EQ(InputEventInjectionResult::FAILED,
- mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+ mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
0ms, 0))
<< "Should reject motion events with undefined action.";
@@ -676,7 +686,7 @@
ARBITRARY_TIME,
/*pointerCount*/ 1, pointerProperties, pointerCoords);
ASSERT_EQ(InputEventInjectionResult::FAILED,
- mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+ mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
0ms, 0))
<< "Should reject motion events with pointer down index too large.";
@@ -688,7 +698,7 @@
identityTransform, ARBITRARY_TIME, ARBITRARY_TIME,
/*pointerCount*/ 1, pointerProperties, pointerCoords);
ASSERT_EQ(InputEventInjectionResult::FAILED,
- mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+ mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
0ms, 0))
<< "Should reject motion events with pointer down index too small.";
@@ -700,7 +710,7 @@
ARBITRARY_TIME,
/*pointerCount*/ 1, pointerProperties, pointerCoords);
ASSERT_EQ(InputEventInjectionResult::FAILED,
- mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+ mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
0ms, 0))
<< "Should reject motion events with pointer up index too large.";
@@ -712,7 +722,7 @@
identityTransform, ARBITRARY_TIME, ARBITRARY_TIME,
/*pointerCount*/ 1, pointerProperties, pointerCoords);
ASSERT_EQ(InputEventInjectionResult::FAILED,
- mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+ mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
0ms, 0))
<< "Should reject motion events with pointer up index too small.";
@@ -724,7 +734,7 @@
ARBITRARY_TIME,
/*pointerCount*/ 0, pointerProperties, pointerCoords);
ASSERT_EQ(InputEventInjectionResult::FAILED,
- mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+ mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
0ms, 0))
<< "Should reject motion events with 0 pointers.";
@@ -735,7 +745,7 @@
ARBITRARY_TIME,
/*pointerCount*/ MAX_POINTERS + 1, pointerProperties, pointerCoords);
ASSERT_EQ(InputEventInjectionResult::FAILED,
- mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+ mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
0ms, 0))
<< "Should reject motion events with more than MAX_POINTERS pointers.";
@@ -748,7 +758,7 @@
ARBITRARY_TIME,
/*pointerCount*/ 1, pointerProperties, pointerCoords);
ASSERT_EQ(InputEventInjectionResult::FAILED,
- mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+ mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
0ms, 0))
<< "Should reject motion events with pointer ids less than 0.";
@@ -760,7 +770,7 @@
ARBITRARY_TIME,
/*pointerCount*/ 1, pointerProperties, pointerCoords);
ASSERT_EQ(InputEventInjectionResult::FAILED,
- mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+ mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
0ms, 0))
<< "Should reject motion events with pointer ids greater than MAX_POINTER_ID.";
@@ -774,7 +784,7 @@
ARBITRARY_TIME,
/*pointerCount*/ 2, pointerProperties, pointerCoords);
ASSERT_EQ(InputEventInjectionResult::FAILED,
- mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+ mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
0ms, 0))
<< "Should reject motion events with duplicate pointer ids.";
}
@@ -783,7 +793,7 @@
TEST_F(InputDispatcherTest, NotifyConfigurationChanged_CallsPolicy) {
constexpr nsecs_t eventTime = 20;
- NotifyConfigurationChangedArgs args(10 /*id*/, eventTime);
+ NotifyConfigurationChangedArgs args(/*id=*/10, eventTime);
mDispatcher->notifyConfigurationChanged(&args);
ASSERT_TRUE(mDispatcher->waitForIdle());
@@ -791,8 +801,8 @@
}
TEST_F(InputDispatcherTest, NotifySwitch_CallsPolicy) {
- NotifySwitchArgs args(10 /*id*/, 20 /*eventTime*/, 0 /*policyFlags*/, 1 /*switchValues*/,
- 2 /*switchMask*/);
+ NotifySwitchArgs args(/*id=*/10, /*eventTime=*/20, /*policyFlags=*/0, /*switchValues=*/1,
+ /*switchMask=*/2);
mDispatcher->notifySwitch(&args);
// InputDispatcher adds POLICY_FLAG_TRUSTED because the event went through InputListener
@@ -853,7 +863,7 @@
std::chrono::time_point start = std::chrono::steady_clock::now();
status_t status = WOULD_BLOCK;
while (status == WOULD_BLOCK) {
- status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq,
+ status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
&event);
std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
if (elapsed > 100ms) {
@@ -944,6 +954,28 @@
}
}
+ MotionEvent* consumeMotion() {
+ InputEvent* event = consume();
+
+ if (event == nullptr) {
+ ADD_FAILURE() << mName << ": expected a MotionEvent, but didn't get one.";
+ return nullptr;
+ }
+
+ if (event->getType() != AINPUT_EVENT_TYPE_MOTION) {
+ ADD_FAILURE() << mName << " expected a MotionEvent, got "
+ << inputEventTypeToString(event->getType()) << " event";
+ return nullptr;
+ }
+ return static_cast<MotionEvent*>(event);
+ }
+
+ void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
+ MotionEvent* motionEvent = consumeMotion();
+ ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event, but expected " << matcher;
+ ASSERT_THAT(*motionEvent, matcher);
+ }
+
void consumeFocusEvent(bool hasFocus, bool inTouchMode) {
InputEvent* event = consume();
ASSERT_NE(nullptr, event) << mName.c_str()
@@ -1196,14 +1228,14 @@
void consumeMotionCancel(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
int32_t expectedFlags = 0) {
- consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, expectedDisplayId,
- expectedFlags);
+ consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(expectedDisplayId),
+ WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED)));
}
void consumeMotionMove(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
int32_t expectedFlags = 0) {
- consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE, expectedDisplayId,
- expectedFlags);
+ consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithDisplayId(expectedDisplayId), WithFlags(expectedFlags)));
}
void consumeMotionDown(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
@@ -1375,8 +1407,8 @@
// Define a valid key down event.
event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, displayId,
- INVALID_HMAC, action, /* flags */ 0, AKEYCODE_A, KEY_A, AMETA_NONE,
- repeatCount, currentTime, currentTime);
+ INVALID_HMAC, action, /*flags=*/0, AKEYCODE_A, KEY_A, AMETA_NONE, repeatCount,
+ currentTime, currentTime);
if (!allowKeyRepeat) {
policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT;
@@ -1387,7 +1419,7 @@
static InputEventInjectionResult injectKeyDown(const std::unique_ptr<InputDispatcher>& dispatcher,
int32_t displayId = ADISPLAY_ID_NONE) {
- return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /* repeatCount */ 0, displayId);
+ return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, displayId);
}
// Inject a down event that has key repeat disabled. This allows InputDispatcher to idle without
@@ -1395,14 +1427,14 @@
// has to be woken up to process the repeating key.
static InputEventInjectionResult injectKeyDownNoRepeat(
const std::unique_ptr<InputDispatcher>& dispatcher, int32_t displayId = ADISPLAY_ID_NONE) {
- return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /* repeatCount */ 0, displayId,
+ return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, displayId,
InputEventInjectionSync::WAIT_FOR_RESULT, INJECT_EVENT_TIMEOUT,
- /* allowKeyRepeat */ false);
+ /*allowKeyRepeat=*/false);
}
static InputEventInjectionResult injectKeyUp(const std::unique_ptr<InputDispatcher>& dispatcher,
int32_t displayId = ADISPLAY_ID_NONE) {
- return injectKey(dispatcher, AKEY_EVENT_ACTION_UP, /* repeatCount */ 0, displayId);
+ return injectKey(dispatcher, AKEY_EVENT_ACTION_UP, /*repeatCount=*/0, displayId);
}
class PointerBuilder {
@@ -1438,6 +1470,17 @@
mAction = action;
mSource = source;
mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ mDownTime = mEventTime;
+ }
+
+ MotionEventBuilder& deviceId(int32_t deviceId) {
+ mDeviceId = deviceId;
+ return *this;
+ }
+
+ MotionEventBuilder& downTime(nsecs_t downTime) {
+ mDownTime = downTime;
+ return *this;
}
MotionEventBuilder& eventTime(nsecs_t eventTime) {
@@ -1498,11 +1541,11 @@
MotionEvent event;
ui::Transform identityTransform;
- event.initialize(InputEvent::nextId(), DEVICE_ID, mSource, mDisplayId, INVALID_HMAC,
+ event.initialize(InputEvent::nextId(), mDeviceId, mSource, mDisplayId, INVALID_HMAC,
mAction, mActionButton, mFlags, /* edgeFlags */ 0, AMETA_NONE,
mButtonState, MotionClassification::NONE, identityTransform,
/* xPrecision */ 0, /* yPrecision */ 0, mRawXCursorPosition,
- mRawYCursorPosition, identityTransform, mEventTime, mEventTime,
+ mRawYCursorPosition, identityTransform, mDownTime, mEventTime,
mPointers.size(), pointerProperties.data(), pointerCoords.data());
return event;
@@ -1510,7 +1553,9 @@
private:
int32_t mAction;
+ int32_t mDeviceId = DEVICE_ID;
int32_t mSource;
+ nsecs_t mDownTime;
nsecs_t mEventTime;
int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
int32_t mActionButton{0};
@@ -1522,6 +1567,113 @@
std::vector<PointerBuilder> mPointers;
};
+class MotionArgsBuilder {
+public:
+ MotionArgsBuilder(int32_t action, int32_t source) {
+ mAction = action;
+ mSource = source;
+ mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ mDownTime = mEventTime;
+ }
+
+ MotionArgsBuilder& deviceId(int32_t deviceId) {
+ mDeviceId = deviceId;
+ return *this;
+ }
+
+ MotionArgsBuilder& downTime(nsecs_t downTime) {
+ mDownTime = downTime;
+ return *this;
+ }
+
+ MotionArgsBuilder& eventTime(nsecs_t eventTime) {
+ mEventTime = eventTime;
+ return *this;
+ }
+
+ MotionArgsBuilder& displayId(int32_t displayId) {
+ mDisplayId = displayId;
+ return *this;
+ }
+
+ MotionArgsBuilder& policyFlags(int32_t policyFlags) {
+ mPolicyFlags = policyFlags;
+ return *this;
+ }
+
+ MotionArgsBuilder& actionButton(int32_t actionButton) {
+ mActionButton = actionButton;
+ return *this;
+ }
+
+ MotionArgsBuilder& buttonState(int32_t buttonState) {
+ mButtonState = buttonState;
+ return *this;
+ }
+
+ MotionArgsBuilder& rawXCursorPosition(float rawXCursorPosition) {
+ mRawXCursorPosition = rawXCursorPosition;
+ return *this;
+ }
+
+ MotionArgsBuilder& rawYCursorPosition(float rawYCursorPosition) {
+ mRawYCursorPosition = rawYCursorPosition;
+ return *this;
+ }
+
+ MotionArgsBuilder& pointer(PointerBuilder pointer) {
+ mPointers.push_back(pointer);
+ return *this;
+ }
+
+ MotionArgsBuilder& addFlag(uint32_t flags) {
+ mFlags |= flags;
+ return *this;
+ }
+
+ NotifyMotionArgs build() {
+ std::vector<PointerProperties> pointerProperties;
+ std::vector<PointerCoords> pointerCoords;
+ for (const PointerBuilder& pointer : mPointers) {
+ pointerProperties.push_back(pointer.buildProperties());
+ pointerCoords.push_back(pointer.buildCoords());
+ }
+
+ // Set mouse cursor position for the most common cases to avoid boilerplate.
+ if (mSource == AINPUT_SOURCE_MOUSE &&
+ !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) &&
+ mPointers.size() == 1) {
+ mRawXCursorPosition = pointerCoords[0].getX();
+ mRawYCursorPosition = pointerCoords[0].getY();
+ }
+
+ NotifyMotionArgs args(InputEvent::nextId(), mEventTime, /*readTime=*/mEventTime, mDeviceId,
+ mSource, mDisplayId, mPolicyFlags, mAction, mActionButton, mFlags,
+ AMETA_NONE, mButtonState, MotionClassification::NONE, /*edgeFlags=*/0,
+ mPointers.size(), pointerProperties.data(), pointerCoords.data(),
+ /*xPrecision=*/0, /*yPrecision=*/0, mRawXCursorPosition,
+ mRawYCursorPosition, mDownTime, /*videoFrames=*/{});
+
+ return args;
+ }
+
+private:
+ int32_t mAction;
+ int32_t mDeviceId = DEVICE_ID;
+ int32_t mSource;
+ nsecs_t mDownTime;
+ nsecs_t mEventTime;
+ int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+ int32_t mPolicyFlags = DEFAULT_POLICY_FLAGS;
+ int32_t mActionButton{0};
+ int32_t mButtonState{0};
+ int32_t mFlags{0};
+ float mRawXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
+ float mRawYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
+
+ std::vector<PointerBuilder> mPointers;
+};
+
static InputEventInjectionResult injectMotionEvent(
const std::unique_ptr<InputDispatcher>& dispatcher, const MotionEvent& event,
std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
@@ -1545,7 +1697,7 @@
.eventTime(eventTime)
.rawXCursorPosition(cursorPosition.x)
.rawYCursorPosition(cursorPosition.y)
- .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER)
.x(position.x)
.y(position.y))
.build();
@@ -1570,7 +1722,7 @@
static NotifyKeyArgs generateKeyArgs(int32_t action, int32_t displayId = ADISPLAY_ID_NONE) {
nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
// Define a valid key event.
- NotifyKeyArgs args(/* id */ 0, currentTime, 0 /*readTime*/, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+ NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
displayId, POLICY_FLAG_PASS_TO_USER, action, /* flags */ 0, AKEYCODE_A,
KEY_A, AMETA_NONE, currentTime);
@@ -1599,7 +1751,7 @@
nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
// Define a valid motion event.
- NotifyMotionArgs args(/* id */ 0, currentTime, 0 /*readTime*/, DEVICE_ID, source, displayId,
+ NotifyMotionArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, source, displayId,
POLICY_FLAG_PASS_TO_USER, action, /* actionButton */ 0, /* flags */ 0,
AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE,
AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties,
@@ -1620,7 +1772,7 @@
static NotifyPointerCaptureChangedArgs generatePointerCaptureChangedArgs(
const PointerCaptureRequest& request) {
- return NotifyPointerCaptureChangedArgs(/* id */ 0, systemTime(SYSTEM_TIME_MONOTONIC), request);
+ return NotifyPointerCaptureChangedArgs(/*id=*/0, systemTime(SYSTEM_TIME_MONOTONIC), request);
}
/**
@@ -1850,32 +2002,24 @@
const MotionEvent secondFingerDownEvent =
MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
.eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
- .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
- .x(100)
- .y(100))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
- .x(150)
- .y(150))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+ .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150))
.build();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
InputEventInjectionSync::WAIT_FOR_RESULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- foregroundWindow->consumeMotionPointerDown(1 /* pointerIndex */);
- wallpaperWindow->consumeMotionPointerDown(1 /* pointerIndex */, ADISPLAY_ID_DEFAULT,
+ foregroundWindow->consumeMotionPointerDown(/*pointerIndex=*/1);
+ wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT,
expectedWallpaperFlags);
const MotionEvent secondFingerUpEvent =
MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
.displayId(ADISPLAY_ID_DEFAULT)
.eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
- .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
- .x(100)
- .y(100))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
- .x(150)
- .y(150))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+ .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150))
.build();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
@@ -1934,12 +2078,8 @@
const MotionEvent secondFingerDownEvent =
MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
.eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
- .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
- .x(100)
- .y(100))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
- .x(300)
- .y(100))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+ .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100))
.build();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -1949,7 +2089,7 @@
leftWindow->consumeMotionMove();
// Since the touch is split, right window gets ACTION_DOWN
rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
- wallpaperWindow->consumeMotionPointerDown(1 /* pointerIndex */, ADISPLAY_ID_DEFAULT,
+ wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT,
expectedWallpaperFlags);
// Now, leftWindow, which received the first finger, disappears.
@@ -1963,12 +2103,8 @@
const MotionEvent secondFingerMoveEvent =
MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
.eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
- .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
- .x(100)
- .y(100))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
- .x(310)
- .y(110))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+ .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(310).y(110))
.build();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT,
@@ -2026,6 +2162,385 @@
}
/**
+ * The policy typically sets POLICY_FLAG_PASS_TO_USER to the events. But when the display is not
+ * interactive, it might stop sending this flag.
+ * In this test, we check that if the policy stops sending this flag mid-gesture, we still ensure
+ * to have a consistent input stream.
+ *
+ * Test procedure:
+ * DOWN -> POINTER_DOWN -> (stop sending POLICY_FLAG_PASS_TO_USER) -> CANCEL.
+ * DOWN (new gesture).
+ *
+ * In the bad implementation, we could potentially drop the CANCEL event, and get an inconsistent
+ * state in the dispatcher. This would cause the final DOWN event to not be delivered to the app.
+ *
+ * We technically just need a single window here, but we are using two windows (spy on top and a
+ * regular window below) to emulate the actual situation where it happens on the device.
+ */
+TEST_F(InputDispatcherTest, TwoPointerCancelInconsistentPolicy) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> spyWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindow->setFrame(Rect(0, 0, 200, 200));
+ spyWindow->setTrustedOverlay(true);
+ spyWindow->setSpy(true);
+
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}});
+ const int32_t touchDeviceId = 4;
+ NotifyMotionArgs args;
+
+ // Two pointers down
+ mDispatcher->notifyMotion(&(
+ args = MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .policyFlags(DEFAULT_POLICY_FLAGS)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+ .build()));
+
+ mDispatcher->notifyMotion(&(
+ args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .policyFlags(DEFAULT_POLICY_FLAGS)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+ .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(120).y(120))
+ .build()));
+ spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+ spyWindow->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
+ window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+ window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
+
+ // Cancel the current gesture. Send the cancel without the default policy flags.
+ mDispatcher->notifyMotion(&(
+ args = MotionArgsBuilder(AMOTION_EVENT_ACTION_CANCEL, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .policyFlags(0)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+ .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(120).y(120))
+ .build()));
+ spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL));
+ window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL));
+
+ // We don't need to reset the device to reproduce the issue, but the reset event typically
+ // follows, so we keep it here to model the actual listener behaviour more closely.
+ NotifyDeviceResetArgs resetArgs;
+ resetArgs.id = 1; // arbitrary id
+ resetArgs.eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ resetArgs.deviceId = touchDeviceId;
+ mDispatcher->notifyDeviceReset(&resetArgs);
+
+ // Start new gesture
+ mDispatcher->notifyMotion(&(
+ args = MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .policyFlags(DEFAULT_POLICY_FLAGS)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+ .build()));
+ spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+ window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+ // No more events
+ spyWindow->assertNoEvents();
+ window->assertNoEvents();
+}
+
+/**
+ * Two windows: a window on the left and a window on the right.
+ * Mouse is hovered from the right window into the left window.
+ * Next, we tap on the left window, where the cursor was last seen.
+ * The second tap is done onto the right window.
+ * The mouse and tap are from two different devices.
+ * We technically don't need to set the downtime / eventtime for these events, but setting these
+ * explicitly helps during debugging.
+ * This test reproduces a crash where there is a mismatch between the downTime and eventTime.
+ * In the buggy implementation, a tap on the right window would cause a crash.
+ */
+TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow}}});
+ // All times need to start at the current time, otherwise the dispatcher will drop the events as
+ // stale.
+ const nsecs_t baseTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ const int32_t mouseDeviceId = 6;
+ const int32_t touchDeviceId = 4;
+ // Move the cursor from right
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+ AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .downTime(baseTime + 10)
+ .eventTime(baseTime + 20)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+ .x(300)
+ .y(100))
+ .build()));
+ rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+ // .. to the left window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+ AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .downTime(baseTime + 10)
+ .eventTime(baseTime + 30)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+ .x(110)
+ .y(100))
+ .build()));
+ rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+ // Now tap the left window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .downTime(baseTime + 40)
+ .eventTime(baseTime + 40)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(100)
+ .y(100))
+ .build()));
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+ // release tap
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .downTime(baseTime + 40)
+ .eventTime(baseTime + 50)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(100)
+ .y(100))
+ .build()));
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
+
+ // Tap the window on the right
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .downTime(baseTime + 60)
+ .eventTime(baseTime + 60)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(300)
+ .y(100))
+ .build()));
+ rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+ // release tap
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .downTime(baseTime + 60)
+ .eventTime(baseTime + 70)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(300)
+ .y(100))
+ .build()));
+ rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
+
+ // No more events
+ leftWindow->assertNoEvents();
+ rightWindow->assertNoEvents();
+}
+
+/**
+ * This test is similar to the test above, but the sequence of injected events is different.
+ *
+ * Two windows: a window on the left and a window on the right.
+ * Mouse is hovered over the left window.
+ * Next, we tap on the left window, where the cursor was last seen.
+ *
+ * After that, we inject one finger down onto the right window, and then a second finger down onto
+ * the left window.
+ * The touch is split, so this last gesture should cause 2 ACTION_DOWN events, one in the right
+ * window (first), and then another on the left window (second).
+ * This test reproduces a crash where there is a mismatch between the downTime and eventTime.
+ * In the buggy implementation, second finger down on the left window would cause a crash.
+ */
+TEST_F(InputDispatcherTest, HoverTapAndSplitTouch) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow}}});
+
+ const int32_t mouseDeviceId = 6;
+ const int32_t touchDeviceId = 4;
+ // Hover over the left window. Keep the cursor there.
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+ AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+ .x(50)
+ .y(50))
+ .build()));
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+ // Tap on left window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(100)
+ .y(100))
+ .build()));
+
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(100)
+ .y(100))
+ .build()));
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
+
+ // First finger down on right window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(300)
+ .y(100))
+ .build()));
+ rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+ // Second finger down on the left window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(300)
+ .y(100))
+ .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(100)
+ .y(100))
+ .build()));
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+ rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+
+ // No more events
+ leftWindow->assertNoEvents();
+ rightWindow->assertNoEvents();
+}
+
+/**
+ * Start hovering with a stylus device, and then tap with a touch device. Ensure no crash occurs.
+ * While the touch is down, new hover events from the stylus device should be ignored. After the
+ * touch is gone, stylus hovering should start working again.
+ */
+TEST_F(InputDispatcherTest, StylusHoverAndTouchTap) {
+ 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, 200, 200));
+
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+ const int32_t stylusDeviceId = 5;
+ const int32_t touchDeviceId = 4;
+ // Start hovering with stylus
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+ AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS)
+ .x(50)
+ .y(50))
+ .build()));
+ window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+ // Finger down on the window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(100)
+ .y(100))
+ .build()));
+ window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+ window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+ // Try to continue hovering with stylus. Since we are already down, injection should fail
+ ASSERT_EQ(InputEventInjectionResult::FAILED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+ AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS)
+ .x(50)
+ .y(50))
+ .build()));
+ // No event should be sent. This event should be ignored because a pointer from another device
+ // is already down.
+
+ // Lift up the finger
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(100)
+ .y(100))
+ .build()));
+ window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
+
+ // Now that the touch is gone, stylus hovering should start working again
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+ AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS)
+ .x(50)
+ .y(50))
+ .build()));
+ window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+ // No more events
+ window->assertNoEvents();
+}
+
+/**
* On the display, have a single window, and also an area where there's no window.
* First pointer touches the "no window" area of the screen. Second pointer touches the window.
* Make sure that the window receives the second pointer, and first pointer is simply ignored.
@@ -2277,6 +2792,124 @@
spyWindow->assertNoEvents();
}
+TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> spyWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindow->setFrame(Rect(0, 0, 600, 800));
+ spyWindow->setTrustedOverlay(true);
+ spyWindow->setSpy(true);
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 600, 800));
+
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}});
+
+ // Send mouse cursor to the window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+ AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+ .x(100)
+ .y(100))
+ .build()));
+
+ // Move mouse cursor
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+ AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+ .x(110)
+ .y(110))
+ .build()));
+
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+ WithSource(AINPUT_SOURCE_MOUSE)));
+ spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+ WithSource(AINPUT_SOURCE_MOUSE)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithSource(AINPUT_SOURCE_MOUSE)));
+ spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithSource(AINPUT_SOURCE_MOUSE)));
+ // Touch down on the window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(SECOND_DEVICE_ID)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(200)
+ .y(200))
+ .build()));
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+ WithSource(AINPUT_SOURCE_MOUSE)));
+ spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+ WithSource(AINPUT_SOURCE_MOUSE)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+ spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+ // pilfer the motion, retaining the gesture on the spy window.
+ EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken()));
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+ // Touch UP on the window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(SECOND_DEVICE_ID)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(200)
+ .y(200))
+ .build()));
+ spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+ // Previously, a touch was pilfered. However, that gesture was just finished. Now, we are going
+ // to send a new gesture. It should again go to both windows (spy and the window below), just
+ // like the first gesture did, before pilfering. The window configuration has not changed.
+
+ // One more tap - DOWN
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(SECOND_DEVICE_ID)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(250)
+ .y(250))
+ .build()));
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+ spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+ // Touch UP on the window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(SECOND_DEVICE_ID)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(250)
+ .y(250))
+ .build()));
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+ spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+ window->assertNoEvents();
+ spyWindow->assertNoEvents();
+}
+
// This test is different from the test above that HOVER_ENTER and HOVER_EXIT events are injected
// directly in this test.
TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) {
@@ -2521,7 +3154,7 @@
// When device reset happens, that key stream should be terminated with FLAG_CANCELED
// on the app side.
- NotifyDeviceResetArgs args(10 /*id*/, 20 /*eventTime*/, DEVICE_ID);
+ NotifyDeviceResetArgs args(/*id=*/10, /*eventTime=*/20, DEVICE_ID);
mDispatcher->notifyDeviceReset(&args);
window->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT,
AKEY_EVENT_FLAG_CANCELED);
@@ -2544,10 +3177,10 @@
// When device reset happens, that motion stream should be terminated with ACTION_CANCEL
// on the app side.
- NotifyDeviceResetArgs args(10 /*id*/, 20 /*eventTime*/, DEVICE_ID);
+ NotifyDeviceResetArgs args(/*id=*/10, /*eventTime=*/20, DEVICE_ID);
mDispatcher->notifyDeviceReset(&args);
- window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, ADISPLAY_ID_DEFAULT,
- 0 /*expectedFlags*/);
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
}
TEST_F(InputDispatcherTest, InterceptKeyByPolicy) {
@@ -2726,10 +3359,8 @@
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))
+ .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,
@@ -2846,7 +3477,7 @@
MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
.displayId(ADISPLAY_ID_DEFAULT)
.eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
- .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER)
.x(untransformedPoint.x)
.y(untransformedPoint.y))
.build();
@@ -3130,7 +3761,7 @@
[&](const std::unique_ptr<InputDispatcher>& dispatcher,
sp<IBinder> from, sp<IBinder> to) {
return dispatcher->transferTouchFocus(from, to,
- false /*isDragAndDrop*/);
+ /*isDragAndDrop=*/false);
}));
TEST_F(InputDispatcherTest, TransferTouchFocus_TwoPointersSplitTouch) {
@@ -3503,7 +4134,7 @@
graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 2;
graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 3;
- window->sendTimeline(1 /*inputEventId*/, graphicsTimeline);
+ window->sendTimeline(/*inputEventId=*/1, graphicsTimeline);
window->assertNoEvents();
mDispatcher->waitForIdle();
}
@@ -3544,15 +4175,17 @@
}
void consumeMotionCancel(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
- mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL,
- expectedDisplayId, expectedFlags);
+ mInputReceiver->consumeMotionEvent(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
+ WithDisplayId(expectedDisplayId),
+ WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED)));
}
void consumeMotionPointerDown(int32_t pointerIdx) {
int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
(pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, action, ADISPLAY_ID_DEFAULT,
- 0 /*expectedFlags*/);
+ /*expectedFlags=*/0);
}
MotionEvent* consumeMotion() {
@@ -3719,7 +4352,7 @@
mDispatcher->notifyMotion(&motionArgs);
window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE, ADISPLAY_ID_DEFAULT,
- 0 /*expectedFlags*/);
+ /*expectedFlags=*/0);
}
/**
@@ -3740,35 +4373,35 @@
SCOPED_TRACE("Check default value of touch mode");
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
setFocusedWindow(window);
- window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+ window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
SCOPED_TRACE("Remove the window to trigger focus loss");
window->setFocusable(false);
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
- window->consumeFocusEvent(false /*hasFocus*/, true /*inTouchMode*/);
+ window->consumeFocusEvent(/*hasFocus=*/false, /*inTouchMode=*/true);
SCOPED_TRACE("Disable touch mode");
mDispatcher->setInTouchMode(false, windowInfo.ownerPid, windowInfo.ownerUid,
- true /*hasPermission*/, ADISPLAY_ID_DEFAULT);
+ /*hasPermission=*/true, ADISPLAY_ID_DEFAULT);
window->consumeTouchModeEvent(false);
window->setFocusable(true);
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
setFocusedWindow(window);
- window->consumeFocusEvent(true /*hasFocus*/, false /*inTouchMode*/);
+ window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/false);
SCOPED_TRACE("Remove the window to trigger focus loss");
window->setFocusable(false);
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
- window->consumeFocusEvent(false /*hasFocus*/, false /*inTouchMode*/);
+ window->consumeFocusEvent(/*hasFocus=*/false, /*inTouchMode=*/false);
SCOPED_TRACE("Enable touch mode again");
mDispatcher->setInTouchMode(true, windowInfo.ownerPid, windowInfo.ownerUid,
- true /*hasPermission*/, ADISPLAY_ID_DEFAULT);
+ /*hasPermission=*/true, ADISPLAY_ID_DEFAULT);
window->consumeTouchModeEvent(true);
window->setFocusable(true);
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
setFocusedWindow(window);
- window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+ window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
window->assertNoEvents();
}
@@ -3784,7 +4417,7 @@
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
setFocusedWindow(window);
- window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+ window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN);
mDispatcher->notifyKey(&keyArgs);
@@ -4045,8 +4678,8 @@
// Injected key goes to pending queue.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */,
- ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE));
+ injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+ InputEventInjectionSync::NONE));
// Window does not get focus event or key down.
window->assertNoEvents();
@@ -4203,23 +4836,23 @@
// Window should receive key down event.
mWindow->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT,
- 0 /*expectedFlags*/);
+ /*expectedFlags=*/0);
}
};
TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_ReceivesKeyRepeat) {
- sendAndConsumeKeyDown(1 /* deviceId */);
+ sendAndConsumeKeyDown(/*deviceId=*/1);
for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
expectKeyRepeatOnce(repeatCount);
}
}
TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_ReceivesKeyRepeatFromTwoDevices) {
- sendAndConsumeKeyDown(1 /* deviceId */);
+ sendAndConsumeKeyDown(/*deviceId=*/1);
for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
expectKeyRepeatOnce(repeatCount);
}
- sendAndConsumeKeyDown(2 /* deviceId */);
+ sendAndConsumeKeyDown(/*deviceId=*/2);
/* repeatCount will start from 1 for deviceId 2 */
for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
expectKeyRepeatOnce(repeatCount);
@@ -4227,42 +4860,42 @@
}
TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_StopsKeyRepeatAfterUp) {
- sendAndConsumeKeyDown(1 /* deviceId */);
- expectKeyRepeatOnce(1 /*repeatCount*/);
- sendAndConsumeKeyUp(1 /* deviceId */);
+ sendAndConsumeKeyDown(/*deviceId=*/1);
+ expectKeyRepeatOnce(/*repeatCount=*/1);
+ sendAndConsumeKeyUp(/*deviceId=*/1);
mWindow->assertNoEvents();
}
TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_KeyRepeatAfterStaleDeviceKeyUp) {
- sendAndConsumeKeyDown(1 /* deviceId */);
- expectKeyRepeatOnce(1 /*repeatCount*/);
- sendAndConsumeKeyDown(2 /* deviceId */);
- expectKeyRepeatOnce(1 /*repeatCount*/);
+ sendAndConsumeKeyDown(/*deviceId=*/1);
+ expectKeyRepeatOnce(/*repeatCount=*/1);
+ sendAndConsumeKeyDown(/*deviceId=*/2);
+ expectKeyRepeatOnce(/*repeatCount=*/1);
// Stale key up from device 1.
- sendAndConsumeKeyUp(1 /* deviceId */);
+ sendAndConsumeKeyUp(/*deviceId=*/1);
// Device 2 is still down, keep repeating
- expectKeyRepeatOnce(2 /*repeatCount*/);
- expectKeyRepeatOnce(3 /*repeatCount*/);
+ expectKeyRepeatOnce(/*repeatCount=*/2);
+ expectKeyRepeatOnce(/*repeatCount=*/3);
// Device 2 key up
- sendAndConsumeKeyUp(2 /* deviceId */);
+ sendAndConsumeKeyUp(/*deviceId=*/2);
mWindow->assertNoEvents();
}
TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_KeyRepeatStopsAfterRepeatingKeyUp) {
- sendAndConsumeKeyDown(1 /* deviceId */);
- expectKeyRepeatOnce(1 /*repeatCount*/);
- sendAndConsumeKeyDown(2 /* deviceId */);
- expectKeyRepeatOnce(1 /*repeatCount*/);
+ sendAndConsumeKeyDown(/*deviceId=*/1);
+ expectKeyRepeatOnce(/*repeatCount=*/1);
+ sendAndConsumeKeyDown(/*deviceId=*/2);
+ expectKeyRepeatOnce(/*repeatCount=*/1);
// Device 2 which holds the key repeating goes up, expect the repeating to stop.
- sendAndConsumeKeyUp(2 /* deviceId */);
+ sendAndConsumeKeyUp(/*deviceId=*/2);
// Device 1 still holds key down, but the repeating was already stopped
mWindow->assertNoEvents();
}
TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_StopsKeyRepeatAfterDisableInputDevice) {
sendAndConsumeKeyDown(DEVICE_ID);
- expectKeyRepeatOnce(1 /*repeatCount*/);
- NotifyDeviceResetArgs args(10 /*id*/, 20 /*eventTime*/, DEVICE_ID);
+ expectKeyRepeatOnce(/*repeatCount=*/1);
+ NotifyDeviceResetArgs args(/*id=*/10, /*eventTime=*/20, DEVICE_ID);
mDispatcher->notifyDeviceReset(&args);
mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT,
AKEY_EVENT_FLAG_CANCELED | AKEY_EVENT_FLAG_LONG_PRESS);
@@ -4270,7 +4903,7 @@
}
TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseEventIdFromInputDispatcher) {
- sendAndConsumeKeyDown(1 /* deviceId */);
+ sendAndConsumeKeyDown(/*deviceId=*/1);
for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
InputEvent* repeatEvent = mWindow->consume();
ASSERT_NE(nullptr, repeatEvent) << "Didn't receive event with repeat count " << repeatCount;
@@ -4280,7 +4913,7 @@
}
TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseUniqueEventId) {
- sendAndConsumeKeyDown(1 /* deviceId */);
+ sendAndConsumeKeyDown(/*deviceId=*/1);
std::unordered_set<int32_t> idSet;
for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
@@ -4626,11 +5259,11 @@
const nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
event.initialize(InputEvent::nextId(), injectedDeviceId, AINPUT_SOURCE_KEYBOARD,
ADISPLAY_ID_NONE, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, 0, AKEYCODE_A,
- KEY_A, AMETA_NONE, 0 /*repeatCount*/, eventTime, eventTime);
+ KEY_A, AMETA_NONE, /*repeatCount=*/0, eventTime, eventTime);
const int32_t additionalPolicyFlags =
POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_DISABLE_KEY_REPEAT;
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- mDispatcher->injectInputEvent(&event, {} /*targetUid*/,
+ mDispatcher->injectInputEvent(&event, /*targetUid=*/{},
InputEventInjectionSync::WAIT_FOR_RESULT, 10ms,
policyFlags | additionalPolicyFlags));
@@ -4665,7 +5298,7 @@
const int32_t additionalPolicyFlags = POLICY_FLAG_PASS_TO_USER;
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- mDispatcher->injectInputEvent(&event, {} /*targetUid*/,
+ mDispatcher->injectInputEvent(&event, /*targetUid=*/{},
InputEventInjectionSync::WAIT_FOR_RESULT, 10ms,
policyFlags | additionalPolicyFlags));
@@ -4685,26 +5318,26 @@
// Must have POLICY_FLAG_FILTERED here to indicate that the event has gone through the input
// filter. Without it, the event will no different from a regularly injected event, and the
// injected device id will be overwritten.
- testInjectedKey(POLICY_FLAG_FILTERED, 3 /*injectedDeviceId*/, 3 /*resolvedDeviceId*/,
- 0 /*flags*/);
+ testInjectedKey(POLICY_FLAG_FILTERED, /*injectedDeviceId=*/3, /*resolvedDeviceId=*/3,
+ /*flags=*/0);
}
TEST_F(InputFilterInjectionPolicyTest, KeyEventsInjectedFromAccessibility_HaveAccessibilityFlag) {
testInjectedKey(POLICY_FLAG_FILTERED | POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY,
- 3 /*injectedDeviceId*/, 3 /*resolvedDeviceId*/,
+ /*injectedDeviceId=*/3, /*resolvedDeviceId=*/3,
AKEY_EVENT_FLAG_IS_ACCESSIBILITY_EVENT);
}
TEST_F(InputFilterInjectionPolicyTest,
MotionEventsInjectedFromAccessibility_HaveAccessibilityFlag) {
testInjectedMotion(POLICY_FLAG_FILTERED | POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY,
- 3 /*injectedDeviceId*/, 3 /*resolvedDeviceId*/,
+ /*injectedDeviceId=*/3, /*resolvedDeviceId=*/3,
AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT);
}
TEST_F(InputFilterInjectionPolicyTest, RegularInjectedEvents_ReceiveVirtualDeviceId) {
- testInjectedKey(0 /*policyFlags*/, 3 /*injectedDeviceId*/,
- VIRTUAL_KEYBOARD_ID /*resolvedDeviceId*/, 0 /*flags*/);
+ testInjectedKey(/*policyFlags=*/0, /*injectedDeviceId=*/3,
+ /*resolvedDeviceId=*/VIRTUAL_KEYBOARD_ID, /*flags=*/0);
}
class InputDispatcherOnPointerDownOutsideFocus : public InputDispatcherTest {
@@ -4803,7 +5436,7 @@
const MotionEvent event =
MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
.eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
- .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(20).y(20))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(20).y(20))
.addFlag(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)
.build();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, event))
@@ -5080,9 +5713,9 @@
mWindow->consumeFocusEvent(false);
InputEventInjectionResult result =
- injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /*repeatCount*/, ADISPLAY_ID_DEFAULT,
- InputEventInjectionSync::NONE, 10ms /*injectionTimeout*/,
- false /* allowKeyRepeat */);
+ injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+ InputEventInjectionSync::NONE, /*injectionTimeout=*/10ms,
+ /*allowKeyRepeat=*/false);
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
// Key will not go to window because we have no focused window.
// The 'no focused window' ANR timer should start instead.
@@ -5109,8 +5742,8 @@
mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow);
mWindow->finishEvent(*sequenceNum);
- mWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL,
- ADISPLAY_ID_DEFAULT, 0 /*flags*/);
+ mWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
ASSERT_TRUE(mDispatcher->waitForIdle());
mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
}
@@ -5144,8 +5777,8 @@
// We specify the injection timeout to be smaller than the application timeout, to ensure that
// injection times out (instead of failing).
const InputEventInjectionResult result =
- injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT,
- InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, false /* allowKeyRepeat */);
+ injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+ InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, /*allowKeyRepeat=*/false);
ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
const std::chrono::duration timeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT);
mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(timeout, mApplication);
@@ -5168,12 +5801,12 @@
// Define a valid key down event that is stale (too old).
event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE,
INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, /* flags */ 0, AKEYCODE_A, KEY_A,
- AMETA_NONE, 1 /*repeatCount*/, eventTime, eventTime);
+ AMETA_NONE, /*repeatCount=*/1, eventTime, eventTime);
const int32_t policyFlags = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER;
InputEventInjectionResult result =
- mDispatcher->injectInputEvent(&event, {} /* targetUid */,
+ mDispatcher->injectInputEvent(&event, /*targetUid=*/{},
InputEventInjectionSync::WAIT_FOR_RESULT,
INJECT_EVENT_TIMEOUT, policyFlags);
ASSERT_EQ(InputEventInjectionResult::FAILED, result)
@@ -5195,8 +5828,8 @@
// We specify the injection timeout to be smaller than the application timeout, to ensure that
// injection times out (instead of failing).
const InputEventInjectionResult result =
- injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT,
- InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, false /* allowKeyRepeat */);
+ injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+ InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, /*allowKeyRepeat=*/false);
ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
const std::chrono::duration appTimeout =
mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT);
@@ -5219,7 +5852,7 @@
// Once a focused event arrives, we get an ANR for this application
const InputEventInjectionResult result =
- injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT,
+ injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
InputEventInjectionSync::WAIT_FOR_RESULT, 10ms);
ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
@@ -5277,8 +5910,8 @@
mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, spy);
spy->finishEvent(*sequenceNum);
- spy->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, ADISPLAY_ID_DEFAULT,
- 0 /*flags*/);
+ spy->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
ASSERT_TRUE(mDispatcher->waitForIdle());
mFakePolicy->assertNotifyWindowResponsiveWasCalled(spy->getToken(), mWindow->getPid());
}
@@ -5400,8 +6033,8 @@
mFakePolicy->assertNotifyAnrWasNotCalled();
// When the ANR happened, dispatcher should abort the current event stream via ACTION_CANCEL
mWindow->consumeMotionDown();
- mWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL,
- ADISPLAY_ID_DEFAULT, 0 /*flags*/);
+ mWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
mWindow->assertNoEvents();
mDispatcher->waitForIdle();
mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
@@ -5435,7 +6068,7 @@
// we will receive INJECTION_TIMED_OUT as the result.
InputEventInjectionResult result =
- injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT,
+ injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
InputEventInjectionSync::WAIT_FOR_RESULT, 10ms);
ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
// Key will not be sent to the window, yet, because the window is still processing events
@@ -5470,8 +6103,8 @@
// Don't finish the events yet, and send a key
// Injection is async, so it will succeed
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */,
- ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE));
+ injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+ InputEventInjectionSync::NONE));
// At this point, key is still pending, and should not be sent to the application yet.
std::optional<uint32_t> keySequenceNum = mWindow->receiveEvent();
ASSERT_FALSE(keySequenceNum);
@@ -5553,7 +6186,7 @@
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mFocusedWindow->consumeMotionDown();
mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
- ADISPLAY_ID_DEFAULT, 0 /*flags*/);
+ ADISPLAY_ID_DEFAULT, /*flags=*/0);
// We consumed all events, so no ANR
ASSERT_TRUE(mDispatcher->waitForIdle());
mFakePolicy->assertNotifyAnrWasNotCalled();
@@ -5627,7 +6260,7 @@
TEST_F(InputDispatcherMultiWindowAnr, DuringAnr_SecondTapIsIgnored) {
tapOnFocusedWindow();
mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
- ADISPLAY_ID_DEFAULT, 0 /*flags*/);
+ ADISPLAY_ID_DEFAULT, /*flags=*/0);
// Receive the events, but don't respond
std::optional<uint32_t> downEventSequenceNum = mFocusedWindow->receiveEvent(); // ACTION_DOWN
ASSERT_TRUE(downEventSequenceNum);
@@ -5716,8 +6349,8 @@
// window even if motions are still being processed.
InputEventInjectionResult result =
- injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /*repeatCount*/, ADISPLAY_ID_DEFAULT,
- InputEventInjectionSync::NONE, 10ms /*injectionTimeout*/);
+ injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+ InputEventInjectionSync::NONE, /*injectionTimeout=*/10ms);
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
// Key will not be sent to the window, yet, because the window is still processing events
// and the key remains pending, waiting for the touch events to be processed
@@ -5756,7 +6389,7 @@
ADISPLAY_ID_DEFAULT, {FOCUSED_WINDOW_LOCATION});
mDispatcher->notifyMotion(&motionArgs);
mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
- ADISPLAY_ID_DEFAULT, 0 /*flags*/);
+ ADISPLAY_ID_DEFAULT, /*flags=*/0);
// Touch Window 2
motionArgs = generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
@@ -5818,9 +6451,9 @@
// 'focusedApplication' will get blamed if this timer completes.
// Key will not be sent anywhere because we have no focused window. It will remain pending.
InputEventInjectionResult result =
- injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /*repeatCount*/, ADISPLAY_ID_DEFAULT,
- InputEventInjectionSync::NONE, 10ms /*injectionTimeout*/,
- false /* allowKeyRepeat */);
+ injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+ InputEventInjectionSync::NONE, /*injectionTimeout=*/10ms,
+ /*allowKeyRepeat=*/false);
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
// Wait until dispatcher starts the "no focused window" timer. If we don't wait here,
@@ -5870,7 +6503,7 @@
mNoInputWindow =
sp<FakeWindowHandle>::make(mApplication, mDispatcher,
"Window without input channel", ADISPLAY_ID_DEFAULT,
- std::make_optional<sp<IBinder>>(nullptr) /*token*/);
+ /*token=*/std::make_optional<sp<IBinder>>(nullptr));
mNoInputWindow->setNoInputChannel(true);
mNoInputWindow->setFrame(Rect(0, 0, 100, 100));
// It's perfectly valid for this window to not have an associated input channel
@@ -6060,8 +6693,8 @@
// Injected key goes to pending queue.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */,
- ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE));
+ injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+ InputEventInjectionSync::NONE));
mMirror->setVisible(true);
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mMirror}}});
@@ -6709,7 +7342,7 @@
// Transfer touch focus to the drag window
bool transferred =
mDispatcher->transferTouchFocus(mWindow->getToken(), mDragWindow->getToken(),
- true /* isDragDrop */);
+ /*isDragDrop=*/true);
if (transferred) {
mWindow->consumeMotionCancel();
mDragWindow->consumeMotionDown();
@@ -6765,8 +7398,8 @@
const MotionEvent secondFingerDownEvent =
MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
.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(60).y(60))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(60).y(60))
.build();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -6909,14 +7542,14 @@
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(75).y(50))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(75).y(50))
.build();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
InputEventInjectionSync::WAIT_FOR_RESULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- mWindow->consumeMotionPointerDown(1 /* pointerIndex */);
+ mWindow->consumeMotionPointerDown(/*pointerIndex=*/1);
// Should not perform drag and drop when window has multi fingers.
ASSERT_FALSE(startDrag(false));
@@ -6936,9 +7569,8 @@
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(150).y(50))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
+ .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
.build();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -6953,9 +7585,8 @@
const MotionEvent secondFingerMoveEvent =
MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
.eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
- .pointer(
- PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
+ .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
.build();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT,
@@ -6968,9 +7599,8 @@
const MotionEvent secondFingerUpEvent =
MotionEventBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
.eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
- .pointer(
- PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
+ .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
.build();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
@@ -7000,7 +7630,7 @@
.y(100))
.build()));
windowInSecondary->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN,
- SECOND_DISPLAY_ID, 0 /* expectedFlag */);
+ SECOND_DISPLAY_ID, /*expectedFlag=*/0);
// Update window again.
mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {windowInSecondary}}});
@@ -7093,7 +7723,7 @@
window->setFocusable(true);
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
setFocusedWindow(window);
- window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+ window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
// With the flag set, window should not get any input
NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
@@ -7139,7 +7769,7 @@
window->setFocusable(true);
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
setFocusedWindow(window);
- window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+ window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
// With the flag set, window should not get any input
NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
@@ -7185,7 +7815,7 @@
window->setFocusable(true);
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
setFocusedWindow(window);
- window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+ window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
// With the flag set, window should not get any input
NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
@@ -7244,8 +7874,7 @@
// Set main display initial touch mode to InputDispatcher::kDefaultInTouchMode.
if (mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, WINDOW_PID,
- WINDOW_UID, true /* hasPermission */,
- ADISPLAY_ID_DEFAULT)) {
+ WINDOW_UID, /*hasPermission=*/true, ADISPLAY_ID_DEFAULT)) {
mWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode);
mSecondWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode);
mThirdWindow->assertNoEvents();
@@ -7253,7 +7882,7 @@
// Set secondary display initial touch mode to InputDispatcher::kDefaultInTouchMode.
if (mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, SECONDARY_WINDOW_PID,
- SECONDARY_WINDOW_UID, true /* hasPermission */,
+ SECONDARY_WINDOW_UID, /*hasPermission=*/true,
SECOND_DISPLAY_ID)) {
mWindow->assertNoEvents();
mSecondWindow->assertNoEvents();
@@ -7275,7 +7904,7 @@
const WindowInfo& windowInfo = *mWindow->getInfo();
changeAndVerifyTouchModeInMainDisplayOnly(!InputDispatcher::kDefaultInTouchMode,
windowInfo.ownerPid, windowInfo.ownerUid,
- false /* hasPermission */);
+ /* hasPermission=*/false);
}
TEST_F(InputDispatcherTouchModeChangedTests, NonFocusedWindowOwnerCannotChangeTouchMode) {
@@ -7284,7 +7913,7 @@
int32_t ownerUid = windowInfo.ownerUid;
mWindow->setOwnerInfo(/* pid */ -1, /* uid */ -1);
ASSERT_FALSE(mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, ownerPid,
- ownerUid, false /*hasPermission*/,
+ ownerUid, /*hasPermission=*/false,
ADISPLAY_ID_DEFAULT));
mWindow->assertNoEvents();
mSecondWindow->assertNoEvents();
@@ -7296,14 +7925,14 @@
int32_t ownerUid = windowInfo.ownerUid;
mWindow->setOwnerInfo(/* pid */ -1, /* uid */ -1);
changeAndVerifyTouchModeInMainDisplayOnly(!InputDispatcher::kDefaultInTouchMode, ownerPid,
- ownerUid, true /*hasPermission*/);
+ ownerUid, /*hasPermission=*/true);
}
TEST_F(InputDispatcherTouchModeChangedTests, EventIsNotGeneratedIfNotChangingTouchMode) {
const WindowInfo& windowInfo = *mWindow->getInfo();
ASSERT_FALSE(mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode,
windowInfo.ownerPid, windowInfo.ownerUid,
- true /*hasPermission*/, ADISPLAY_ID_DEFAULT));
+ /*hasPermission=*/true, ADISPLAY_ID_DEFAULT));
mWindow->assertNoEvents();
mSecondWindow->assertNoEvents();
}
@@ -7312,7 +7941,7 @@
const WindowInfo& windowInfo = *mThirdWindow->getInfo();
ASSERT_TRUE(mDispatcher->setInTouchMode(!InputDispatcher::kDefaultInTouchMode,
windowInfo.ownerPid, windowInfo.ownerUid,
- true /*hasPermission*/, SECOND_DISPLAY_ID));
+ /*hasPermission=*/true, SECOND_DISPLAY_ID));
mWindow->assertNoEvents();
mSecondWindow->assertNoEvents();
mThirdWindow->consumeTouchModeEvent(!InputDispatcher::kDefaultInTouchMode);
@@ -7332,7 +7961,7 @@
const WindowInfo& windowInfo = *mWindow->getInfo();
ASSERT_TRUE(mDispatcher->setInTouchMode(!InputDispatcher::kDefaultInTouchMode,
windowInfo.ownerPid, windowInfo.ownerUid,
- false /*hasPermission*/, ADISPLAY_ID_DEFAULT));
+ /*hasPermission=*/false, ADISPLAY_ID_DEFAULT));
}
class InputDispatcherSpyWindowTest : public InputDispatcherTest {
@@ -7544,16 +8173,15 @@
const MotionEvent secondFingerDownEvent =
MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
.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(150).y(50))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
.build();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
InputEventInjectionSync::WAIT_FOR_RESULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
windowRight->consumeMotionDown();
- spy->consumeMotionPointerDown(1 /*pointerIndex*/);
+ spy->consumeMotionPointerDown(/*pointerIndex=*/1);
}
/**
@@ -7577,15 +8205,14 @@
const MotionEvent secondFingerDownEvent =
MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
.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(150).y(50))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
.build();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
InputEventInjectionSync::WAIT_FOR_RESULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeMotionPointerDown(1 /*pointerIndex*/);
+ window->consumeMotionPointerDown(/*pointerIndex=*/1);
spyRight->consumeMotionDown();
}
@@ -7617,10 +8244,8 @@
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(100)
- .y(200))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(200))
+ .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
.build();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -7628,7 +8253,7 @@
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
- spy->consumeMotionPointerDown(1 /* pointerIndex */);
+ spy->consumeMotionPointerDown(/*pointerIndex=*/1);
}
/**
@@ -7742,33 +8367,29 @@
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(100)
- .y(200))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(200))
+ .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
.build();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
InputEventInjectionSync::WAIT_FOR_RESULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- spy->consumeMotionPointerDown(1 /*pointerIndex*/);
+ spy->consumeMotionPointerDown(/*pointerIndex=*/1);
// Third finger goes down outside all windows, so injection should fail.
const MotionEvent thirdFingerDownEvent =
MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
.displayId(ADISPLAY_ID_DEFAULT)
.eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
- .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
- .x(100)
- .y(200))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
- .pointer(PointerBuilder(/* id */ 2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(-5).y(-5))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(200))
+ .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/*id=*/2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(-5).y(-5))
.build();
ASSERT_EQ(InputEventInjectionResult::FAILED,
injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT,
InputEventInjectionSync::WAIT_FOR_RESULT))
- << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ << "Inject motion event should return InputEventInjectionResult::FAILED";
spy->assertNoEvents();
window->assertNoEvents();
@@ -7797,10 +8418,8 @@
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(150)
- .y(150))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150))
+ .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
.build();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -7814,11 +8433,9 @@
MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
.displayId(ADISPLAY_ID_DEFAULT)
.eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
- .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
- .x(150)
- .y(150))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
- .pointer(PointerBuilder(/* id */ 2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150))
+ .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
+ .pointer(PointerBuilder(/*id=*/2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
.build();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -7862,8 +8479,8 @@
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(10).y(10))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
+ .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
.build();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -7909,10 +8526,8 @@
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(10).y(10))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
- .x(150)
- .y(150))
+ .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
+ .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150))
.build();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -7951,7 +8566,7 @@
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
setFocusedWindow(window);
- window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+ window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
return {std::move(overlay), std::move(window)};
}
@@ -8075,9 +8690,9 @@
}
InputEventInjectionResult injectTargetedKey(int32_t action) const {
- return inputdispatcher::injectKey(mDispatcher, action, 0 /* repeatCount*/, ADISPLAY_ID_NONE,
+ return inputdispatcher::injectKey(mDispatcher, action, /*repeatCount=*/0, ADISPLAY_ID_NONE,
InputEventInjectionSync::WAIT_FOR_RESULT,
- INJECT_EVENT_TIMEOUT, false /*allowKeyRepeat*/, {mUid},
+ INJECT_EVENT_TIMEOUT, /*allowKeyRepeat=*/false, {mUid},
mPolicyFlags);
}
diff --git a/services/inputflinger/tests/InputProcessorConverter_test.cpp b/services/inputflinger/tests/InputProcessorConverter_test.cpp
index 040c8da..161a24f 100644
--- a/services/inputflinger/tests/InputProcessorConverter_test.cpp
+++ b/services/inputflinger/tests/InputProcessorConverter_test.cpp
@@ -38,15 +38,15 @@
coords.setAxisValue(AMOTION_EVENT_AXIS_Y, 2);
coords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.5);
static constexpr nsecs_t downTime = 2;
- NotifyMotionArgs motionArgs(1 /*sequenceNum*/, downTime /*eventTime*/, 2 /*readTime*/,
- 3 /*deviceId*/, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT,
- 4 /*policyFlags*/, AMOTION_EVENT_ACTION_DOWN, 0 /*actionButton*/,
- 0 /*flags*/, AMETA_NONE, 0 /*buttonState*/,
+ NotifyMotionArgs motionArgs(/*sequenceNum=*/1, /*eventTime=*/downTime, /*readTime=*/2,
+ /*deviceId=*/3, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT,
+ /*policyFlags=*/4, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0,
+ /*flags=*/0, AMETA_NONE, /*buttonState=*/0,
MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE,
- 1 /*pointerCount*/, &properties, &coords, 0 /*xPrecision*/,
- 0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+ /*pointerCount=*/1, &properties, &coords, /*xPrecision=*/0,
+ /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime,
- {} /*videoFrames*/);
+ /*videoFrames=*/{});
return motionArgs;
}
diff --git a/services/inputflinger/tests/InputProcessor_test.cpp b/services/inputflinger/tests/InputProcessor_test.cpp
index 380001c..b6deed8 100644
--- a/services/inputflinger/tests/InputProcessor_test.cpp
+++ b/services/inputflinger/tests/InputProcessor_test.cpp
@@ -44,15 +44,15 @@
coords.setAxisValue(AMOTION_EVENT_AXIS_X, 1);
coords.setAxisValue(AMOTION_EVENT_AXIS_Y, 1);
static constexpr nsecs_t downTime = 2;
- NotifyMotionArgs motionArgs(1 /*sequenceNum*/, downTime /*eventTime*/, 2 /*readTime*/,
- 3 /*deviceId*/, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT,
- 4 /*policyFlags*/, AMOTION_EVENT_ACTION_DOWN, 0 /*actionButton*/,
- 0 /*flags*/, AMETA_NONE, 0 /*buttonState*/,
+ NotifyMotionArgs motionArgs(/*sequenceNum=*/1, /*eventTime=*/downTime, /*readTime=*/2,
+ /*deviceId=*/3, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT,
+ /*policyFlags=*/4, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0,
+ /*flags=*/0, AMETA_NONE, /*buttonState=*/0,
MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE,
- 1 /*pointerCount*/, &properties, &coords, 0 /*xPrecision*/,
- 0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+ /*pointerCount=*/1, &properties, &coords, /*xPrecision=*/0,
+ /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime,
- {} /*videoFrames*/);
+ /*videoFrames=*/{});
return motionArgs;
}
@@ -70,7 +70,7 @@
*/
TEST_F(InputProcessorTest, SendToNextStage_NotifyConfigurationChangedArgs) {
// Create a basic configuration change and send to processor
- NotifyConfigurationChangedArgs args(1 /*sequenceNum*/, 2 /*eventTime*/);
+ NotifyConfigurationChangedArgs args(/*sequenceNum=*/1, /*eventTime=*/2);
mProcessor->notifyConfigurationChanged(&args);
NotifyConfigurationChangedArgs outArgs;
@@ -80,10 +80,10 @@
TEST_F(InputProcessorTest, SendToNextStage_NotifyKeyArgs) {
// Create a basic key event and send to processor
- NotifyKeyArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 21 /*readTime*/, 3 /*deviceId*/,
- AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, 0 /*policyFlags*/,
- AKEY_EVENT_ACTION_DOWN, 4 /*flags*/, AKEYCODE_HOME, 5 /*scanCode*/,
- AMETA_NONE, 6 /*downTime*/);
+ NotifyKeyArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*readTime=*/21, /*deviceId=*/3,
+ AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, /*policyFlags=*/0,
+ AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5,
+ AMETA_NONE, /*downTime=*/6);
mProcessor->notifyKey(&args);
NotifyKeyArgs outArgs;
@@ -108,8 +108,8 @@
* Expect that the event is received by the next input stage, unmodified.
*/
TEST_F(InputProcessorTest, SendToNextStage_NotifySwitchArgs) {
- NotifySwitchArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 3 /*policyFlags*/, 4 /*switchValues*/,
- 5 /*switchMask*/);
+ NotifySwitchArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*policyFlags=*/3,
+ /*switchValues=*/4, /*switchMask=*/5);
mProcessor->notifySwitch(&args);
NotifySwitchArgs outArgs;
@@ -122,7 +122,7 @@
* Expect that the event is received by the next input stage, unmodified.
*/
TEST_F(InputProcessorTest, SendToNextStage_NotifyDeviceResetArgs) {
- NotifyDeviceResetArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 3 /*deviceId*/);
+ NotifyDeviceResetArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*deviceId=*/3);
mProcessor->notifyDeviceReset(&args);
NotifyDeviceResetArgs outArgs;
@@ -252,7 +252,7 @@
* Make sure MotionClassifier does not crash when a device is reset.
*/
TEST_F(MotionClassifierTest, DeviceReset_DoesNotCrash) {
- NotifyDeviceResetArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 3 /*deviceId*/);
+ NotifyDeviceResetArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*deviceId=*/3);
ASSERT_NO_FATAL_FAILURE(mMotionClassifier->reset(args));
}
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index feda191..fe7af80 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -367,7 +367,7 @@
// Add an internal viewport, then clear it
mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
- true /*isActive*/, uniqueId, NO_PORT, ViewportType::INTERNAL);
+ /*isActive=*/true, uniqueId, NO_PORT, ViewportType::INTERNAL);
// Check matching by uniqueId
internalViewport = mFakePolicy->getDisplayViewportByUniqueId(uniqueId);
@@ -397,19 +397,19 @@
// Add an internal viewport
mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
- true /*isActive*/, internalUniqueId, NO_PORT,
+ /*isActive=*/true, internalUniqueId, NO_PORT,
ViewportType::INTERNAL);
// Add an external viewport
mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
- true /*isActive*/, externalUniqueId, NO_PORT,
+ /*isActive=*/true, externalUniqueId, NO_PORT,
ViewportType::EXTERNAL);
// Add an virtual viewport
mFakePolicy->addDisplayViewport(virtualDisplayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- ui::ROTATION_0, true /*isActive*/, virtualUniqueId1, NO_PORT,
+ ui::ROTATION_0, /*isActive=*/true, virtualUniqueId1, NO_PORT,
ViewportType::VIRTUAL);
// Add another virtual viewport
mFakePolicy->addDisplayViewport(virtualDisplayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- ui::ROTATION_0, true /*isActive*/, virtualUniqueId2, NO_PORT,
+ ui::ROTATION_0, /*isActive=*/true, virtualUniqueId2, NO_PORT,
ViewportType::VIRTUAL);
// Check matching by type for internal
@@ -459,10 +459,10 @@
mFakePolicy->clearViewports();
// Add a viewport
mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
- true /*isActive*/, uniqueId1, NO_PORT, type);
+ /*isActive=*/true, uniqueId1, NO_PORT, type);
// Add another viewport
mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
- true /*isActive*/, uniqueId2, NO_PORT, type);
+ /*isActive=*/true, uniqueId2, NO_PORT, type);
// Check that correct display viewport was returned by comparing the display IDs.
std::optional<DisplayViewport> viewport1 =
@@ -502,10 +502,10 @@
// Add the default display first and ensure it gets returned.
mFakePolicy->clearViewports();
mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- ui::ROTATION_0, true /*isActive*/, uniqueId1, NO_PORT,
+ ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT,
ViewportType::INTERNAL);
mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- ui::ROTATION_0, true /*isActive*/, uniqueId2, NO_PORT,
+ ui::ROTATION_0, /*isActive=*/true, uniqueId2, NO_PORT,
ViewportType::INTERNAL);
std::optional<DisplayViewport> viewport =
@@ -517,10 +517,10 @@
// Add the default display second to make sure order doesn't matter.
mFakePolicy->clearViewports();
mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- ui::ROTATION_0, true /*isActive*/, uniqueId2, NO_PORT,
+ ui::ROTATION_0, /*isActive=*/true, uniqueId2, NO_PORT,
ViewportType::INTERNAL);
mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- ui::ROTATION_0, true /*isActive*/, uniqueId1, NO_PORT,
+ ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT,
ViewportType::INTERNAL);
viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
@@ -545,10 +545,10 @@
mFakePolicy->clearViewports();
// Add a viewport that's associated with some display port that's not of interest.
mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
- true /*isActive*/, uniqueId1, hdmi3, type);
+ /*isActive=*/true, uniqueId1, hdmi3, type);
// Add another viewport, connected to HDMI1 port
mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
- true /*isActive*/, uniqueId2, hdmi1, type);
+ /*isActive=*/true, uniqueId2, hdmi1, type);
// Check that correct display viewport was returned by comparing the display ports.
std::optional<DisplayViewport> hdmi1Viewport = mFakePolicy->getDisplayViewportByPort(hdmi1);
@@ -1002,9 +1002,9 @@
// Add default and second display.
mFakePolicy->clearViewports();
mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
- true /*isActive*/, "local:0", NO_PORT, ViewportType::INTERNAL);
+ /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- ui::ROTATION_0, true /*isActive*/, "local:1", hdmi1,
+ ui::ROTATION_0, /*isActive=*/true, "local:1", hdmi1,
ViewportType::EXTERNAL);
mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
mReader->loopOnce();
@@ -1255,15 +1255,15 @@
.maxBrightness = 255,
.flags = InputLightClass::BRIGHTNESS,
.path = ""};
- mFakeEventHub->addRawLightInfo(1 /* rawId */, std::move(info));
- mFakeEventHub->fakeLightBrightness(1 /* rawId */, 0x55);
+ mFakeEventHub->addRawLightInfo(/*rawId=*/1, std::move(info));
+ mFakeEventHub->fakeLightBrightness(/*rawId=*/1, 0x55);
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
- ASSERT_TRUE(controller.setLightColor(1 /* lightId */, LIGHT_BRIGHTNESS));
- ASSERT_EQ(controller.getLightColor(1 /* lightId */), LIGHT_BRIGHTNESS);
- ASSERT_TRUE(mReader->setLightColor(deviceId, 1 /* lightId */, LIGHT_BRIGHTNESS));
- ASSERT_EQ(mReader->getLightColor(deviceId, 1 /* lightId */), LIGHT_BRIGHTNESS);
+ ASSERT_TRUE(controller.setLightColor(/*lightId=*/1, LIGHT_BRIGHTNESS));
+ ASSERT_EQ(controller.getLightColor(/*lightId=*/1), LIGHT_BRIGHTNESS);
+ ASSERT_TRUE(mReader->setLightColor(deviceId, /*lightId=*/1, LIGHT_BRIGHTNESS));
+ ASSERT_EQ(mReader->getLightColor(deviceId, /*lightId=*/1), LIGHT_BRIGHTNESS);
}
// --- InputReaderIntegrationTest ---
@@ -1288,8 +1288,8 @@
mFakePolicy = sp<FakeInputReaderPolicy>::make();
mFakePointerController = std::make_shared<FakePointerController>();
mFakePolicy->setPointerController(mFakePointerController);
- mTestListener = std::make_unique<TestInputListener>(2000ms /*eventHappenedTimeout*/,
- 30ms /*eventDidNotHappenTimeout*/);
+ mTestListener = std::make_unique<TestInputListener>(/*eventHappenedTimeout=*/2000ms,
+ /*eventDidNotHappenTimeout=*/30ms);
mReader = std::make_unique<InputReader>(std::make_shared<EventHub>(), mFakePolicy,
*mTestListener);
@@ -1326,7 +1326,7 @@
// An invalid input device that is only used for this test.
class InvalidUinputDevice : public UinputDevice {
public:
- InvalidUinputDevice() : UinputDevice("Invalid Device", 99 /*productId*/) {}
+ InvalidUinputDevice() : UinputDevice("Invalid Device", /*productId=*/99) {}
private:
void configureDevice(int fd, uinput_user_dev* device) override {}
@@ -1478,7 +1478,7 @@
ui::Rotation orientation, const std::string& uniqueId,
std::optional<uint8_t> physicalPort,
ViewportType viewportType) {
- mFakePolicy->addDisplayViewport(displayId, width, height, orientation, true /*isActive*/,
+ mFakePolicy->addDisplayViewport(displayId, width, height, orientation, /*isActive=*/true,
uniqueId, physicalPort, viewportType);
mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
}
@@ -2452,7 +2452,7 @@
// Prepare displays.
mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- ui::ROTATION_0, true /*isActive*/, UNIQUE_ID, hdmi,
+ ui::ROTATION_0, /*isActive=*/true, UNIQUE_ID, hdmi,
ViewportType::INTERNAL);
unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
InputReaderConfiguration::CHANGE_DISPLAY_INFO);
@@ -2528,8 +2528,8 @@
constexpr int32_t TEST_EVENTHUB_ID = 10;
mFakeEventHub->addDevice(TEST_EVENTHUB_ID, "Test EventHub device", InputDeviceClass::BATTERY);
- InputDevice device(mReader->getContext(), 1 /*id*/, 2 /*generation*/, {} /*identifier*/);
- device.addEventHubDevice(TEST_EVENTHUB_ID, true /*populateMappers*/);
+ InputDevice device(mReader->getContext(), /*id=*/1, /*generation=*/2, /*identifier=*/{});
+ device.addEventHubDevice(TEST_EVENTHUB_ID, /*populateMappers=*/true);
device.removeEventHubDevice(TEST_EVENTHUB_ID);
std::string dumpStr, eventHubDevStr;
device.dump(dumpStr, eventHubDevStr);
@@ -2609,12 +2609,12 @@
VibrationElement pattern(2);
VibrationSequence sequence(2);
pattern.duration = std::chrono::milliseconds(200);
- pattern.channels = {{0 /* vibratorId */, DEFAULT_AMPLITUDE / 2},
- {1 /* vibratorId */, DEFAULT_AMPLITUDE}};
+ pattern.channels = {{/*vibratorId=*/0, DEFAULT_AMPLITUDE / 2},
+ {/*vibratorId=*/1, DEFAULT_AMPLITUDE}};
sequence.addElement(pattern);
pattern.duration = std::chrono::milliseconds(500);
- pattern.channels = {{0 /* vibratorId */, DEFAULT_AMPLITUDE / 4},
- {1 /* vibratorId */, DEFAULT_AMPLITUDE}};
+ pattern.channels = {{/*vibratorId=*/0, DEFAULT_AMPLITUDE / 4},
+ {/*vibratorId=*/1, DEFAULT_AMPLITUDE}};
sequence.addElement(pattern);
std::vector<int64_t> timings = {0, 1};
@@ -2622,7 +2622,7 @@
ASSERT_FALSE(mapper.isVibrating());
// Start vibrating
- std::list<NotifyArgs> out = mapper.vibrate(sequence, -1 /* repeat */, VIBRATION_TOKEN);
+ std::list<NotifyArgs> out = mapper.vibrate(sequence, /*repeat=*/-1, VIBRATION_TOKEN);
ASSERT_TRUE(mapper.isVibrating());
// Verify vibrator state listener was notified.
mReader->loopOnce();
@@ -2981,12 +2981,12 @@
NotifyKeyArgs args;
// Key down
- process(mapper, ARBITRARY_TIME, 12 /*readTime*/, EV_KEY, KEY_HOME, 1);
+ process(mapper, ARBITRARY_TIME, /*readTime=*/12, EV_KEY, KEY_HOME, 1);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
ASSERT_EQ(12, args.readTime);
// Key up
- process(mapper, ARBITRARY_TIME, 15 /*readTime*/, EV_KEY, KEY_HOME, 1);
+ process(mapper, ARBITRARY_TIME, /*readTime=*/15, EV_KEY, KEY_HOME, 1);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
ASSERT_EQ(15, args.readTime);
}
@@ -3369,7 +3369,7 @@
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
std::list<NotifyArgs> unused =
device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
- 0 /*changes*/);
+ /*changes=*/0);
unused += device2->reset(ARBITRARY_TIME);
// Prepared displays and associated info.
@@ -3479,7 +3479,7 @@
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
std::list<NotifyArgs> unused =
device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
- 0 /*changes*/);
+ /*changes=*/0);
unused += device2->reset(ARBITRARY_TIME);
ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_CAPSL));
@@ -3540,7 +3540,7 @@
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
std::list<NotifyArgs> unused =
device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
- 0 /*changes*/);
+ /*changes=*/0);
unused += device2->reset(ARBITRARY_TIME);
// Initial metastate is AMETA_NONE.
@@ -4508,8 +4508,8 @@
*/
TEST_F(CursorInputMapperTest, PointerCaptureDisablesVelocityProcessing) {
addConfigurationProperty("cursor.mode", "pointer");
- const VelocityControlParameters testParams(5.f /*scale*/, 0.f /*low threshold*/,
- 100.f /*high threshold*/, 10.f /*acceleration*/);
+ const VelocityControlParameters testParams(/*scale=*/5.f, /*low threshold=*/0.f,
+ /*high threshold=*/100.f, /*acceleration=*/10.f);
mFakePolicy->setVelocityControlParams(testParams);
CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
@@ -6678,6 +6678,52 @@
ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mapper.getSources());
}
+TEST_F(SingleTouchInputMapperTest, Process_WhenConfigEnabled_ShouldShowDirectStylusPointer) {
+ std::shared_ptr<FakePointerController> fakePointerController =
+ std::make_shared<FakePointerController>();
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(ui::ROTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION);
+ mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
+ mFakePolicy->setPointerController(fakePointerController);
+ mFakePolicy->setStylusPointerIconEnabled(true);
+ SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+
+ processKey(mapper, BTN_TOOL_PEN, 1);
+ processMove(mapper, 100, 200);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+ WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
+ ASSERT_TRUE(fakePointerController->isPointerShown());
+ ASSERT_NO_FATAL_FAILURE(
+ fakePointerController->assertPosition(toDisplayX(100), toDisplayY(200)));
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirectStylusPointer) {
+ std::shared_ptr<FakePointerController> fakePointerController =
+ std::make_shared<FakePointerController>();
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(ui::ROTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION);
+ mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
+ mFakePolicy->setPointerController(fakePointerController);
+ mFakePolicy->setStylusPointerIconEnabled(false);
+ SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+
+ processKey(mapper, BTN_TOOL_PEN, 1);
+ processMove(mapper, 100, 200);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+ WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
+ ASSERT_FALSE(fakePointerController->isPointerShown());
+}
+
// --- TouchDisplayProjectionTest ---
class TouchDisplayProjectionTest : public SingleTouchInputMapperTest {
@@ -8886,18 +8932,18 @@
MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
prepareDisplay(ui::ROTATION_0);
- process(mapper, 10, 11 /*readTime*/, EV_ABS, ABS_MT_TRACKING_ID, 1);
- process(mapper, 15, 16 /*readTime*/, EV_ABS, ABS_MT_POSITION_X, 100);
- process(mapper, 20, 21 /*readTime*/, EV_ABS, ABS_MT_POSITION_Y, 100);
- process(mapper, 25, 26 /*readTime*/, EV_SYN, SYN_REPORT, 0);
+ process(mapper, 10, /*readTime=*/11, EV_ABS, ABS_MT_TRACKING_ID, 1);
+ process(mapper, 15, /*readTime=*/16, EV_ABS, ABS_MT_POSITION_X, 100);
+ process(mapper, 20, /*readTime=*/21, EV_ABS, ABS_MT_POSITION_Y, 100);
+ process(mapper, 25, /*readTime=*/26, EV_SYN, SYN_REPORT, 0);
NotifyMotionArgs args;
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(26, args.readTime);
- process(mapper, 30, 31 /*readTime*/, EV_ABS, ABS_MT_POSITION_X, 110);
- process(mapper, 30, 32 /*readTime*/, EV_ABS, ABS_MT_POSITION_Y, 220);
- process(mapper, 30, 33 /*readTime*/, EV_SYN, SYN_REPORT, 0);
+ process(mapper, 30, /*readTime=*/31, EV_ABS, ABS_MT_POSITION_X, 110);
+ process(mapper, 30, /*readTime=*/32, EV_ABS, ABS_MT_POSITION_Y, 220);
+ process(mapper, 30, /*readTime=*/33, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(33, args.readTime);
@@ -8911,7 +8957,7 @@
addConfigurationProperty("touch.deviceType", "touchScreen");
// Don't set touch.enableForInactiveViewport to verify the default behavior.
mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
- false /*isActive*/, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
+ /*isActive=*/false, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
prepareAxes(POSITION);
MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
@@ -8931,7 +8977,7 @@
addConfigurationProperty("touch.deviceType", "touchScreen");
addConfigurationProperty("touch.enableForInactiveViewport", "1");
mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
- false /*isActive*/, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
+ /*isActive=*/false, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
prepareAxes(POSITION);
MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
@@ -8948,7 +8994,7 @@
addConfigurationProperty("touch.deviceType", "touchScreen");
addConfigurationProperty("touch.enableForInactiveViewport", "0");
mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
- true /*isActive*/, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
+ /*isActive=*/true, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
std::optional<DisplayViewport> optionalDisplayViewport =
mFakePolicy->getDisplayViewportByUniqueId(UNIQUE_ID);
ASSERT_TRUE(optionalDisplayViewport.has_value());
@@ -9011,14 +9057,14 @@
ftl::Flags<InputDeviceClass>(0));
mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX,
- 0 /*flat*/, 0 /*fuzz*/);
+ /*flat=*/0, /*fuzz=*/0);
mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_POSITION_Y, RAW_Y_MIN, RAW_Y_MAX,
- 0 /*flat*/, 0 /*fuzz*/);
+ /*flat=*/0, /*fuzz=*/0);
mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_TRACKING_ID, RAW_ID_MIN, RAW_ID_MAX,
- 0 /*flat*/, 0 /*fuzz*/);
+ /*flat=*/0, /*fuzz=*/0);
mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_SLOT, RAW_SLOT_MIN, RAW_SLOT_MAX,
- 0 /*flat*/, 0 /*fuzz*/);
- mFakeEventHub->setAbsoluteAxisValue(SECOND_EVENTHUB_ID, ABS_MT_SLOT, 0 /*value*/);
+ /*flat=*/0, /*fuzz=*/0);
+ mFakeEventHub->setAbsoluteAxisValue(SECOND_EVENTHUB_ID, ABS_MT_SLOT, /*value=*/0);
mFakeEventHub->addConfigurationProperty(SECOND_EVENTHUB_ID, String8("touch.deviceType"),
String8("touchScreen"));
@@ -9026,7 +9072,7 @@
MultiTouchInputMapper& mapper2 = device2->addMapper<MultiTouchInputMapper>(SECOND_EVENTHUB_ID);
std::list<NotifyArgs> unused =
device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
- 0 /*changes*/);
+ /*changes=*/0);
unused += device2->reset(ARBITRARY_TIME);
// Setup PointerController.
@@ -9757,6 +9803,58 @@
WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
}
+TEST_F(MultiTouchInputMapperTest, Process_WhenConfigEnabled_ShouldShowDirectStylusPointer) {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(ui::ROTATION_0);
+ prepareAxes(POSITION | ID | SLOT | TOOL_TYPE | PRESSURE);
+ // Add BTN_TOOL_PEN to statically show stylus support, since using ABS_MT_TOOL_TYPE can only
+ // indicate stylus presence dynamically.
+ mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
+ std::shared_ptr<FakePointerController> fakePointerController =
+ std::make_shared<FakePointerController>();
+ mFakePolicy->setPointerController(fakePointerController);
+ mFakePolicy->setStylusPointerIconEnabled(true);
+ MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+
+ processId(mapper, FIRST_TRACKING_ID);
+ processPressure(mapper, RAW_PRESSURE_MIN);
+ processPosition(mapper, 100, 200);
+ processToolType(mapper, MT_TOOL_PEN);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+ WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
+ ASSERT_TRUE(fakePointerController->isPointerShown());
+ ASSERT_NO_FATAL_FAILURE(
+ fakePointerController->assertPosition(toDisplayX(100), toDisplayY(200)));
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirectStylusPointer) {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(ui::ROTATION_0);
+ prepareAxes(POSITION | ID | SLOT | TOOL_TYPE | PRESSURE);
+ // Add BTN_TOOL_PEN to statically show stylus support, since using ABS_MT_TOOL_TYPE can only
+ // indicate stylus presence dynamically.
+ mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
+ std::shared_ptr<FakePointerController> fakePointerController =
+ std::make_shared<FakePointerController>();
+ mFakePolicy->setPointerController(fakePointerController);
+ mFakePolicy->setStylusPointerIconEnabled(false);
+ MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+
+ processId(mapper, FIRST_TRACKING_ID);
+ processPressure(mapper, RAW_PRESSURE_MIN);
+ processPosition(mapper, 100, 200);
+ processToolType(mapper, MT_TOOL_PEN);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+ WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
+ ASSERT_FALSE(fakePointerController->isPointerShown());
+}
+
// --- MultiTouchInputMapperTest_ExternalDevice ---
class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest {
@@ -9986,6 +10084,26 @@
ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], 100 * scale, 100 * scale, 0,
0, 0, 0, 0, 0, 0, 0));
+
+ // BUTTON DOWN
+ processKey(mapper, BTN_LEFT, 1);
+ processSync(mapper);
+
+ // touchinputmapper design sends a move before button press
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
+
+ // BUTTON UP
+ processKey(mapper, BTN_LEFT, 0);
+ processSync(mapper);
+
+ // touchinputmapper design sends a move after button release
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
}
TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) {
@@ -10108,7 +10226,7 @@
// The min freeform gesture width is 25units/mm x 30mm = 750
// which is greater than fraction of the diagnal length of the touchpad (349).
// Thus, MaxSwipWidth is 750.
- preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/);
+ preparePointerMode(/*xResolution=*/25, /*yResolution=*/25);
MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
NotifyMotionArgs motionArgs;
@@ -10168,7 +10286,7 @@
// The min freeform gesture width is 5units/mm x 30mm = 150
// which is greater than fraction of the diagnal length of the touchpad (349).
// Thus, MaxSwipWidth is the fraction of the diagnal length, 349.
- preparePointerMode(5 /*xResolution*/, 5 /*yResolution*/);
+ preparePointerMode(/*xResolution=*/5, /*yResolution=*/5);
MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
NotifyMotionArgs motionArgs;
@@ -10224,7 +10342,7 @@
* freeform gestures after two fingers start to move downwards.
*/
TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthFreeform) {
- preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/);
+ preparePointerMode(/*xResolution=*/25, /*yResolution=*/25);
MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
NotifyMotionArgs motionArgs;
@@ -10319,7 +10437,7 @@
}
TEST_F(MultiTouchPointerModeTest, TwoFingerSwipeOffsets) {
- preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/);
+ preparePointerMode(/*xResolution=*/25, /*yResolution=*/25);
MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
NotifyMotionArgs motionArgs;
@@ -10365,7 +10483,7 @@
}
TEST_F(MultiTouchPointerModeTest, WhenViewportActiveStatusChanged_PointerGestureIsReset) {
- preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/);
+ preparePointerMode(/*xResolution=*/25, /*yResolution=*/25);
mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
diff --git a/services/inputflinger/tests/LatencyTracker_test.cpp b/services/inputflinger/tests/LatencyTracker_test.cpp
index 1f4b248..fa149db 100644
--- a/services/inputflinger/tests/LatencyTracker_test.cpp
+++ b/services/inputflinger/tests/LatencyTracker_test.cpp
@@ -36,10 +36,10 @@
InputEventTimeline getTestTimeline() {
InputEventTimeline t(
- /*isDown*/ true,
- /*eventTime*/ 2,
- /*readTime*/ 3);
- ConnectionTimeline expectedCT(/*deliveryTime*/ 6, /* consumeTime*/ 7, /*finishTime*/ 8);
+ /*isDown=*/true,
+ /*eventTime=*/2,
+ /*readTime=*/3);
+ ConnectionTimeline expectedCT(/*deliveryTime=*/6, /*consumeTime=*/7, /*finishTime=*/8);
std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 9;
graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 10;
@@ -87,7 +87,8 @@
void LatencyTrackerTest::triggerEventReporting(nsecs_t lastEventTime) {
const nsecs_t triggerEventTime =
lastEventTime + std::chrono::nanoseconds(ANR_TIMEOUT).count() + 1;
- mTracker->trackListener(1 /*inputEventId*/, true /*isDown*/, triggerEventTime, 3 /*readTime*/);
+ mTracker->trackListener(/*inputEventId=*/1, /*isDown=*/true, triggerEventTime,
+ /*readTime=*/3);
}
void LatencyTrackerTest::assertReceivedTimeline(const InputEventTimeline& timeline) {
@@ -136,8 +137,9 @@
* any additional ConnectionTimeline's.
*/
TEST_F(LatencyTrackerTest, TrackListener_DoesNotTriggerReporting) {
- mTracker->trackListener(1 /*inputEventId*/, false /*isDown*/, 2 /*eventTime*/, 3 /*readTime*/);
- triggerEventReporting(2 /*eventTime*/);
+ mTracker->trackListener(/*inputEventId=*/1, /*isDown=*/false, /*eventTime=*/2,
+ /*readTime=*/3);
+ triggerEventReporting(/*eventTime=*/2);
assertReceivedTimeline(InputEventTimeline{false, 2, 3});
}
@@ -145,9 +147,9 @@
* A single call to trackFinishedEvent should not cause a timeline to be reported.
*/
TEST_F(LatencyTrackerTest, TrackFinishedEvent_DoesNotTriggerReporting) {
- mTracker->trackFinishedEvent(1 /*inputEventId*/, connection1, 2 /*deliveryTime*/,
- 3 /*consumeTime*/, 4 /*finishTime*/);
- triggerEventReporting(4 /*eventTime*/);
+ mTracker->trackFinishedEvent(/*inputEventId=*/1, connection1, /*deliveryTime=*/2,
+ /*consumeTime=*/3, /*finishTime=*/4);
+ triggerEventReporting(/*eventTime=*/4);
assertReceivedTimelines({});
}
@@ -158,8 +160,8 @@
std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 2;
graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 3;
- mTracker->trackGraphicsLatency(1 /*inputEventId*/, connection2, graphicsTimeline);
- triggerEventReporting(3 /*eventTime*/);
+ mTracker->trackGraphicsLatency(/*inputEventId=*/1, connection2, graphicsTimeline);
+ triggerEventReporting(/*eventTime=*/3);
assertReceivedTimelines({});
}
@@ -189,10 +191,10 @@
// In the following 2 calls to trackListener, the inputEventId's are the same, but event times
// are different.
- mTracker->trackListener(inputEventId, isDown, 1 /*eventTime*/, readTime);
- mTracker->trackListener(inputEventId, isDown, 2 /*eventTime*/, readTime);
+ mTracker->trackListener(inputEventId, isDown, /*eventTime=*/1, readTime);
+ mTracker->trackListener(inputEventId, isDown, /*eventTime=*/2, readTime);
- triggerEventReporting(2 /*eventTime*/);
+ triggerEventReporting(/*eventTime=*/2);
// Since we sent duplicate input events, the tracker should just delete all of them, because it
// does not have enough information to properly track them.
assertReceivedTimelines({});
@@ -215,13 +217,13 @@
constexpr int32_t inputEventId2 = 10;
InputEventTimeline timeline2(
- /*isDown*/ false,
- /*eventTime*/ 20,
- /*readTime*/ 30);
+ /*isDown=*/false,
+ /*eventTime=*/20,
+ /*readTime=*/30);
timeline2.connectionTimelines.emplace(connection2,
- ConnectionTimeline(/*deliveryTime*/ 60,
- /*consumeTime*/ 70,
- /*finishTime*/ 80));
+ ConnectionTimeline(/*deliveryTime=*/60,
+ /*consumeTime=*/70,
+ /*finishTime=*/80));
ConnectionTimeline& connectionTimeline2 = timeline2.connectionTimelines.begin()->second;
std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline2;
graphicsTimeline2[GraphicsTimeline::GPU_COMPLETED_TIME] = 90;
@@ -258,15 +260,15 @@
const sp<IBinder>& token = timeline.connectionTimelines.begin()->first;
for (size_t i = 1; i <= 100; i++) {
- mTracker->trackListener(i /*inputEventId*/, timeline.isDown, timeline.eventTime,
+ mTracker->trackListener(/*inputEventId=*/i, timeline.isDown, timeline.eventTime,
timeline.readTime);
expectedTimelines.push_back(
InputEventTimeline{timeline.isDown, timeline.eventTime, timeline.readTime});
}
// Now, complete the first event that was sent.
- mTracker->trackFinishedEvent(1 /*inputEventId*/, token, expectedCT.deliveryTime,
+ mTracker->trackFinishedEvent(/*inputEventId=*/1, token, expectedCT.deliveryTime,
expectedCT.consumeTime, expectedCT.finishTime);
- mTracker->trackGraphicsLatency(1 /*inputEventId*/, token, expectedCT.graphicsTimeline);
+ mTracker->trackGraphicsLatency(/*inputEventId=*/1, token, expectedCT.graphicsTimeline);
expectedTimelines[0].connectionTimelines.emplace(token, std::move(expectedCT));
triggerEventReporting(timeline.eventTime);
diff --git a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
index 7265362..9014dfb 100644
--- a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
+++ b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
@@ -64,13 +64,13 @@
}
// Define a valid motion event.
- NotifyMotionArgs args(/* id */ 0, eventTime, 0 /*readTime*/, deviceId, source, 0 /*displayId*/,
+ NotifyMotionArgs args(/*id=*/0, eventTime, /*readTime=*/0, deviceId, source, /*displayId=*/0,
POLICY_FLAG_PASS_TO_USER, action, /* actionButton */ 0,
- /* flags */ 0, AMETA_NONE, /* buttonState */ 0,
- MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
- pointerProperties, pointerCoords, /* xPrecision */ 0, /* yPrecision */ 0,
+ /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE,
+ AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties,
+ pointerCoords, /*xPrecision=*/0, /*yPrecision=*/0,
AMOTION_EVENT_INVALID_CURSOR_POSITION,
- AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, /* videoFrames */ {});
+ AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, /*videoFrames=*/{});
return args;
}
@@ -109,26 +109,26 @@
TEST_F(PreferStylusOverTouchTest, TouchGestureIsNotBlocked) {
NotifyMotionArgs args;
- args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+ args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2}}, TOUCHSCREEN);
assertNotBlocked(args);
- args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
+ args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 3}}, TOUCHSCREEN);
assertNotBlocked(args);
- args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
+ args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{1, 3}}, TOUCHSCREEN);
assertNotBlocked(args);
}
TEST_F(PreferStylusOverTouchTest, StylusGestureIsNotBlocked) {
NotifyMotionArgs args;
- args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, STYLUS);
+ args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2}}, STYLUS);
assertNotBlocked(args);
- args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, STYLUS);
+ args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 3}}, STYLUS);
assertNotBlocked(args);
- args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, STYLUS);
+ args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{1, 3}}, STYLUS);
assertNotBlocked(args);
}
@@ -139,24 +139,24 @@
TEST_F(PreferStylusOverTouchTest, TouchIsCanceledWhenStylusGoesDown) {
NotifyMotionArgs args;
- args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+ args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2}}, TOUCHSCREEN);
assertNotBlocked(args);
- args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
+ args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 3}}, TOUCHSCREEN);
assertNotBlocked(args);
- args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
+ args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 30}}, STYLUS);
NotifyMotionArgs cancelArgs =
- generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, CANCEL, {{1, 3}}, TOUCHSCREEN);
+ generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, CANCEL, {{1, 3}}, TOUCHSCREEN);
cancelArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
assertResponse(args, {cancelArgs, args});
// Both stylus and touch events continue. Stylus should be not blocked, and touch should be
// blocked
- args = generateMotionArgs(3 /*downTime*/, 4 /*eventTime*/, MOVE, {{10, 31}}, STYLUS);
+ args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/4, MOVE, {{10, 31}}, STYLUS);
assertNotBlocked(args);
- args = generateMotionArgs(0 /*downTime*/, 5 /*eventTime*/, MOVE, {{1, 4}}, TOUCHSCREEN);
+ args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/5, MOVE, {{1, 4}}, TOUCHSCREEN);
assertDropped(args);
}
@@ -166,17 +166,17 @@
TEST_F(PreferStylusOverTouchTest, StylusDownAfterTouch) {
NotifyMotionArgs args;
- args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+ args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2}}, TOUCHSCREEN);
assertNotBlocked(args);
- args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
+ args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 3}}, TOUCHSCREEN);
assertNotBlocked(args);
- args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
+ args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{1, 3}}, TOUCHSCREEN);
assertNotBlocked(args);
// Stylus goes down
- args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
+ args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 30}}, STYLUS);
assertNotBlocked(args);
}
@@ -189,21 +189,21 @@
constexpr nsecs_t stylusDownTime = 0;
constexpr nsecs_t touchDownTime = 1;
- args = generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
+ args = generateMotionArgs(stylusDownTime, /*eventTime=*/0, DOWN, {{10, 30}}, STYLUS);
assertNotBlocked(args);
- args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+ args = generateMotionArgs(touchDownTime, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN);
assertDropped(args);
// Stylus should continue to work
- args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, MOVE, {{10, 31}}, STYLUS);
+ args = generateMotionArgs(stylusDownTime, /*eventTime=*/2, MOVE, {{10, 31}}, STYLUS);
assertNotBlocked(args);
// Touch should continue to be blocked
- args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
+ args = generateMotionArgs(touchDownTime, /*eventTime=*/1, MOVE, {{1, 3}}, TOUCHSCREEN);
assertDropped(args);
- args = generateMotionArgs(0 /*downTime*/, 5 /*eventTime*/, MOVE, {{1, 4}}, TOUCHSCREEN);
+ args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/5, MOVE, {{1, 4}}, TOUCHSCREEN);
assertDropped(args);
}
@@ -217,23 +217,23 @@
constexpr nsecs_t touchDownTime = 4;
// Stylus goes down and up
- args = generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
+ args = generateMotionArgs(stylusDownTime, /*eventTime=*/0, DOWN, {{10, 30}}, STYLUS);
assertNotBlocked(args);
- args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, MOVE, {{10, 31}}, STYLUS);
+ args = generateMotionArgs(stylusDownTime, /*eventTime=*/2, MOVE, {{10, 31}}, STYLUS);
assertNotBlocked(args);
- args = generateMotionArgs(stylusDownTime, 3 /*eventTime*/, UP, {{10, 31}}, STYLUS);
+ args = generateMotionArgs(stylusDownTime, /*eventTime=*/3, UP, {{10, 31}}, STYLUS);
assertNotBlocked(args);
// New touch goes down. It should not be blocked
args = generateMotionArgs(touchDownTime, touchDownTime, DOWN, {{1, 2}}, TOUCHSCREEN);
assertNotBlocked(args);
- args = generateMotionArgs(touchDownTime, 5 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
+ args = generateMotionArgs(touchDownTime, /*eventTime=*/5, MOVE, {{1, 3}}, TOUCHSCREEN);
assertNotBlocked(args);
- args = generateMotionArgs(touchDownTime, 6 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
+ args = generateMotionArgs(touchDownTime, /*eventTime=*/6, UP, {{1, 3}}, TOUCHSCREEN);
assertNotBlocked(args);
}
@@ -246,25 +246,25 @@
constexpr nsecs_t stylusDownTime = 0;
constexpr nsecs_t touchDownTime = 1;
- assertNotBlocked(generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS));
+ assertNotBlocked(generateMotionArgs(stylusDownTime, /*eventTime=*/0, DOWN, {{10, 30}}, STYLUS));
- args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+ args = generateMotionArgs(touchDownTime, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN);
assertDropped(args);
// Lift the stylus
- args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, UP, {{10, 30}}, STYLUS);
+ args = generateMotionArgs(stylusDownTime, /*eventTime=*/2, UP, {{10, 30}}, STYLUS);
assertNotBlocked(args);
// Touch should continue to be blocked
- args = generateMotionArgs(touchDownTime, 3 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
+ args = generateMotionArgs(touchDownTime, /*eventTime=*/3, MOVE, {{1, 3}}, TOUCHSCREEN);
assertDropped(args);
- args = generateMotionArgs(touchDownTime, 4 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
+ args = generateMotionArgs(touchDownTime, /*eventTime=*/4, UP, {{1, 3}}, TOUCHSCREEN);
assertDropped(args);
// New touch should go through, though.
constexpr nsecs_t newTouchDownTime = 5;
- args = generateMotionArgs(newTouchDownTime, 5 /*eventTime*/, DOWN, {{10, 20}}, TOUCHSCREEN);
+ args = generateMotionArgs(newTouchDownTime, /*eventTime=*/5, DOWN, {{10, 20}}, TOUCHSCREEN);
assertNotBlocked(args);
}
@@ -276,20 +276,20 @@
NotifyMotionArgs args;
// Event from a stylus device, but with finger tool type
- args = generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, STYLUS);
+ args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{1, 2}}, STYLUS);
// Keep source stylus, but make the tool type touch
args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
assertNotBlocked(args);
// Second pointer (stylus pointer) goes down, from the same device
- args = generateMotionArgs(1 /*downTime*/, 2 /*eventTime*/, POINTER_1_DOWN, {{1, 2}, {10, 20}},
+ args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/2, POINTER_1_DOWN, {{1, 2}, {10, 20}},
STYLUS);
// Keep source stylus, but make the tool type touch
args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
assertNotBlocked(args);
// Second pointer (stylus pointer) goes down, from the same device
- args = generateMotionArgs(1 /*downTime*/, 3 /*eventTime*/, MOVE, {{2, 3}, {11, 21}}, STYLUS);
+ args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/3, MOVE, {{2, 3}, {11, 21}}, STYLUS);
// Keep source stylus, but make the tool type touch
args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
assertNotBlocked(args);
@@ -300,16 +300,16 @@
*/
TEST_F(PreferStylusOverTouchTest, TouchFromTwoDevicesAndStylus) {
NotifyMotionArgs touch1Down =
- generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+ generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN);
assertNotBlocked(touch1Down);
NotifyMotionArgs touch2Down =
- generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{3, 4}}, TOUCHSCREEN);
+ generateMotionArgs(/*downTime=*/2, /*eventTime=*/2, DOWN, {{3, 4}}, TOUCHSCREEN);
touch2Down.deviceId = SECOND_TOUCH_DEVICE_ID;
assertNotBlocked(touch2Down);
NotifyMotionArgs stylusDown =
- generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
+ generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 30}}, STYLUS);
NotifyMotionArgs cancelArgs1 = touch1Down;
cancelArgs1.action = CANCEL;
cancelArgs1.flags |= AMOTION_EVENT_FLAG_CANCELED;
@@ -328,12 +328,12 @@
TEST_F(PreferStylusOverTouchTest, AllTouchMustLiftAfterCanceledByStylus) {
// First device touches down
NotifyMotionArgs touch1Down =
- generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+ generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN);
assertNotBlocked(touch1Down);
// Stylus goes down - touch should be canceled
NotifyMotionArgs stylusDown =
- generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
+ generateMotionArgs(/*downTime=*/2, /*eventTime=*/2, DOWN, {{10, 30}}, STYLUS);
NotifyMotionArgs cancelArgs1 = touch1Down;
cancelArgs1.action = CANCEL;
cancelArgs1.flags |= AMOTION_EVENT_FLAG_CANCELED;
@@ -341,44 +341,44 @@
// Stylus goes up
NotifyMotionArgs stylusUp =
- generateMotionArgs(2 /*downTime*/, 3 /*eventTime*/, UP, {{10, 30}}, STYLUS);
+ generateMotionArgs(/*downTime=*/2, /*eventTime=*/3, UP, {{10, 30}}, STYLUS);
assertNotBlocked(stylusUp);
// Touch from the first device remains blocked
NotifyMotionArgs touch1Move =
- generateMotionArgs(1 /*downTime*/, 4 /*eventTime*/, MOVE, {{2, 3}}, TOUCHSCREEN);
+ generateMotionArgs(/*downTime=*/1, /*eventTime=*/4, MOVE, {{2, 3}}, TOUCHSCREEN);
assertDropped(touch1Move);
// Second touch goes down. It should not be blocked because stylus has already lifted.
NotifyMotionArgs touch2Down =
- generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{31, 32}}, TOUCHSCREEN);
+ generateMotionArgs(/*downTime=*/5, /*eventTime=*/5, DOWN, {{31, 32}}, TOUCHSCREEN);
touch2Down.deviceId = SECOND_TOUCH_DEVICE_ID;
assertNotBlocked(touch2Down);
// First device is lifted up. It's already been canceled, so the UP event should be dropped.
NotifyMotionArgs touch1Up =
- generateMotionArgs(1 /*downTime*/, 6 /*eventTime*/, UP, {{2, 3}}, TOUCHSCREEN);
+ generateMotionArgs(/*downTime=*/1, /*eventTime=*/6, UP, {{2, 3}}, TOUCHSCREEN);
assertDropped(touch1Up);
// Touch from second device touch should continue to work
NotifyMotionArgs touch2Move =
- generateMotionArgs(5 /*downTime*/, 7 /*eventTime*/, MOVE, {{32, 33}}, TOUCHSCREEN);
+ generateMotionArgs(/*downTime=*/5, /*eventTime=*/7, MOVE, {{32, 33}}, TOUCHSCREEN);
touch2Move.deviceId = SECOND_TOUCH_DEVICE_ID;
assertNotBlocked(touch2Move);
// Second touch lifts up
NotifyMotionArgs touch2Up =
- generateMotionArgs(5 /*downTime*/, 8 /*eventTime*/, UP, {{32, 33}}, TOUCHSCREEN);
+ generateMotionArgs(/*downTime=*/5, /*eventTime=*/8, UP, {{32, 33}}, TOUCHSCREEN);
touch2Up.deviceId = SECOND_TOUCH_DEVICE_ID;
assertNotBlocked(touch2Up);
// Now that all touch has been lifted, new touch from either first or second device should work
NotifyMotionArgs touch3Down =
- generateMotionArgs(9 /*downTime*/, 9 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+ generateMotionArgs(/*downTime=*/9, /*eventTime=*/9, DOWN, {{1, 2}}, TOUCHSCREEN);
assertNotBlocked(touch3Down);
NotifyMotionArgs touch4Down =
- generateMotionArgs(10 /*downTime*/, 10 /*eventTime*/, DOWN, {{100, 200}}, TOUCHSCREEN);
+ generateMotionArgs(/*downTime=*/10, /*eventTime=*/10, DOWN, {{100, 200}}, TOUCHSCREEN);
touch4Down.deviceId = SECOND_TOUCH_DEVICE_ID;
assertNotBlocked(touch4Down);
}
@@ -403,27 +403,27 @@
TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchDeviceIsCanceledAtFirst) {
// Touch from device 1 goes down
NotifyMotionArgs touchDown =
- generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+ generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN);
touchDown.source = STYLUS;
assertNotBlocked(touchDown);
// Stylus from device 2 goes down. Touch should be canceled.
NotifyMotionArgs args =
- generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{10, 20}}, STYLUS);
+ generateMotionArgs(/*downTime=*/2, /*eventTime=*/2, DOWN, {{10, 20}}, STYLUS);
NotifyMotionArgs cancelTouchArgs = touchDown;
cancelTouchArgs.action = CANCEL;
cancelTouchArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
assertResponse(args, {cancelTouchArgs, args});
// Introduce a stylus pointer into the device 1 stream. It should be ignored.
- args = generateMotionArgs(1 /*downTime*/, 3 /*eventTime*/, POINTER_1_DOWN, {{1, 2}, {3, 4}},
+ args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/3, POINTER_1_DOWN, {{1, 2}, {3, 4}},
TOUCHSCREEN);
args.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
args.source = STYLUS;
assertDropped(args);
// Lift up touch from the mixed touch/stylus device
- args = generateMotionArgs(1 /*downTime*/, 4 /*eventTime*/, CANCEL, {{1, 2}, {3, 4}},
+ args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/4, CANCEL, {{1, 2}, {3, 4}},
TOUCHSCREEN);
args.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
args.source = STYLUS;
@@ -431,19 +431,19 @@
// Stylus from device 2 is still down. Since the device 1 is now identified as a mixed
// touch/stylus device, its events should go through, even if they are touch.
- args = generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{21, 22}}, TOUCHSCREEN);
+ args = generateMotionArgs(/*downTime=*/5, /*eventTime=*/5, DOWN, {{21, 22}}, TOUCHSCREEN);
touchDown.source = STYLUS;
assertResponse(args, {args});
// Reconfigure such that only the stylus device remains
InputDeviceInfo stylusDevice;
- stylusDevice.initialize(STYLUS_DEVICE_ID, 1 /*generation*/, 1 /*controllerNumber*/,
- {} /*identifier*/, "stylus device", false /*external*/,
- false /*hasMic*/, ADISPLAY_ID_NONE);
+ stylusDevice.initialize(STYLUS_DEVICE_ID, /*generation=*/1, /*controllerNumber=*/1,
+ /*identifier=*/{}, "stylus device", /*external=*/false,
+ /*hasMic=*/false, ADISPLAY_ID_NONE);
notifyInputDevicesChanged({stylusDevice});
// The touchscreen device was removed, so we no longer remember anything about it. We should
// again start blocking touch events from it.
- args = generateMotionArgs(6 /*downTime*/, 6 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+ args = generateMotionArgs(/*downTime=*/6, /*eventTime=*/6, DOWN, {{1, 2}}, TOUCHSCREEN);
args.source = STYLUS;
assertDropped(args);
}
@@ -456,41 +456,41 @@
NotifyMotionArgs args;
// First stylus is down
- assertNotBlocked(generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS));
+ assertNotBlocked(generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{10, 30}}, STYLUS));
// Second stylus is down
- args = generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{20, 40}}, STYLUS);
+ args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{20, 40}}, STYLUS);
args.deviceId = SECOND_STYLUS_DEVICE_ID;
assertNotBlocked(args);
// Touch goes down. It should be ignored.
- args = generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+ args = generateMotionArgs(/*downTime=*/2, /*eventTime=*/2, DOWN, {{1, 2}}, TOUCHSCREEN);
assertDropped(args);
// Lift the first stylus
- args = generateMotionArgs(0 /*downTime*/, 3 /*eventTime*/, UP, {{10, 30}}, STYLUS);
+ args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/3, UP, {{10, 30}}, STYLUS);
assertNotBlocked(args);
// Touch should continue to be blocked
- args = generateMotionArgs(2 /*downTime*/, 4 /*eventTime*/, UP, {{1, 2}}, TOUCHSCREEN);
+ args = generateMotionArgs(/*downTime=*/2, /*eventTime=*/4, UP, {{1, 2}}, TOUCHSCREEN);
assertDropped(args);
// New touch should be blocked because second stylus is still down
- args = generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{5, 6}}, TOUCHSCREEN);
+ args = generateMotionArgs(/*downTime=*/5, /*eventTime=*/5, DOWN, {{5, 6}}, TOUCHSCREEN);
assertDropped(args);
// Second stylus goes up
- args = generateMotionArgs(1 /*downTime*/, 6 /*eventTime*/, UP, {{20, 40}}, STYLUS);
+ args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/6, UP, {{20, 40}}, STYLUS);
args.deviceId = SECOND_STYLUS_DEVICE_ID;
assertNotBlocked(args);
// Current touch gesture should continue to be blocked
// Touch should continue to be blocked
- args = generateMotionArgs(5 /*downTime*/, 7 /*eventTime*/, UP, {{5, 6}}, TOUCHSCREEN);
+ args = generateMotionArgs(/*downTime=*/5, /*eventTime=*/7, UP, {{5, 6}}, TOUCHSCREEN);
assertDropped(args);
// Now that all styli were lifted, new touch should go through
- args = generateMotionArgs(8 /*downTime*/, 8 /*eventTime*/, DOWN, {{7, 8}}, TOUCHSCREEN);
+ args = generateMotionArgs(/*downTime=*/8, /*eventTime=*/8, DOWN, {{7, 8}}, TOUCHSCREEN);
assertNotBlocked(args);
}
diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
index e12f88e..3f749b1 100644
--- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
+++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
@@ -88,8 +88,8 @@
}
// Define a valid motion event.
- NotifyMotionArgs args(/* id */ 0, eventTime, 0 /*readTime*/, DEVICE_ID,
- AINPUT_SOURCE_TOUCHSCREEN, 0 /*displayId*/, POLICY_FLAG_PASS_TO_USER,
+ NotifyMotionArgs args(/* id */ 0, eventTime, /*readTime=*/0, DEVICE_ID,
+ AINPUT_SOURCE_TOUCHSCREEN, /*displayId=*/0, POLICY_FLAG_PASS_TO_USER,
action, /* actionButton */ 0,
/* flags */ 0, AMETA_NONE, /* buttonState */ 0,
MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
@@ -409,7 +409,7 @@
void SetUp() override {
mBlocker = std::make_unique<UnwantedInteractionBlocker>(mTestListener,
- /*enablePalmRejection*/ true);
+ /*enablePalmRejection=*/true);
}
};
@@ -419,7 +419,7 @@
*/
TEST_F(UnwantedInteractionBlockerTest, ConfigurationChangedIsPassedToNextListener) {
// Create a basic configuration change and send to blocker
- NotifyConfigurationChangedArgs args(1 /*sequenceNum*/, 2 /*eventTime*/);
+ NotifyConfigurationChangedArgs args(/*sequenceNum=*/1, /*eventTime=*/2);
mBlocker->notifyConfigurationChanged(&args);
NotifyConfigurationChangedArgs outArgs;
@@ -433,10 +433,10 @@
*/
TEST_F(UnwantedInteractionBlockerTest, KeyIsPassedToNextListener) {
// Create a basic key event and send to blocker
- NotifyKeyArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 21 /*readTime*/, 3 /*deviceId*/,
- AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, 0 /*policyFlags*/,
- AKEY_EVENT_ACTION_DOWN, 4 /*flags*/, AKEYCODE_HOME, 5 /*scanCode*/,
- AMETA_NONE, 6 /*downTime*/);
+ NotifyKeyArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*readTime=*/21, /*deviceId=*/3,
+ AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, /*policyFlags=*/0,
+ AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5,
+ AMETA_NONE, /*downTime=*/6);
mBlocker->notifyKey(&args);
NotifyKeyArgs outArgs;
@@ -451,7 +451,7 @@
*/
TEST_F(UnwantedInteractionBlockerTest, DownEventIsPassedToNextListener) {
NotifyMotionArgs motionArgs =
- generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}});
+ generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
mBlocker->notifyMotion(&motionArgs);
NotifyMotionArgs args;
ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyMotionWasCalled(&args));
@@ -463,8 +463,8 @@
* Expect that the event is received by the next input stage, unmodified.
*/
TEST_F(UnwantedInteractionBlockerTest, SwitchIsPassedToNextListener) {
- NotifySwitchArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 3 /*policyFlags*/, 4 /*switchValues*/,
- 5 /*switchMask*/);
+ NotifySwitchArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*policyFlags=*/3,
+ /*switchValues=*/4, /*switchMask=*/5);
mBlocker->notifySwitch(&args);
NotifySwitchArgs outArgs;
@@ -477,7 +477,7 @@
* Expect that the event is received by the next input stage, unmodified.
*/
TEST_F(UnwantedInteractionBlockerTest, DeviceResetIsPassedToNextListener) {
- NotifyDeviceResetArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, DEVICE_ID);
+ NotifyDeviceResetArgs args(/*sequenceNum=*/1, /*eventTime=*/2, DEVICE_ID);
mBlocker->notifyDeviceReset(&args);
NotifyDeviceResetArgs outArgs;
@@ -494,19 +494,19 @@
NotifyMotionArgs args;
mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
mBlocker->notifyMotion(
- &(args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}})));
+ &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}})));
mBlocker->notifyMotion(
- &(args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, MOVE, {{4, 5, 6}})));
- NotifyDeviceResetArgs resetArgs(1 /*sequenceNum*/, 3 /*eventTime*/, DEVICE_ID);
+ &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}})));
+ NotifyDeviceResetArgs resetArgs(/*sequenceNum=*/1, /*eventTime=*/3, DEVICE_ID);
mBlocker->notifyDeviceReset(&resetArgs);
// Start a new gesture with a DOWN event, even though the previous event stream was incomplete.
mBlocker->notifyMotion(
- &(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/, DOWN, {{7, 8, 9}})));
+ &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, DOWN, {{7, 8, 9}})));
}
TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenStylusSourceWithFingerToolIsReceived) {
mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
- NotifyMotionArgs args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}});
+ NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}});
args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
args.source = AINPUT_SOURCE_STYLUS;
mBlocker->notifyMotion(&args);
@@ -520,9 +520,9 @@
NotifyMotionArgs args;
mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
mBlocker->notifyMotion(
- &(args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}})));
+ &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}})));
mBlocker->notifyMotion(
- &(args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, MOVE, {{4, 5, 6}})));
+ &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}})));
// Now pretend the device changed, even though nothing is different for DEVICE_ID in practice.
mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
@@ -530,7 +530,7 @@
// The MOVE event continues the gesture that started before 'devices changed', so it should not
// cause a crash.
mBlocker->notifyMotion(
- &(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/, MOVE, {{7, 8, 9}})));
+ &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, MOVE, {{7, 8, 9}})));
}
/**
@@ -539,23 +539,23 @@
TEST_F(UnwantedInteractionBlockerTest, StylusAfterTouchWorks) {
NotifyMotionArgs args;
mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
- args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}});
+ args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
mBlocker->notifyMotion(&args);
- args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{4, 5, 6}});
+ args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{4, 5, 6}});
mBlocker->notifyMotion(&args);
- args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{4, 5, 6}});
+ args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{4, 5, 6}});
mBlocker->notifyMotion(&args);
// Now touch down stylus
- args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 20, 30}});
+ args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 20, 30}});
args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
args.source |= AINPUT_SOURCE_STYLUS;
mBlocker->notifyMotion(&args);
- args = generateMotionArgs(3 /*downTime*/, 4 /*eventTime*/, MOVE, {{40, 50, 60}});
+ args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/4, MOVE, {{40, 50, 60}});
args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
args.source |= AINPUT_SOURCE_STYLUS;
mBlocker->notifyMotion(&args);
- args = generateMotionArgs(3 /*downTime*/, 5 /*eventTime*/, UP, {{40, 50, 60}});
+ args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/5, UP, {{40, 50, 60}});
args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
args.source |= AINPUT_SOURCE_STYLUS;
mBlocker->notifyMotion(&args);
@@ -569,15 +569,15 @@
*/
TEST_F(UnwantedInteractionBlockerTest, DumpCanBeAccessedOnAnotherThread) {
mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
- NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}});
+ NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
mBlocker->notifyMotion(&args1);
std::thread dumpThread([this]() {
std::string dump;
mBlocker->dump(dump);
});
- NotifyMotionArgs args2 = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{4, 5, 6}});
+ NotifyMotionArgs args2 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{4, 5, 6}});
mBlocker->notifyMotion(&args2);
- NotifyMotionArgs args3 = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{4, 5, 6}});
+ NotifyMotionArgs args3 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{4, 5, 6}});
mBlocker->notifyMotion(&args3);
dumpThread.join();
}
@@ -589,19 +589,19 @@
TEST_F(UnwantedInteractionBlockerTest, HeuristicFilterWorks) {
mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
// Small touch down
- NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}});
+ NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
mBlocker->notifyMotion(&args1);
mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN));
// Large touch oval on the next move
NotifyMotionArgs args2 =
- generateMotionArgs(0 /*downTime*/, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}});
+ generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}});
mBlocker->notifyMotion(&args2);
mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE));
// Lift up the touch to force the model to decide on whether it's a palm
NotifyMotionArgs args3 =
- generateMotionArgs(0 /*downTime*/, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}});
+ generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}});
mBlocker->notifyMotion(&args3);
mTestListener.assertNotifyMotionWasCalled(WithMotionAction(CANCEL));
}
@@ -616,14 +616,14 @@
InputDeviceInfo info = generateTestDeviceInfo();
info.addSource(AINPUT_SOURCE_STYLUS);
mBlocker->notifyInputDevicesChanged({info});
- NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}});
+ NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
args1.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
mBlocker->notifyMotion(&args1);
mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN));
// Move the stylus, setting large TOUCH_MAJOR/TOUCH_MINOR dimensions
NotifyMotionArgs args2 =
- generateMotionArgs(0 /*downTime*/, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}});
+ generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}});
args2.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
mBlocker->notifyMotion(&args2);
mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE));
@@ -631,7 +631,7 @@
// Lift up the stylus. If it were a touch event, this would force the model to decide on whether
// it's a palm.
NotifyMotionArgs args3 =
- generateMotionArgs(0 /*downTime*/, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}});
+ generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}});
args3.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
mBlocker->notifyMotion(&args3);
mTestListener.assertNotifyMotionWasCalled(WithMotionAction(UP));
@@ -648,26 +648,26 @@
mBlocker->notifyInputDevicesChanged({info});
// Touch down
- NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}});
+ NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
mBlocker->notifyMotion(&args1);
mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN));
// Stylus pointer down
- NotifyMotionArgs args2 = generateMotionArgs(0 /*downTime*/, RESAMPLE_PERIOD, POINTER_1_DOWN,
+ NotifyMotionArgs args2 = generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, POINTER_1_DOWN,
{{1, 2, 3}, {10, 20, 30}});
args2.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
mBlocker->notifyMotion(&args2);
mTestListener.assertNotifyMotionWasCalled(WithMotionAction(POINTER_1_DOWN));
// Large touch oval on the next finger move
- NotifyMotionArgs args3 = generateMotionArgs(0 /*downTime*/, 2 * RESAMPLE_PERIOD, MOVE,
+ NotifyMotionArgs args3 = generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, MOVE,
{{1, 2, 300}, {11, 21, 30}});
args3.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
mBlocker->notifyMotion(&args3);
mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE));
// Lift up the finger pointer. It should be canceled due to the heuristic filter.
- NotifyMotionArgs args4 = generateMotionArgs(0 /*downTime*/, 3 * RESAMPLE_PERIOD, POINTER_0_UP,
+ NotifyMotionArgs args4 = generateMotionArgs(/*downTime=*/0, 3 * RESAMPLE_PERIOD, POINTER_0_UP,
{{1, 2, 300}, {11, 21, 30}});
args4.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
mBlocker->notifyMotion(&args4);
@@ -675,7 +675,7 @@
AllOf(WithMotionAction(POINTER_0_UP), WithFlags(FLAG_CANCELED)));
NotifyMotionArgs args5 =
- generateMotionArgs(0 /*downTime*/, 4 * RESAMPLE_PERIOD, MOVE, {{12, 22, 30}});
+ generateMotionArgs(/*downTime=*/0, 4 * RESAMPLE_PERIOD, MOVE, {{12, 22, 30}});
args5.pointerProperties[0].id = args4.pointerProperties[1].id;
args5.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
mBlocker->notifyMotion(&args5);
@@ -683,7 +683,7 @@
// Lift up the stylus pointer
NotifyMotionArgs args6 =
- generateMotionArgs(0 /*downTime*/, 5 * RESAMPLE_PERIOD, UP, {{4, 5, 200}});
+ generateMotionArgs(/*downTime=*/0, 5 * RESAMPLE_PERIOD, UP, {{4, 5, 200}});
args6.pointerProperties[0].id = args4.pointerProperties[1].id;
args6.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
mBlocker->notifyMotion(&args6);
@@ -701,15 +701,15 @@
NotifyMotionArgs args;
mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
mBlocker->notifyMotion(
- &(args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}})));
+ &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}})));
mBlocker->notifyMotion(
- &(args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, MOVE, {{4, 5, 6}})));
- NotifyDeviceResetArgs resetArgs(1 /*sequenceNum*/, 3 /*eventTime*/, DEVICE_ID);
+ &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}})));
+ NotifyDeviceResetArgs resetArgs(/*sequenceNum=*/1, /*eventTime=*/3, DEVICE_ID);
mBlocker->notifyDeviceReset(&resetArgs);
// Sending MOVE without a DOWN -> should crash!
ASSERT_DEATH(
{
- mBlocker->notifyMotion(&(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/,
+ mBlocker->notifyMotion(&(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/4,
MOVE, {{7, 8, 9}})));
},
"Could not find slot");
@@ -720,7 +720,7 @@
*/
TEST_F(UnwantedInteractionBlockerTestDeathTest, WhenMoveWithoutDownCausesACrash) {
ScopedSilentDeath _silentDeath;
- NotifyMotionArgs args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 2, 3}});
+ NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 2, 3}});
mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
ASSERT_DEATH({ mBlocker->notifyMotion(&args); }, "Could not find slot");
}
@@ -741,7 +741,7 @@
ScopedSilentDeath _silentDeath;
constexpr nsecs_t downTime = 0;
NotifyMotionArgs args =
- generateMotionArgs(downTime, 2 /*eventTime*/, MOVE, {{1406.0, 650.0, 52.0}});
+ generateMotionArgs(downTime, /*eventTime=*/2, MOVE, {{1406.0, 650.0, 52.0}});
ASSERT_DEATH({ mPalmRejector->processMotion(args); }, "Could not find slot");
}
diff --git a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
index c407cff..2909129 100644
--- a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
@@ -37,22 +37,22 @@
const nsecs_t downTime = 2;
const nsecs_t readTime = downTime + fdp.ConsumeIntegralInRange<nsecs_t>(0, 1E8);
- NotifyMotionArgs motionArgs(fdp.ConsumeIntegral<uint32_t>() /*sequenceNum*/,
- downTime /*eventTime*/, readTime,
- fdp.ConsumeIntegral<int32_t>() /*deviceId*/, AINPUT_SOURCE_ANY,
+ NotifyMotionArgs motionArgs(/*sequenceNum=*/fdp.ConsumeIntegral<uint32_t>(),
+ /*eventTime=*/downTime, readTime,
+ /*deviceId=*/fdp.ConsumeIntegral<int32_t>(), AINPUT_SOURCE_ANY,
ADISPLAY_ID_DEFAULT,
- fdp.ConsumeIntegral<uint32_t>() /*policyFlags*/,
+ /*policyFlags=*/fdp.ConsumeIntegral<uint32_t>(),
AMOTION_EVENT_ACTION_DOWN,
- fdp.ConsumeIntegral<int32_t>() /*actionButton*/,
- fdp.ConsumeIntegral<int32_t>() /*flags*/, AMETA_NONE,
- fdp.ConsumeIntegral<int32_t>() /*buttonState*/,
+ /*actionButton=*/fdp.ConsumeIntegral<int32_t>(),
+ /*flags=*/fdp.ConsumeIntegral<int32_t>(), AMETA_NONE,
+ /*buttonState=*/fdp.ConsumeIntegral<int32_t>(),
MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE,
- 1 /*pointerCount*/, &properties, &coords,
- fdp.ConsumeFloatingPoint<float>() /*xPrecision*/,
- fdp.ConsumeFloatingPoint<float>() /*yPrecision*/,
+ /*pointerCount=*/1, &properties, &coords,
+ /*xPrecision=*/fdp.ConsumeFloatingPoint<float>(),
+ /*yPrecision=*/fdp.ConsumeFloatingPoint<float>(),
AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime,
- {} /*videoFrames*/);
+ /*videoFrames=*/{});
return motionArgs;
}
@@ -68,8 +68,8 @@
[&]() -> void {
// SendToNextStage_NotifyConfigurationChangedArgs
NotifyConfigurationChangedArgs
- args(fdp.ConsumeIntegral<uint32_t>() /*sequenceNum*/,
- fdp.ConsumeIntegral<nsecs_t>() /*eventTime*/);
+ args(/*sequenceNum=*/fdp.ConsumeIntegral<uint32_t>(),
+ /*eventTime=*/fdp.ConsumeIntegral<nsecs_t>());
mClassifier->notifyConfigurationChanged(&args);
},
[&]() -> void {
@@ -77,15 +77,15 @@
const nsecs_t eventTime = fdp.ConsumeIntegral<nsecs_t>();
const nsecs_t readTime =
eventTime + fdp.ConsumeIntegralInRange<nsecs_t>(0, 1E8);
- NotifyKeyArgs keyArgs(fdp.ConsumeIntegral<uint32_t>() /*sequenceNum*/,
+ NotifyKeyArgs keyArgs(/*sequenceNum=*/fdp.ConsumeIntegral<uint32_t>(),
eventTime, readTime,
- fdp.ConsumeIntegral<int32_t>() /*deviceId*/,
+ /*deviceId=*/fdp.ConsumeIntegral<int32_t>(),
AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT,
- fdp.ConsumeIntegral<uint32_t>() /*policyFlags*/,
+ /*policyFlags=*/fdp.ConsumeIntegral<uint32_t>(),
AKEY_EVENT_ACTION_DOWN,
- fdp.ConsumeIntegral<int32_t>() /*flags*/, AKEYCODE_HOME,
- fdp.ConsumeIntegral<int32_t>() /*scanCode*/, AMETA_NONE,
- fdp.ConsumeIntegral<nsecs_t>() /*downTime*/);
+ /*flags=*/fdp.ConsumeIntegral<int32_t>(), AKEYCODE_HOME,
+ /*scanCode=*/fdp.ConsumeIntegral<int32_t>(), AMETA_NONE,
+ /*downTime=*/fdp.ConsumeIntegral<nsecs_t>());
mClassifier->notifyKey(&keyArgs);
},
@@ -96,19 +96,20 @@
},
[&]() -> void {
// SendToNextStage_NotifySwitchArgs
- NotifySwitchArgs switchArgs(fdp.ConsumeIntegral<uint32_t>() /*sequenceNum*/,
- fdp.ConsumeIntegral<nsecs_t>() /*eventTime*/,
- fdp.ConsumeIntegral<uint32_t>() /*policyFlags*/,
- fdp.ConsumeIntegral<uint32_t>() /*switchValues*/,
- fdp.ConsumeIntegral<uint32_t>() /*switchMask*/);
+ NotifySwitchArgs switchArgs(/*sequenceNum=*/fdp.ConsumeIntegral<uint32_t>(),
+ /*eventTime=*/fdp.ConsumeIntegral<nsecs_t>(),
+ /*policyFlags=*/fdp.ConsumeIntegral<uint32_t>(),
+ /*switchValues=*/fdp.ConsumeIntegral<uint32_t>(),
+ /*switchMask=*/fdp.ConsumeIntegral<uint32_t>());
mClassifier->notifySwitch(&switchArgs);
},
[&]() -> void {
// SendToNextStage_NotifyDeviceResetArgs
- NotifyDeviceResetArgs resetArgs(fdp.ConsumeIntegral<uint32_t>() /*sequenceNum*/,
- fdp.ConsumeIntegral<nsecs_t>() /*eventTime*/,
- fdp.ConsumeIntegral<int32_t>() /*deviceId*/);
+ NotifyDeviceResetArgs resetArgs(
+ /*sequenceNum=*/fdp.ConsumeIntegral<uint32_t>(),
+ /*eventTime=*/fdp.ConsumeIntegral<nsecs_t>(),
+ /*deviceId=*/fdp.ConsumeIntegral<int32_t>());
mClassifier->notifyDeviceReset(&resetArgs);
},
diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
index a80839c..20242b1 100644
--- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
@@ -182,13 +182,13 @@
VibrationSequence pattern(patternCount);
for (size_t i = 0; i < patternCount; ++i) {
VibrationElement element(i);
- element.addChannel(fdp->ConsumeIntegral<int32_t>() /* vibratorId */,
- fdp->ConsumeIntegral<uint8_t>() /* amplitude */);
+ element.addChannel(/*vibratorId=*/fdp->ConsumeIntegral<int32_t>(),
+ /*amplitude=*/fdp->ConsumeIntegral<uint8_t>());
pattern.addElement(element);
}
reader->vibrate(fdp->ConsumeIntegral<int32_t>(), pattern,
- fdp->ConsumeIntegral<ssize_t>() /*repeat*/,
- fdp->ConsumeIntegral<int32_t>() /*token*/);
+ /*repeat=*/fdp->ConsumeIntegral<ssize_t>(),
+ /*token=*/fdp->ConsumeIntegral<int32_t>());
reader->start();
// Loop through mapper operations until randomness is exhausted.
diff --git a/services/sensorservice/SensorInterface.cpp b/services/sensorservice/SensorInterface.cpp
index 398cdf9..e9c8335 100644
--- a/services/sensorservice/SensorInterface.cpp
+++ b/services/sensorservice/SensorInterface.cpp
@@ -87,14 +87,15 @@
// ---------------------------------------------------------------------------
-RuntimeSensor::RuntimeSensor(const sensor_t& sensor, sp<StateChangeCallback> callback)
+RuntimeSensor::RuntimeSensor(const sensor_t& sensor, sp<SensorCallback> callback)
: BaseSensor(sensor), mCallback(std::move(callback)) {
}
status_t RuntimeSensor::activate(void*, bool enabled) {
if (enabled != mEnabled) {
mEnabled = enabled;
- mCallback->onStateChanged(mEnabled, mSamplingPeriodNs, mBatchReportLatencyNs);
+ return mCallback->onConfigurationChanged(mSensor.getHandle(), mEnabled, mSamplingPeriodNs,
+ mBatchReportLatencyNs);
}
return OK;
}
@@ -105,7 +106,8 @@
mSamplingPeriodNs = samplingPeriodNs;
mBatchReportLatencyNs = maxBatchReportLatencyNs;
if (mEnabled) {
- mCallback->onStateChanged(mEnabled, mSamplingPeriodNs, mBatchReportLatencyNs);
+ return mCallback->onConfigurationChanged(mSensor.getHandle(), mEnabled,
+ mSamplingPeriodNs, mBatchReportLatencyNs);
}
}
return OK;
@@ -115,7 +117,8 @@
if (mSamplingPeriodNs != ns) {
mSamplingPeriodNs = ns;
if (mEnabled) {
- mCallback->onStateChanged(mEnabled, mSamplingPeriodNs, mBatchReportLatencyNs);
+ return mCallback->onConfigurationChanged(mSensor.getHandle(), mEnabled,
+ mSamplingPeriodNs, mBatchReportLatencyNs);
}
}
return OK;
diff --git a/services/sensorservice/SensorInterface.h b/services/sensorservice/SensorInterface.h
index 5ee5e12..c446d61 100644
--- a/services/sensorservice/SensorInterface.h
+++ b/services/sensorservice/SensorInterface.h
@@ -108,12 +108,12 @@
public:
static constexpr int DEFAULT_DEVICE_ID = 0;
- class StateChangeCallback : public virtual RefBase {
+ class SensorCallback : public virtual RefBase {
public:
- virtual void onStateChanged(bool enabled, int64_t samplingPeriodNs,
- int64_t batchReportLatencyNs) = 0;
+ virtual status_t onConfigurationChanged(int handle, bool enabled, int64_t samplingPeriodNs,
+ int64_t batchReportLatencyNs) = 0;
};
- RuntimeSensor(const sensor_t& sensor, sp<StateChangeCallback> callback);
+ RuntimeSensor(const sensor_t& sensor, sp<SensorCallback> callback);
virtual status_t activate(void* ident, bool enabled) override;
virtual status_t batch(void* ident, int handle, int flags, int64_t samplingPeriodNs,
int64_t maxBatchReportLatencyNs) override;
@@ -125,7 +125,7 @@
bool mEnabled = false;
int64_t mSamplingPeriodNs = 0;
int64_t mBatchReportLatencyNs = 0;
- sp<StateChangeCallback> mCallback;
+ sp<SensorCallback> mCallback;
};
// ---------------------------------------------------------------------------
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 5c98614..3a0329c 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -116,16 +116,17 @@
return nextHandle++;
}
-class RuntimeSensorCallbackProxy : public RuntimeSensor::StateChangeCallback {
+class RuntimeSensorCallbackProxy : public RuntimeSensor::SensorCallback {
public:
- RuntimeSensorCallbackProxy(sp<SensorService::RuntimeSensorStateChangeCallback> callback)
+ RuntimeSensorCallbackProxy(sp<SensorService::RuntimeSensorCallback> callback)
: mCallback(std::move(callback)) {}
- void onStateChanged(bool enabled, int64_t samplingPeriodNs,
- int64_t batchReportLatencyNs) override {
- mCallback->onStateChanged(enabled, samplingPeriodNs, batchReportLatencyNs);
+ status_t onConfigurationChanged(int handle, bool enabled, int64_t samplingPeriodNs,
+ int64_t batchReportLatencyNs) override {
+ return mCallback->onConfigurationChanged(handle, enabled, samplingPeriodNs,
+ batchReportLatencyNs);
}
private:
- sp<SensorService::RuntimeSensorStateChangeCallback> mCallback;
+ sp<SensorService::RuntimeSensorCallback> mCallback;
};
} // namespace
@@ -166,7 +167,7 @@
}
int SensorService::registerRuntimeSensor(
- const sensor_t& sensor, int deviceId, sp<RuntimeSensorStateChangeCallback> callback) {
+ const sensor_t& sensor, int deviceId, sp<RuntimeSensorCallback> callback) {
int handle = 0;
while (handle == 0 || !mSensors.isNewHandle(handle)) {
handle = nextRuntimeSensorHandle();
@@ -179,7 +180,7 @@
ALOGI("Registering runtime sensor handle 0x%x, type %d, name %s",
handle, sensor.type, sensor.name);
- sp<RuntimeSensor::StateChangeCallback> runtimeSensorCallback(
+ sp<RuntimeSensor::SensorCallback> runtimeSensorCallback(
new RuntimeSensorCallbackProxy(std::move(callback)));
sensor_t runtimeSensor = sensor;
// force the handle to be consistent
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index 0798279..3f6a895 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -147,12 +147,13 @@
virtual void onProximityActive(bool isActive) = 0;
};
- class RuntimeSensorStateChangeCallback : public virtual RefBase {
+ class RuntimeSensorCallback : public virtual RefBase {
public:
// Note that the callback is invoked from an async thread and can interact with the
// SensorService directly.
- virtual void onStateChanged(bool enabled, int64_t samplingPeriodNanos,
- int64_t batchReportLatencyNanos) = 0;
+ virtual status_t onConfigurationChanged(int handle, bool enabled,
+ int64_t samplingPeriodNanos,
+ int64_t batchReportLatencyNanos) = 0;
};
static char const* getServiceName() ANDROID_API { return "sensorservice"; }
@@ -182,7 +183,7 @@
status_t removeProximityActiveListener(const sp<ProximityActiveListener>& callback) ANDROID_API;
int registerRuntimeSensor(const sensor_t& sensor, int deviceId,
- sp<RuntimeSensorStateChangeCallback> callback) ANDROID_API;
+ sp<RuntimeSensorCallback> callback) ANDROID_API;
status_t unregisterRuntimeSensor(int handle) ANDROID_API;
status_t sendRuntimeSensorEvent(const sensors_event_t& event) ANDROID_API;
diff --git a/services/stats/.clang-format b/services/stats/.clang-format
new file mode 100644
index 0000000..cead3a0
--- /dev/null
+++ b/services/stats/.clang-format
@@ -0,0 +1,17 @@
+BasedOnStyle: Google
+AllowShortIfStatementsOnASingleLine: true
+AllowShortFunctionsOnASingleLine: false
+AllowShortLoopsOnASingleLine: true
+BinPackArguments: true
+BinPackParameters: true
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+ContinuationIndentWidth: 8
+DerivePointerAlignment: false
+IndentWidth: 4
+PointerAlignment: Left
+TabWidth: 4
+AccessModifierOffset: -4
+IncludeCategories:
+ - Regex: '^"Log\.h"'
+ Priority: -1
diff --git a/services/stats/StatsAidl.cpp b/services/stats/StatsAidl.cpp
index 410a5af..0f01507 100644
--- a/services/stats/StatsAidl.cpp
+++ b/services/stats/StatsAidl.cpp
@@ -14,57 +14,130 @@
* limitations under the License.
*/
-#define DEBUG false // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#define LOG_TAG "StatsAidl"
-#include <log/log.h>
-#include <statslog.h>
+#define VLOG(...) \
+ if (DEBUG) ALOGD(__VA_ARGS__);
#include "StatsAidl.h"
+#include <log/log.h>
+#include <stats_annotations.h>
+#include <stats_event.h>
+#include <statslog.h>
+
+#include <unordered_map>
+
namespace aidl {
namespace android {
namespace frameworks {
namespace stats {
-StatsHal::StatsHal() {}
+template <typename E>
+constexpr typename std::underlying_type<E>::type to_underlying(E e) noexcept {
+ return static_cast<typename std::underlying_type<E>::type>(e);
+}
+
+StatsHal::StatsHal() {
+}
+
+bool write_annotation(AStatsEvent* event, const Annotation& annotation) {
+ switch (annotation.value.getTag()) {
+ case AnnotationValue::boolValue: {
+ AStatsEvent_addBoolAnnotation(event, to_underlying(annotation.annotationId),
+ annotation.value.get<AnnotationValue::boolValue>());
+ break;
+ }
+ case AnnotationValue::intValue: {
+ AStatsEvent_addInt32Annotation(event, to_underlying(annotation.annotationId),
+ annotation.value.get<AnnotationValue::intValue>());
+ break;
+ }
+ default: {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool write_atom_annotations(AStatsEvent* event,
+ const std::vector<std::optional<Annotation>>& annotations) {
+ for (const auto& atomAnnotation : annotations) {
+ if (!atomAnnotation) {
+ return false;
+ }
+ if (!write_annotation(event, *atomAnnotation)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool write_field_annotations(AStatsEvent* event, const std::vector<Annotation>& annotations) {
+ for (const auto& fieldAnnotation : annotations) {
+ if (!write_annotation(event, fieldAnnotation)) {
+ return false;
+ }
+ }
+ return true;
+}
ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) {
if (vendorAtom.atomId < 100000 || vendorAtom.atomId >= 200000) {
- ALOGE("Atom ID %ld is not a valid vendor atom ID", (long) vendorAtom.atomId);
+ ALOGE("Atom ID %ld is not a valid vendor atom ID", (long)vendorAtom.atomId);
return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
- -1, "Not a valid vendor atom ID");
+ -1, "Not a valid vendor atom ID");
}
if (vendorAtom.reverseDomainName.length() > 50) {
ALOGE("Vendor atom reverse domain name %s is too long.",
- vendorAtom.reverseDomainName.c_str());
+ vendorAtom.reverseDomainName.c_str());
return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
- -1, "Vendor atom reverse domain name is too long");
+ -1, "Vendor atom reverse domain name is too long");
}
AStatsEvent* event = AStatsEvent_obtain();
AStatsEvent_setAtomId(event, vendorAtom.atomId);
+
+ if (vendorAtom.atomAnnotations) {
+ if (!write_atom_annotations(event, *vendorAtom.atomAnnotations)) {
+ ALOGE("Atom ID %ld has incompatible atom level annotation", (long)vendorAtom.atomId);
+ AStatsEvent_release(event);
+ return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
+ -1, "invalid atom annotation");
+ }
+ }
+
+ // populate map for quickier access for VendorAtomValue associated annotations by value index
+ std::unordered_map<int, int> fieldIndexToAnnotationSetMap;
+ if (vendorAtom.valuesAnnotations) {
+ const std::vector<std::optional<AnnotationSet>>& valuesAnnotations =
+ *vendorAtom.valuesAnnotations;
+ for (int i = 0; i < valuesAnnotations.size(); i++) {
+ if (valuesAnnotations[i]) {
+ fieldIndexToAnnotationSetMap[valuesAnnotations[i]->valueIndex] = i;
+ }
+ }
+ }
+
AStatsEvent_writeString(event, vendorAtom.reverseDomainName.c_str());
+ size_t atomValueIdx = 0;
for (const auto& atomValue : vendorAtom.values) {
switch (atomValue.getTag()) {
case VendorAtomValue::intValue:
- AStatsEvent_writeInt32(event,
- atomValue.get<VendorAtomValue::intValue>());
+ AStatsEvent_writeInt32(event, atomValue.get<VendorAtomValue::intValue>());
break;
case VendorAtomValue::longValue:
- AStatsEvent_writeInt64(event,
- atomValue.get<VendorAtomValue::longValue>());
+ AStatsEvent_writeInt64(event, atomValue.get<VendorAtomValue::longValue>());
break;
case VendorAtomValue::floatValue:
- AStatsEvent_writeFloat(event,
- atomValue.get<VendorAtomValue::floatValue>());
+ AStatsEvent_writeFloat(event, atomValue.get<VendorAtomValue::floatValue>());
break;
case VendorAtomValue::stringValue:
AStatsEvent_writeString(event,
- atomValue.get<VendorAtomValue::stringValue>().c_str());
+ atomValue.get<VendorAtomValue::stringValue>().c_str());
break;
case VendorAtomValue::boolValue:
- AStatsEvent_writeBool(event,
- atomValue.get<VendorAtomValue::boolValue>());
+ AStatsEvent_writeBool(event, atomValue.get<VendorAtomValue::boolValue>());
break;
case VendorAtomValue::repeatedIntValue: {
const std::optional<std::vector<int>>& repeatedIntValue =
@@ -112,8 +185,8 @@
for (int i = 0; i < repeatedStringVector.size(); ++i) {
cStringArray[i] = repeatedStringVector[i].has_value()
- ? repeatedStringVector[i]->c_str()
- : "";
+ ? repeatedStringVector[i]->c_str()
+ : "";
}
AStatsEvent_writeStringArray(event, cStringArray, repeatedStringVector.size());
@@ -146,15 +219,40 @@
AStatsEvent_writeByteArray(event, byteArrayValue->data(), byteArrayValue->size());
break;
}
+ default: {
+ AStatsEvent_release(event);
+ ALOGE("Atom ID %ld has invalid atomValue.getTag", (long)vendorAtom.atomId);
+ return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
+ -1, "invalid atomValue.getTag");
+ break;
+ }
}
+
+ const auto& valueAnnotationIndex = fieldIndexToAnnotationSetMap.find(atomValueIdx);
+ if (valueAnnotationIndex != fieldIndexToAnnotationSetMap.end()) {
+ const std::vector<Annotation>& fieldAnnotations =
+ (*vendorAtom.valuesAnnotations)[valueAnnotationIndex->second]->annotations;
+ VLOG("Atom ID %ld has %ld annotations for field #%ld", (long)vendorAtom.atomId,
+ (long)fieldAnnotations.size(), (long)atomValueIdx + 2);
+ if (!write_field_annotations(event, fieldAnnotations)) {
+ ALOGE("Atom ID %ld has incompatible field level annotation for field #%ld",
+ (long)vendorAtom.atomId, (long)atomValueIdx + 2);
+ AStatsEvent_release(event);
+ return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
+ -1, "invalid atom field annotation");
+ }
+ }
+ atomValueIdx++;
}
AStatsEvent_build(event);
const int ret = AStatsEvent_write(event);
AStatsEvent_release(event);
-
- return ret <= 0 ?
- ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(ret, "report atom failed") :
- ndk::ScopedAStatus::ok();
+ if (ret <= 0) {
+ ALOGE("Error writing Atom ID %ld. Result: %d", (long)vendorAtom.atomId, ret);
+ }
+ return ret <= 0 ? ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(ret,
+ "report atom failed")
+ : ndk::ScopedAStatus::ok();
}
} // namespace stats
diff --git a/services/stats/StatsHal.cpp b/services/stats/StatsHal.cpp
index d27d989..19176d9 100644
--- a/services/stats/StatsHal.cpp
+++ b/services/stats/StatsHal.cpp
@@ -14,42 +14,42 @@
* limitations under the License.
*/
-#define DEBUG false // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#define LOG_TAG "StatsHal"
+#include "StatsHal.h"
+
#include <log/log.h>
#include <statslog.h>
-#include "StatsHal.h"
-
namespace android {
namespace frameworks {
namespace stats {
namespace V1_0 {
namespace implementation {
-StatsHal::StatsHal() {}
+StatsHal::StatsHal() {
+}
-hardware::Return<void> StatsHal::reportSpeakerImpedance(
- const SpeakerImpedance& speakerImpedance) {
+hardware::Return<void> StatsHal::reportSpeakerImpedance(const SpeakerImpedance& speakerImpedance) {
android::util::stats_write(android::util::SPEAKER_IMPEDANCE_REPORTED,
- speakerImpedance.speakerLocation, speakerImpedance.milliOhms);
+ speakerImpedance.speakerLocation, speakerImpedance.milliOhms);
return hardware::Void();
}
hardware::Return<void> StatsHal::reportHardwareFailed(const HardwareFailed& hardwareFailed) {
android::util::stats_write(android::util::HARDWARE_FAILED, int32_t(hardwareFailed.hardwareType),
- hardwareFailed.hardwareLocation, int32_t(hardwareFailed.errorCode));
+ hardwareFailed.hardwareLocation, int32_t(hardwareFailed.errorCode));
return hardware::Void();
}
hardware::Return<void> StatsHal::reportPhysicalDropDetected(
const PhysicalDropDetected& physicalDropDetected) {
- android::util::stats_write(android::util::PHYSICAL_DROP_DETECTED,
- int32_t(physicalDropDetected.confidencePctg), physicalDropDetected.accelPeak,
- physicalDropDetected.freefallDuration);
+ android::util::stats_write(
+ android::util::PHYSICAL_DROP_DETECTED, int32_t(physicalDropDetected.confidencePctg),
+ physicalDropDetected.accelPeak, physicalDropDetected.freefallDuration);
return hardware::Void();
}
@@ -58,20 +58,21 @@
std::vector<int32_t> buckets = chargeCycles.cycleBucket;
int initialSize = buckets.size();
for (int i = 0; i < 10 - initialSize; i++) {
- buckets.push_back(0); // Push 0 for buckets that do not exist.
+ buckets.push_back(0); // Push 0 for buckets that do not exist.
}
android::util::stats_write(android::util::CHARGE_CYCLES_REPORTED, buckets[0], buckets[1],
- buckets[2], buckets[3], buckets[4], buckets[5], buckets[6], buckets[7], buckets[8],
- buckets[9]);
+ buckets[2], buckets[3], buckets[4], buckets[5], buckets[6],
+ buckets[7], buckets[8], buckets[9]);
return hardware::Void();
}
hardware::Return<void> StatsHal::reportBatteryHealthSnapshot(
const BatteryHealthSnapshotArgs& batteryHealthSnapshotArgs) {
- android::util::stats_write(android::util::BATTERY_HEALTH_SNAPSHOT,
- int32_t(batteryHealthSnapshotArgs.type), batteryHealthSnapshotArgs.temperatureDeciC,
- batteryHealthSnapshotArgs.voltageMicroV, batteryHealthSnapshotArgs.currentMicroA,
+ android::util::stats_write(
+ android::util::BATTERY_HEALTH_SNAPSHOT, int32_t(batteryHealthSnapshotArgs.type),
+ batteryHealthSnapshotArgs.temperatureDeciC, batteryHealthSnapshotArgs.voltageMicroV,
+ batteryHealthSnapshotArgs.currentMicroA,
batteryHealthSnapshotArgs.openCircuitVoltageMicroV,
batteryHealthSnapshotArgs.resistanceMicroOhm, batteryHealthSnapshotArgs.levelPercent);
@@ -87,14 +88,15 @@
hardware::Return<void> StatsHal::reportBatteryCausedShutdown(
const BatteryCausedShutdown& batteryCausedShutdown) {
android::util::stats_write(android::util::BATTERY_CAUSED_SHUTDOWN,
- batteryCausedShutdown.voltageMicroV);
+ batteryCausedShutdown.voltageMicroV);
return hardware::Void();
}
hardware::Return<void> StatsHal::reportUsbPortOverheatEvent(
const UsbPortOverheatEvent& usbPortOverheatEvent) {
- android::util::stats_write(android::util::USB_PORT_OVERHEAT_EVENT_REPORTED,
+ android::util::stats_write(
+ android::util::USB_PORT_OVERHEAT_EVENT_REPORTED,
usbPortOverheatEvent.plugTemperatureDeciC, usbPortOverheatEvent.maxTemperatureDeciC,
usbPortOverheatEvent.timeToOverheat, usbPortOverheatEvent.timeToHysteresis,
usbPortOverheatEvent.timeToInactive);
@@ -102,18 +104,17 @@
return hardware::Void();
}
-hardware::Return<void> StatsHal::reportSpeechDspStat(
- const SpeechDspStat& speechDspStat) {
+hardware::Return<void> StatsHal::reportSpeechDspStat(const SpeechDspStat& speechDspStat) {
android::util::stats_write(android::util::SPEECH_DSP_STAT_REPORTED,
- speechDspStat.totalUptimeMillis, speechDspStat.totalDowntimeMillis,
- speechDspStat.totalCrashCount, speechDspStat.totalRecoverCount);
+ speechDspStat.totalUptimeMillis, speechDspStat.totalDowntimeMillis,
+ speechDspStat.totalCrashCount, speechDspStat.totalRecoverCount);
return hardware::Void();
}
hardware::Return<void> StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) {
if (vendorAtom.atomId < 100000 || vendorAtom.atomId >= 200000) {
- ALOGE("Atom ID %ld is not a valid vendor atom ID", (long) vendorAtom.atomId);
+ ALOGE("Atom ID %ld is not a valid vendor atom ID", (long)vendorAtom.atomId);
return hardware::Void();
}
if (vendorAtom.reverseDomainName.size() > 50) {
diff --git a/services/stats/include/stats/StatsAidl.h b/services/stats/include/stats/StatsAidl.h
index 219e71e..340b539 100644
--- a/services/stats/include/stats/StatsAidl.h
+++ b/services/stats/include/stats/StatsAidl.h
@@ -28,8 +28,7 @@
/**
* Binder call to get vendor atom.
*/
- virtual ndk::ScopedAStatus reportVendorAtom(
- const VendorAtom& in_vendorAtom) override;
+ virtual ndk::ScopedAStatus reportVendorAtom(const VendorAtom& in_vendorAtom) override;
};
} // namespace stats
diff --git a/services/stats/include/stats/StatsHal.h b/services/stats/include/stats/StatsHal.h
index 071e54f..864ad14 100644
--- a/services/stats/include/stats/StatsHal.h
+++ b/services/stats/include/stats/StatsHal.h
@@ -16,7 +16,6 @@
#include <android/frameworks/stats/1.0/IStats.h>
#include <android/frameworks/stats/1.0/types.h>
-
#include <stats_event.h>
using namespace android::frameworks::stats::V1_0;
@@ -30,8 +29,8 @@
using android::hardware::Return;
/**
-* Implements the Stats HAL
-*/
+ * Implements the Stats HAL
+ */
class StatsHal : public IStats {
public:
StatsHal();
@@ -50,12 +49,12 @@
* Binder call to get PhysicalDropDetected atom.
*/
virtual Return<void> reportPhysicalDropDetected(
- const PhysicalDropDetected& physicalDropDetected) override;
+ const PhysicalDropDetected& physicalDropDetected) override;
/**
* Binder call to get ChargeCyclesReported atom.
*/
- virtual Return<void> reportChargeCycles(const ChargeCycles& chargeCycles) override;
+ virtual Return<void> reportChargeCycles(const ChargeCycles& chargeCycles) override;
/**
* Binder call to get BatteryHealthSnapshot atom.
@@ -83,8 +82,7 @@
/**
* Binder call to get Speech DSP state atom.
*/
- virtual Return<void> reportSpeechDspStat(
- const SpeechDspStat& speechDspStat) override;
+ virtual Return<void> reportSpeechDspStat(const SpeechDspStat& speechDspStat) override;
/**
* Binder call to get vendor atom.
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index ad98e93..5bb2497 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -209,6 +209,9 @@
// The dimming flag
bool dimmingEnabled{true};
+ float currentSdrHdrRatio = 1.f;
+ float desiredSdrHdrRatio = 1.f;
+
virtual ~LayerFECompositionState();
// Debugging
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index 8ec77c0..229a657 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -136,9 +136,8 @@
compositionengine::Output::FrameFences presentAndGetFrameFences() override;
virtual renderengine::DisplaySettings generateClientCompositionDisplaySettings() const;
std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
- bool supportsProtectedContent, ui::Dataspace outputDataspace,
- std::vector<LayerFE*> &outLayerFEs) override;
- virtual bool layerNeedsFiltering(const OutputLayer*) const;
+ bool supportsProtectedContent, ui::Dataspace outputDataspace,
+ std::vector<LayerFE*>& outLayerFEs) override;
void appendRegionFlashRequests(const Region&, std::vector<LayerFE::LayerSettings>&) override;
void setExpensiveRenderingExpected(bool enabled) override;
void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) override;
diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
index a405c4d..531b659 100644
--- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
@@ -121,6 +121,10 @@
dumpVal(out, "dataspace", toString(dataspace), dataspace);
dumpVal(out, "hdr metadata types", hdrMetadata.validTypes);
dumpVal(out, "dimming enabled", dimmingEnabled);
+ if (currentSdrHdrRatio > 1.01f || desiredSdrHdrRatio > 1.01f) {
+ dumpVal(out, "current sdr/hdr ratio", currentSdrHdrRatio);
+ dumpVal(out, "desired sdr/hdr ratio", desiredSdrHdrRatio);
+ }
dumpVal(out, "colorTransform", colorTransform);
out.append("\n");
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 3ec6816..403385e 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -1438,7 +1438,7 @@
Enabled);
compositionengine::LayerFE::ClientCompositionTargetSettings
targetSettings{.clip = clip,
- .needsFiltering = layerNeedsFiltering(layer) ||
+ .needsFiltering = layer->needsFiltering() ||
outputState.needsFiltering,
.isSecure = outputState.isSecure,
.supportsProtectedContent = supportsProtectedContent,
@@ -1469,10 +1469,6 @@
return clientCompositionLayers;
}
-bool Output::layerNeedsFiltering(const compositionengine::OutputLayer* layer) const {
- return layer->needsFiltering();
-}
-
void Output::appendRegionFlashRequests(
const Region& flashRegion, std::vector<LayerFE::LayerSettings>& clientCompositionLayers) {
if (flashRegion.isEmpty()) {
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 60a2c83..1b86cd3 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -340,10 +340,17 @@
state.dimmingRatio = 1.f;
state.whitePointNits = getOutput().getState().displayBrightnessNits;
} else {
- state.dimmingRatio = std::clamp(getOutput().getState().sdrWhitePointNits /
- getOutput().getState().displayBrightnessNits,
- 0.f, 1.f);
- state.whitePointNits = getOutput().getState().sdrWhitePointNits;
+ float layerBrightnessNits = getOutput().getState().sdrWhitePointNits;
+ // RANGE_EXTENDED can "self-promote" to HDR, but is still rendered for a particular
+ // range that we may need to re-adjust to the current display conditions
+ if ((state.dataspace & HAL_DATASPACE_RANGE_MASK) == HAL_DATASPACE_RANGE_EXTENDED &&
+ layerFEState->currentSdrHdrRatio > 1.01f) {
+ layerBrightnessNits *= layerFEState->currentSdrHdrRatio;
+ }
+ state.dimmingRatio =
+ std::clamp(layerBrightnessNits / getOutput().getState().displayBrightnessNits, 0.f,
+ 1.f);
+ state.whitePointNits = layerBrightnessNits;
}
// These are evaluated every frame as they can potentially change at any
@@ -578,6 +585,7 @@
case Composition::CURSOR:
case Composition::DEVICE:
case Composition::DISPLAY_DECORATION:
+ case Composition::REFRESH_RATE_INDICATOR:
writeBufferStateToHWC(hwcLayer, outputIndependentState, skipLayer);
break;
case Composition::INVALID:
@@ -773,6 +781,7 @@
case Composition::CURSOR:
case Composition::SIDEBAND:
case Composition::DISPLAY_DECORATION:
+ case Composition::REFRESH_RATE_INDICATOR:
result = (to == Composition::CLIENT || to == Composition::DEVICE);
break;
}
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
index 2fc029f..6064126 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
@@ -151,6 +151,10 @@
// A for "Alpha", since the decoration is an alpha layer.
result.append("A");
break;
+ case aidl::android::hardware::graphics::composer3::Composition::REFRESH_RATE_INDICATOR:
+ // R for "Refresh", since the layer is Refresh rate overlay.
+ result.append("R");
+ break;
}
}
return result;
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 6199a5a..933f616 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -93,7 +93,7 @@
MOCK_METHOD2(onHotplug,
std::optional<DisplayIdentificationInfo>(hal::HWDisplayId, hal::Connection));
MOCK_CONST_METHOD0(updatesDeviceProductInfoOnHotplugReconnect, bool());
- MOCK_METHOD2(onVsync, bool(hal::HWDisplayId, int64_t));
+ MOCK_METHOD(std::optional<PhysicalDisplayId>, onVsync, (hal::HWDisplayId, int64_t));
MOCK_METHOD2(setVsyncEnabled, void(PhysicalDisplayId, hal::Vsync));
MOCK_CONST_METHOD1(isConnected, bool(PhysicalDisplayId));
MOCK_CONST_METHOD1(getModes, std::vector<HWComposer::HWCDisplayMode>(PhysicalDisplayId));
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index c61f7d8..5f73fbc 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -71,6 +71,7 @@
mRenderFrameRateFPSTrace("RenderRateFPS -" + to_string(getId())),
mPhysicalOrientation(args.physicalOrientation),
mIsPrimary(args.isPrimary),
+ mRequestedRefreshRate(args.requestedRefreshRate),
mRefreshRateSelector(std::move(args.refreshRateSelector)) {
mCompositionDisplay->editState().isSecure = args.isSecure;
mCompositionDisplay->createRenderSurface(
@@ -347,10 +348,6 @@
return mCompositionDisplay->getState().undefinedRegion;
}
-bool DisplayDevice::needsFiltering() const {
- return mCompositionDisplay->getState().needsFiltering;
-}
-
ui::LayerStack DisplayDevice::getLayerStack() const {
return mCompositionDisplay->getState().layerFilter.layerStack;
}
@@ -513,6 +510,23 @@
mDesiredActiveModeChanged = false;
}
+void DisplayDevice::adjustRefreshRate(Fps leaderDisplayRefreshRate) {
+ using fps_approx_ops::operator==;
+ if (mRequestedRefreshRate == 0_Hz) {
+ return;
+ }
+
+ using fps_approx_ops::operator>;
+ if (mRequestedRefreshRate > leaderDisplayRefreshRate) {
+ mAdjustedRefreshRate = leaderDisplayRefreshRate;
+ return;
+ }
+
+ unsigned divisor = static_cast<unsigned>(
+ std::round(leaderDisplayRefreshRate.getValue() / mRequestedRefreshRate.getValue()));
+ mAdjustedRefreshRate = leaderDisplayRefreshRate / divisor;
+}
+
std::atomic<int32_t> DisplayDeviceState::sNextSequenceId(1);
} // namespace android
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 370bd66..b86d9be 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -24,6 +24,7 @@
#include <android-base/thread_annotations.h>
#include <android/native_window.h>
#include <binder/IBinder.h>
+#include <ftl/concat.h>
#include <gui/LayerState.h>
#include <math/mat4.h>
#include <renderengine/RenderEngine.h>
@@ -115,7 +116,6 @@
const ui::Transform& getTransform() const;
const Rect& getLayerStackSpaceRect() const;
const Rect& getOrientedDisplaySpaceRect() const;
- bool needsFiltering() const;
ui::LayerStack getLayerStack() const;
bool receivesInput() const { return mFlags & eReceivesInput; }
@@ -245,6 +245,12 @@
nsecs_t getVsyncPeriodFromHWC() const;
+ Fps getAdjustedRefreshRate() const { return mAdjustedRefreshRate; }
+
+ // Round the requested refresh rate to match a divisor of the leader
+ // display's refresh rate. Only supported for virtual displays.
+ void adjustRefreshRate(Fps leaderDisplayRefreshRate);
+
// release HWC resources (if any) for removable displays
void disconnect();
@@ -279,6 +285,15 @@
uint32_t mFlags = 0;
+ // Requested refresh rate in fps, supported only for virtual displays.
+ // when this value is non zero, SurfaceFlinger will try to drop frames
+ // for virtual displays to match this requested refresh rate.
+ const Fps mRequestedRefreshRate;
+
+ // Adjusted refresh rate, rounded to match a divisor of the leader
+ // display's refresh rate. Only supported for virtual displays.
+ Fps mAdjustedRefreshRate = 0_Hz;
+
std::vector<ui::Hdr> mOverrideHdrTypes;
std::shared_ptr<scheduler::RefreshRateSelector> mRefreshRateSelector;
@@ -286,8 +301,8 @@
mutable std::mutex mActiveModeLock;
ActiveModeInfo mDesiredActiveMode GUARDED_BY(mActiveModeLock);
- TracedOrdinal<bool> mDesiredActiveModeChanged
- GUARDED_BY(mActiveModeLock) = {"DesiredActiveModeChanged", false};
+ TracedOrdinal<bool> mDesiredActiveModeChanged GUARDED_BY(mActiveModeLock) =
+ {ftl::Concat("DesiredActiveModeChanged-", getId().value).c_str(), false};
ActiveModeInfo mUpcomingActiveMode GUARDED_BY(kMainThreadContext);
};
@@ -316,6 +331,8 @@
uint32_t height = 0;
std::string displayName;
bool isSecure = false;
+ // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only
+ Fps requestedRefreshRate;
private:
static std::atomic<int32_t> sNextSequenceId;
@@ -345,6 +362,8 @@
std::optional<hardware::graphics::composer::hal::PowerMode> initialPowerMode;
bool isPrimary{false};
DisplayModeId activeModeId;
+ // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only
+ Fps requestedRefreshRate;
};
} // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 9470552..ba9aed8 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -208,6 +208,12 @@
return ::ndk::ScopedAStatus::ok();
}
+ ::ndk::ScopedAStatus onRefreshRateChangedDebug(
+ const RefreshRateChangedDebugData& refreshRateChangedDebugData) override {
+ mCallback.onRefreshRateChangedDebug(refreshRateChangedDebugData);
+ return ::ndk::ScopedAStatus::ok();
+ }
+
private:
HWC2::ComposerCallback& mCallback;
};
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index c1c7070..23dd3e5 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -44,6 +44,7 @@
#include <aidl/android/hardware/graphics/composer3/Composition.h>
#include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
#include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
+#include <aidl/android/hardware/graphics/composer3/RefreshRateChangedDebugData.h>
namespace android {
@@ -63,6 +64,8 @@
namespace hal = android::hardware::graphics::composer::hal;
+using aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
+
// Implement this interface to receive hardware composer events.
//
// These callback functions will generally be called on a hwbinder thread, but
@@ -77,6 +80,7 @@
const hal::VsyncPeriodChangeTimeline&) = 0;
virtual void onComposerHalSeamlessPossible(hal::HWDisplayId) = 0;
virtual void onComposerHalVsyncIdle(hal::HWDisplayId) = 0;
+ virtual void onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) = 0;
protected:
~ComposerCallback() = default;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 7dde6b4..6d94079 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -30,6 +30,7 @@
#include <compositionengine/Output.h>
#include <compositionengine/OutputLayer.h>
#include <compositionengine/impl/OutputLayerCompositionState.h>
+#include <ftl/concat.h>
#include <log/log.h>
#include <ui/DebugUtils.h>
#include <ui/GraphicBuffer.h>
@@ -148,16 +149,17 @@
return mUpdateDeviceProductInfoOnHotplugReconnect;
}
-bool HWComposer::onVsync(hal::HWDisplayId hwcDisplayId, nsecs_t timestamp) {
- const auto displayId = toPhysicalDisplayId(hwcDisplayId);
- if (!displayId) {
+std::optional<PhysicalDisplayId> HWComposer::onVsync(hal::HWDisplayId hwcDisplayId,
+ nsecs_t timestamp) {
+ const auto displayIdOpt = toPhysicalDisplayId(hwcDisplayId);
+ if (!displayIdOpt) {
LOG_HWC_DISPLAY_ERROR(hwcDisplayId, "Invalid HWC display");
- return false;
+ return {};
}
- RETURN_IF_INVALID_DISPLAY(*displayId, false);
+ RETURN_IF_INVALID_DISPLAY(*displayIdOpt, {});
- auto& displayData = mDisplayData[*displayId];
+ auto& displayData = mDisplayData[*displayIdOpt];
{
// There have been reports of HWCs that signal several vsync events
@@ -166,18 +168,18 @@
// out here so they don't cause havoc downstream.
if (timestamp == displayData.lastPresentTimestamp) {
ALOGW("Ignoring duplicate VSYNC event from HWC for display %s (t=%" PRId64 ")",
- to_string(*displayId).c_str(), timestamp);
- return false;
+ to_string(*displayIdOpt).c_str(), timestamp);
+ return {};
}
displayData.lastPresentTimestamp = timestamp;
}
- const auto tag = "HW_VSYNC_" + to_string(*displayId);
- ATRACE_INT(tag.c_str(), displayData.vsyncTraceToggle);
+ ATRACE_INT(ftl::Concat("HW_VSYNC_", displayIdOpt->value).c_str(),
+ displayData.vsyncTraceToggle);
displayData.vsyncTraceToggle = !displayData.vsyncTraceToggle;
- return true;
+ return displayIdOpt;
}
size_t HWComposer::getMaxVirtualDisplayCount() const {
@@ -375,8 +377,8 @@
displayData.vsyncEnabled = enabled;
- const auto tag = "HW_VSYNC_ON_" + to_string(displayId);
- ATRACE_INT(tag.c_str(), enabled == hal::Vsync::ENABLE ? 1 : 0);
+ ATRACE_INT(ftl::Concat("HW_VSYNC_ON_", displayId.value).c_str(),
+ enabled == hal::Vsync::ENABLE ? 1 : 0);
}
status_t HWComposer::setClientTarget(HalDisplayId displayId, uint32_t slot,
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index f6155d2..acebfb2 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -221,7 +221,10 @@
// TODO(b/157555476): Remove when the framework has proper support for headless mode
virtual bool updatesDeviceProductInfoOnHotplugReconnect() const = 0;
- virtual bool onVsync(hal::HWDisplayId, nsecs_t timestamp) = 0;
+ // Called when a vsync happens. If the vsync is valid, returns the
+ // corresponding PhysicalDisplayId. Otherwise returns nullopt.
+ virtual std::optional<PhysicalDisplayId> onVsync(hal::HWDisplayId, nsecs_t timestamp) = 0;
+
virtual void setVsyncEnabled(PhysicalDisplayId, hal::Vsync enabled) = 0;
virtual bool isConnected(PhysicalDisplayId) const = 0;
@@ -402,7 +405,7 @@
bool updatesDeviceProductInfoOnHotplugReconnect() const override;
- bool onVsync(hal::HWDisplayId, nsecs_t timestamp) override;
+ std::optional<PhysicalDisplayId> onVsync(hal::HWDisplayId, nsecs_t timestamp) override;
void setVsyncEnabled(PhysicalDisplayId, hal::Vsync enabled) override;
bool isConnected(PhysicalDisplayId) const override;
diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h
index 537d545..bf3089f 100644
--- a/services/surfaceflinger/DisplayHardware/Hal.h
+++ b/services/surfaceflinger/DisplayHardware/Hal.h
@@ -113,6 +113,8 @@
return "Sideband";
case aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION:
return "DisplayDecoration";
+ case aidl::android::hardware::graphics::composer3::Composition::REFRESH_RATE_INDICATOR:
+ return "RefreshRateIndicator";
default:
return "Unknown";
}
diff --git a/services/surfaceflinger/DisplayRenderArea.cpp b/services/surfaceflinger/DisplayRenderArea.cpp
index 20486e0..8f39e26 100644
--- a/services/surfaceflinger/DisplayRenderArea.cpp
+++ b/services/surfaceflinger/DisplayRenderArea.cpp
@@ -48,8 +48,8 @@
DisplayRenderArea::DisplayRenderArea(sp<const DisplayDevice> display, const Rect& sourceCrop,
ui::Size reqSize, ui::Dataspace reqDataSpace,
bool useIdentityTransform, bool allowSecureLayers)
- : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, display->getLayerStackSpaceRect(),
- allowSecureLayers, applyDeviceOrientation(useIdentityTransform, *display)),
+ : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, allowSecureLayers,
+ applyDeviceOrientation(useIdentityTransform, *display)),
mDisplay(std::move(display)),
mSourceCrop(sourceCrop) {}
@@ -57,18 +57,6 @@
return mTransform;
}
-Rect DisplayRenderArea::getBounds() const {
- return mDisplay->getBounds();
-}
-
-int DisplayRenderArea::getHeight() const {
- return mDisplay->getHeight();
-}
-
-int DisplayRenderArea::getWidth() const {
- return mDisplay->getWidth();
-}
-
bool DisplayRenderArea::isSecure() const {
return mAllowSecureLayers && mDisplay->isSecure();
}
@@ -77,18 +65,6 @@
return mDisplay;
}
-bool DisplayRenderArea::needsFiltering() const {
- // check if the projection from the logical render area
- // to the physical render area requires filtering
- const Rect& sourceCrop = getSourceCrop();
- int width = sourceCrop.width();
- int height = sourceCrop.height();
- if (getRotationFlags() & ui::Transform::ROT_90) {
- std::swap(width, height);
- }
- return width != getReqWidth() || height != getReqHeight();
-}
-
Rect DisplayRenderArea::getSourceCrop() const {
// use the projected display viewport by default.
if (mSourceCrop.isEmpty()) {
@@ -107,4 +83,4 @@
return rotation.transform(mSourceCrop);
}
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/DisplayRenderArea.h b/services/surfaceflinger/DisplayRenderArea.h
index 3478fc1..ce5410a 100644
--- a/services/surfaceflinger/DisplayRenderArea.h
+++ b/services/surfaceflinger/DisplayRenderArea.h
@@ -33,12 +33,8 @@
bool allowSecureLayers = true);
const ui::Transform& getTransform() const override;
- Rect getBounds() const override;
- int getHeight() const override;
- int getWidth() const override;
bool isSecure() const override;
sp<const DisplayDevice> getDisplayDevice() const override;
- bool needsFiltering() const override;
Rect getSourceCrop() const override;
private:
@@ -50,4 +46,4 @@
const ui::Transform mTransform;
};
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index 27a099c..925f111 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -1095,6 +1095,12 @@
}
void FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const {
+ if (mSurfaceFrames.empty()) {
+ // We don't want to trace display frames without any surface frames updates as this cannot
+ // be janky
+ return;
+ }
+
if (mToken == FrameTimelineInfo::INVALID_VSYNC_ID) {
// DisplayFrame should not have an invalid token.
ALOGE("Cannot trace DisplayFrame with invalid token");
diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
index 7b5a157..9d2aaab 100644
--- a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
+++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
@@ -18,10 +18,12 @@
#include <binder/Binder.h>
#include <gui/LayerMetadata.h>
+#include <ui/LayerStack.h>
#include <utils/StrongPointer.h>
#include <cstdint>
#include <limits>
#include <optional>
+
constexpr uint32_t UNASSIGNED_LAYER_ID = std::numeric_limits<uint32_t>::max();
namespace android {
@@ -51,6 +53,7 @@
bool addToRoot = true;
wp<IBinder> parentHandle = nullptr;
wp<IBinder> mirrorLayerHandle = nullptr;
+ ui::LayerStack layerStackToMirror = ui::INVALID_LAYER_STACK;
};
} // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
index 678d36b..a4fac1c 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -252,8 +252,8 @@
attachToParent(hierarchy);
attachToRelativeParent(hierarchy);
- if (layer->mirrorId != UNASSIGNED_LAYER_ID) {
- LayerHierarchy* mirror = getHierarchyFromId(layer->mirrorId);
+ for (uint32_t mirrorId : layer->mirrorIds) {
+ LayerHierarchy* mirror = getHierarchyFromId(mirrorId);
hierarchy->addChild(mirror, LayerHierarchy::Variant::Mirror);
}
}
@@ -292,14 +292,14 @@
auto it = hierarchy->mChildren.begin();
while (it != hierarchy->mChildren.end()) {
if (it->second == LayerHierarchy::Variant::Mirror) {
- hierarchy->mChildren.erase(it);
- break;
+ it = hierarchy->mChildren.erase(it);
+ } else {
+ it++;
}
- it++;
}
- if (layer->mirrorId != UNASSIGNED_LAYER_ID) {
- hierarchy->addChild(getHierarchyFromId(layer->mirrorId), LayerHierarchy::Variant::Mirror);
+ for (uint32_t mirrorId : layer->mirrorIds) {
+ hierarchy->addChild(getHierarchyFromId(mirrorId), LayerHierarchy::Variant::Mirror);
}
}
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
index 5514c06..547a852 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -44,9 +44,28 @@
layer.parentId = linkLayer(layer.parentId, layer.id);
layer.relativeParentId = linkLayer(layer.relativeParentId, layer.id);
- layer.mirrorId = linkLayer(layer.mirrorId, layer.id);
+ if (layer.layerStackToMirror != ui::INVALID_LAYER_STACK) {
+ // if this layer is mirroring a display, then walk though all the existing root layers
+ // for the layer stack and add them as children to be mirrored.
+ mDisplayMirroringLayers.emplace_back(layer.id);
+ for (auto& rootLayer : mLayers) {
+ if (rootLayer->isRoot() && rootLayer->layerStack == layer.layerStackToMirror) {
+ layer.mirrorIds.emplace_back(rootLayer->id);
+ linkLayer(rootLayer->id, layer.id);
+ }
+ }
+ } else {
+ // Check if we are mirroring a single layer, and if so add it to the list of children
+ // to be mirrored.
+ layer.layerIdToMirror = linkLayer(layer.layerIdToMirror, layer.id);
+ if (layer.layerIdToMirror != UNASSIGNED_LAYER_ID) {
+ layer.mirrorIds.emplace_back(layer.layerIdToMirror);
+ }
+ }
layer.touchCropId = linkLayer(layer.touchCropId, layer.id);
-
+ if (layer.isRoot()) {
+ updateDisplayMirrorLayers(layer);
+ }
mLayers.emplace_back(std::move(newLayer));
}
}
@@ -85,7 +104,14 @@
layer.parentId = unlinkLayer(layer.parentId, layer.id);
layer.relativeParentId = unlinkLayer(layer.relativeParentId, layer.id);
- layer.mirrorId = unlinkLayer(layer.mirrorId, layer.id);
+ if (layer.layerStackToMirror != ui::INVALID_LAYER_STACK) {
+ layer.mirrorIds = unlinkLayers(layer.mirrorIds, layer.id);
+ swapErase(mDisplayMirroringLayers, layer.id);
+ } else {
+ layer.layerIdToMirror = unlinkLayer(layer.layerIdToMirror, layer.id);
+ layer.mirrorIds.clear();
+ }
+
layer.touchCropId = unlinkLayer(layer.touchCropId, layer.id);
auto& references = it->second.references;
@@ -106,8 +132,8 @@
if (linkedLayer->relativeParentId == layer.id) {
linkedLayer->relativeParentId = UNASSIGNED_LAYER_ID;
}
- if (linkedLayer->mirrorId == layer.id) {
- linkedLayer->mirrorId = UNASSIGNED_LAYER_ID;
+ if (swapErase(linkedLayer->mirrorIds, layer.id)) {
+ linkedLayer->changes |= RequestedLayerState::Changes::Mirror;
}
if (linkedLayer->touchCropId == layer.id) {
linkedLayer->touchCropId = UNASSIGNED_LAYER_ID;
@@ -200,6 +226,12 @@
if (oldParentId != layer->parentId) {
unlinkLayer(oldParentId, layer->id);
layer->parentId = linkLayer(layer->parentId, layer->id);
+ if (oldParentId == UNASSIGNED_LAYER_ID) {
+ updateDisplayMirrorLayers(*layer);
+ }
+ }
+ if (layer->what & layer_state_t::eLayerStackChanged && layer->isRoot()) {
+ updateDisplayMirrorLayers(*layer);
}
if (oldRelativeParentId != layer->relativeParentId) {
unlinkLayer(oldRelativeParentId, layer->id);
@@ -308,6 +340,14 @@
return UNASSIGNED_LAYER_ID;
}
+std::vector<uint32_t> LayerLifecycleManager::unlinkLayers(const std::vector<uint32_t>& layerIds,
+ uint32_t linkedLayer) {
+ for (uint32_t layerId : layerIds) {
+ unlinkLayer(layerId, linkedLayer);
+ }
+ return {};
+}
+
std::string LayerLifecycleManager::References::getDebugString() const {
std::string debugInfo = owner.name + "[" + std::to_string(owner.id) + "] refs:";
std::for_each(references.begin(), references.end(),
@@ -329,4 +369,30 @@
mGlobalChanges |= RequestedLayerState::Changes::Hierarchy;
}
+// Some layers mirror the entire display stack. Since we don't have a single root layer per display
+// we have to track all these layers and update what they mirror when the list of root layers
+// on a display changes. This function walks through the list of display mirroring layers
+// and updates its list of layers that its mirroring. This function should be called when a new
+// root layer is added, removed or moved to another display.
+void LayerLifecycleManager::updateDisplayMirrorLayers(RequestedLayerState& rootLayer) {
+ for (uint32_t mirrorLayerId : mDisplayMirroringLayers) {
+ RequestedLayerState* mirrorLayer = getLayerFromId(mirrorLayerId);
+ bool canBeMirrored =
+ rootLayer.isRoot() && rootLayer.layerStack == mirrorLayer->layerStackToMirror;
+ bool currentlyMirrored =
+ std::find(mirrorLayer->mirrorIds.begin(), mirrorLayer->mirrorIds.end(),
+ rootLayer.id) != mirrorLayer->mirrorIds.end();
+
+ if (canBeMirrored && !currentlyMirrored) {
+ mirrorLayer->mirrorIds.emplace_back(rootLayer.id);
+ linkLayer(rootLayer.id, mirrorLayer->id);
+ mirrorLayer->changes |= RequestedLayerState::Changes::Mirror;
+ } else if (!canBeMirrored && currentlyMirrored) {
+ swapErase(mirrorLayer->mirrorIds, rootLayer.id);
+ unlinkLayer(rootLayer.id, mirrorLayer->id);
+ mirrorLayer->changes |= RequestedLayerState::Changes::Mirror;
+ }
+ }
+}
+
} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
index 63a7afc..25d27ee 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
@@ -80,6 +80,9 @@
std::vector<uint32_t>* getLinkedLayersFromId(uint32_t);
uint32_t linkLayer(uint32_t layerId, uint32_t layerToLink);
uint32_t unlinkLayer(uint32_t layerId, uint32_t linkedLayer);
+ std::vector<uint32_t> unlinkLayers(const std::vector<uint32_t>& layerIds, uint32_t linkedLayer);
+
+ void updateDisplayMirrorLayers(RequestedLayerState& rootLayer);
struct References {
// Lifetime tied to mLayers
@@ -90,6 +93,8 @@
std::unordered_map<uint32_t, References> mIdToLayer;
// Listeners are invoked once changes are committed.
std::vector<std::shared_ptr<ILifecycleListener>> mListeners;
+ // Layers that mirror a display stack (see updateDisplayMirrorLayers)
+ std::vector<uint32_t> mDisplayMirroringLayers;
// Aggregation of changes since last commit.
ftl::Flags<RequestedLayerState::Changes> mGlobalChanges;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
index 3a0540c..dbb7fbf 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -112,6 +112,10 @@
}
bool LayerSnapshot::getIsVisible() const {
+ if (handleSkipScreenshotFlag & outputFilter.toInternalDisplay) {
+ return false;
+ }
+
if (!hasSomethingToDraw()) {
return false;
}
@@ -125,6 +129,7 @@
std::string LayerSnapshot::getIsVisibleReason() const {
// not visible
+ if (handleSkipScreenshotFlag & outputFilter.toInternalDisplay) return "eLayerSkipScreenshot";
if (!hasSomethingToDraw()) return "!hasSomethingToDraw";
if (invalidTransform) return "invalidTransform";
if (isHiddenByPolicyFromParent) return "hidden by parent or layer flag";
@@ -163,4 +168,11 @@
return debug.str();
}
+FloatRect LayerSnapshot::sourceBounds() const {
+ if (!externalTexture) {
+ return geomLayerBounds;
+ }
+ return geomBufferSize.toFloatRect();
+}
+
} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index 4512ade..5d74203 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -68,7 +68,6 @@
renderengine::ShadowSettings shadowSettings;
bool premultipliedAlpha;
bool isHdrY410;
- bool bufferNeedsFiltering;
ui::Transform parentTransform;
Rect bufferSize;
Rect croppedBufferSize;
@@ -84,6 +83,7 @@
gui::GameMode gameMode;
scheduler::LayerInfo::FrameRate frameRate;
ui::Transform::RotationFlags fixedTransformHint;
+ bool handleSkipScreenshotFlag = false;
ChildState childState;
static bool isOpaqueFormat(PixelFormat format);
@@ -102,6 +102,7 @@
std::string getDebugString() const;
std::string getIsVisibleReason() const;
bool hasInputInfo() const;
+ FloatRect sourceBounds() const;
};
} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 40dffb9..3ed24b2 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -237,12 +237,6 @@
}
}
-bool getBufferNeedsFiltering(const LayerSnapshot& snapshot, const ui::Size& unrotatedBufferSize) {
- const int32_t layerWidth = static_cast<int32_t>(snapshot.geomLayerBounds.getWidth());
- const int32_t layerHeight = static_cast<int32_t>(snapshot.geomLayerBounds.getHeight());
- return layerWidth != unrotatedBufferSize.width || layerHeight != unrotatedBufferSize.height;
-}
-
auto getBlendMode(const LayerSnapshot& snapshot, const RequestedLayerState& requested) {
auto blendMode = Hwc2::IComposerClient::BlendMode::NONE;
if (snapshot.alpha != 1.0f || !snapshot.isContentOpaque()) {
@@ -397,7 +391,9 @@
void LayerSnapshotBuilder::updateSnapshots(const Args& args) {
ATRACE_NAME("UpdateSnapshots");
- if (args.forceUpdate || args.displayChanges) {
+ if (args.parentCrop) {
+ mRootSnapshot.geomLayerBounds = *args.parentCrop;
+ } else if (args.forceUpdate || args.displayChanges) {
mRootSnapshot.geomLayerBounds = getMaxDisplayBounds(args.displays);
}
if (args.displayChanges) {
@@ -624,7 +620,8 @@
RequestedLayerState::Changes::AffectsChildren);
snapshot.changes = parentChanges | requested.changes;
snapshot.isHiddenByPolicyFromParent = parentSnapshot.isHiddenByPolicyFromParent ||
- parentSnapshot.invalidTransform || requested.isHiddenByPolicy();
+ parentSnapshot.invalidTransform || requested.isHiddenByPolicy() ||
+ (args.excludeLayerIds.find(path.id) != args.excludeLayerIds.end());
snapshot.contentDirty = requested.what & layer_state_t::CONTENT_DIRTY;
// TODO(b/238781169) scope down the changes to only buffer updates.
snapshot.hasReadyFrame =
@@ -667,6 +664,8 @@
snapshot.sidebandStream = requested.sidebandStream;
snapshot.transparentRegionHint = requested.transparentRegion;
snapshot.color.rgb = requested.getColor().rgb;
+ snapshot.currentSdrHdrRatio = requested.currentSdrHdrRatio;
+ snapshot.desiredSdrHdrRatio = requested.desiredSdrHdrRatio;
}
if (snapshot.isHiddenByPolicyFromParent && !newSnapshot) {
@@ -712,6 +711,10 @@
snapshot.fixedTransformHint = requested.fixedTransformHint != ui::Transform::ROT_INVALID
? requested.fixedTransformHint
: parentSnapshot.fixedTransformHint;
+ // Display mirrors are always placed in a VirtualDisplay so we never want to capture layers
+ // marked as skip capture
+ snapshot.handleSkipScreenshotFlag = parentSnapshot.handleSkipScreenshotFlag ||
+ (requested.layerStackToMirror != ui::INVALID_LAYER_STACK);
}
if (forceUpdate || requested.changes.get() != 0) {
@@ -865,11 +868,6 @@
if (requested.potentialCursor) {
snapshot.cursorFrame = snapshot.geomLayerTransform.transform(bounds);
}
-
- // TODO(b/238781169) use dest vs src
- snapshot.bufferNeedsFiltering = snapshot.externalTexture &&
- getBufferNeedsFiltering(snapshot,
- requested.getUnrotatedBufferSize(displayRotationFlags));
}
void LayerSnapshotBuilder::updateShadows(LayerSnapshot& snapshot,
@@ -988,6 +986,20 @@
}
}
+// Visit each visible snapshot in z-order
+void LayerSnapshotBuilder::forEachVisibleSnapshot(const ConstVisitor& visitor,
+ const LayerHierarchy& root) const {
+ root.traverseInZOrder(
+ [this, visitor](const LayerHierarchy&,
+ const LayerHierarchy::TraversalPath& traversalPath) -> bool {
+ LayerSnapshot* snapshot = getSnapshot(traversalPath);
+ if (snapshot && snapshot->isVisible) {
+ visitor(*snapshot);
+ }
+ return true;
+ });
+}
+
void LayerSnapshotBuilder::forEachVisibleSnapshot(const Visitor& visitor) {
for (int i = 0; i < mNumInterestingSnapshots; i++) {
std::unique_ptr<LayerSnapshot>& snapshot = mSnapshots.at((size_t)i);
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
index abb7e66..f4544fd 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
@@ -36,7 +36,7 @@
class LayerSnapshotBuilder {
public:
struct Args {
- const LayerHierarchy& root;
+ LayerHierarchy root;
const LayerLifecycleManager& layerLifecycleManager;
bool forceUpdate = false;
bool includeMetadata = false;
@@ -46,6 +46,8 @@
const renderengine::ShadowSettings& globalShadowSettings;
bool supportsBlur = true;
bool forceFullDamage = false;
+ std::optional<FloatRect> parentCrop = std::nullopt;
+ std::unordered_set<uint32_t> excludeLayerIds;
};
LayerSnapshotBuilder();
@@ -65,6 +67,9 @@
// Visit each visible snapshot in z-order
void forEachVisibleSnapshot(const ConstVisitor& visitor) const;
+ // Visit each visible snapshot in z-order
+ void forEachVisibleSnapshot(const ConstVisitor& visitor, const LayerHierarchy& root) const;
+
typedef std::function<void(std::unique_ptr<LayerSnapshot>& snapshot)> Visitor;
// Visit each visible snapshot in z-order and move the snapshot if needed
void forEachVisibleSnapshot(const Visitor& visitor);
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 39bf07a..d63b126 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -45,6 +45,16 @@
return layerId == UNASSIGNED_LAYER_ID ? "none" : std::to_string(layerId);
}
+std::string layerIdsToString(const std::vector<uint32_t>& layerIds) {
+ std::stringstream stream;
+ stream << "{";
+ for (auto layerId : layerIds) {
+ stream << layerId << ",";
+ }
+ stream << "}";
+ return stream.str();
+}
+
} // namespace
RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args)
@@ -64,8 +74,11 @@
if (args.parentHandle != nullptr) {
canBeRoot = false;
}
- mirrorId = LayerHandle::getLayerId(args.mirrorLayerHandle.promote());
- if (mirrorId != UNASSIGNED_LAYER_ID) {
+ layerIdToMirror = LayerHandle::getLayerId(args.mirrorLayerHandle.promote());
+ if (layerIdToMirror != UNASSIGNED_LAYER_ID) {
+ changes |= RequestedLayerState::Changes::Mirror;
+ } else if (args.layerStackToMirror != ui::INVALID_LAYER_STACK) {
+ layerStackToMirror = args.layerStackToMirror;
changes |= RequestedLayerState::Changes::Mirror;
}
@@ -96,6 +109,8 @@
layerStack = ui::DEFAULT_LAYER_STACK;
transformToDisplayInverse = false;
dataspace = ui::Dataspace::UNKNOWN;
+ desiredSdrHdrRatio = 1.f;
+ currentSdrHdrRatio = 1.f;
dataspaceRequested = false;
hdrMetadata.validTypes = 0;
surfaceDamageRegion = Region::INVALID_REGION;
@@ -307,7 +322,7 @@
return "[" + std::to_string(id) + "]" + name + ",parent=" + layerIdToString(parentId) +
",relativeParent=" + layerIdToString(relativeParentId) +
",isRelativeOf=" + std::to_string(isRelativeOf) +
- ",mirrorId=" + layerIdToString(mirrorId) +
+ ",mirrorIds=" + layerIdsToString(mirrorIds) +
",handleAlive=" + std::to_string(handleAlive) + ",z=" + std::to_string(z);
}
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
index 3a16531..6317b95 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -97,13 +97,15 @@
std::shared_ptr<renderengine::ExternalTexture> externalTexture;
gui::GameMode gameMode;
scheduler::LayerInfo::FrameRate requestedFrameRate;
+ ui::LayerStack layerStackToMirror = ui::INVALID_LAYER_STACK;
+ uint32_t layerIdToMirror = UNASSIGNED_LAYER_ID;
// 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;
+ std::vector<uint32_t> mirrorIds{};
uint32_t touchCropId = UNASSIGNED_LAYER_ID;
uint32_t bgColorLayerId = UNASSIGNED_LAYER_ID;
ftl::Flags<RequestedLayerState::Changes> changes;
diff --git a/services/surfaceflinger/FrontEnd/SwapErase.h b/services/surfaceflinger/FrontEnd/SwapErase.h
index f672f99..0061c53 100644
--- a/services/surfaceflinger/FrontEnd/SwapErase.h
+++ b/services/surfaceflinger/FrontEnd/SwapErase.h
@@ -23,12 +23,15 @@
// 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) {
+bool swapErase(std::vector<T>& vec, const T& value) {
+ bool found = false;
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);
+ found = true;
}
+ return found;
}
// Similar to swapErase(std::vector<T>& vec, const T& value) but erases the first element
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h
index a06b870..7fc825e 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.h
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h
@@ -34,7 +34,7 @@
class TransactionHandler {
public:
struct TransactionFlushState {
- const TransactionState* transaction;
+ TransactionState* transaction;
bool firstTransaction = true;
nsecs_t queueProcessTime = 0;
// Layer handles that have transactions with buffers that are ready to be applied.
diff --git a/services/surfaceflinger/HdrLayerInfoReporter.h b/services/surfaceflinger/HdrLayerInfoReporter.h
index 4ada2b6..9b70c16 100644
--- a/services/surfaceflinger/HdrLayerInfoReporter.h
+++ b/services/surfaceflinger/HdrLayerInfoReporter.h
@@ -33,6 +33,17 @@
int32_t maxW = 0;
int32_t maxH = 0;
int32_t flags = 0;
+ // Counter-intuitively a value of "1" means "as much as you can give me" due to "1" being
+ // the default value for all layers, so any HDR layer with a value of 1.f means no
+ // reduced maximum has been requested
+ // TODO: Should the max desired ratio have a better meaning for HLG/PQ so this can be
+ // eliminated? If we assume an SDR white point of even just 100 nits for those content
+ // then HLG could have a meaningful max ratio of 10.f and PQ of 100.f instead of needing
+ // to treat 1.f as "uncapped"
+ // With peak display brightnesses exceeding 1,000 nits currently, HLG's request could
+ // actually be satisfied in some ambient conditions such that limiting that max for that
+ // content in theory makes sense
+ float maxDesiredSdrHdrRatio = 0.f;
bool operator==(const HdrLayerInfo& other) const {
return numberOfHdrLayers == other.numberOfHdrLayers && maxW == other.maxW &&
@@ -40,6 +51,20 @@
}
bool operator!=(const HdrLayerInfo& other) const { return !(*this == other); }
+
+ void mergeDesiredRatio(float update) {
+ if (maxDesiredSdrHdrRatio == 0.f) {
+ // If nothing is set, take the incoming value
+ maxDesiredSdrHdrRatio = update;
+ } else if (update == 1.f) {
+ // If the request is to "go to max", then take it regardless
+ maxDesiredSdrHdrRatio = 1.f;
+ } else if (maxDesiredSdrHdrRatio != 1.f) {
+ // If we're not currently asked to "go to max", then take the max
+ // of the incoming requests
+ maxDesiredSdrHdrRatio = std::max(maxDesiredSdrHdrRatio, update);
+ }
+ }
};
HdrLayerInfoReporter() = default;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index b0964fe..8c484f0 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -146,7 +146,7 @@
mLayerCreationFlags(args.flags),
mBorderEnabled(false),
mTextureName(args.textureName),
- mLayerFE(args.flinger->getFactory().createLayerFE(mName)) {
+ mLegacyLayerFE(args.flinger->getFactory().createLayerFE(mName)) {
ALOGV("Creating Layer %s", getDebugName());
uint32_t layerFlags = 0;
@@ -228,9 +228,7 @@
if (mBufferInfo.mBuffer != nullptr) {
callReleaseBufferCallback(mDrawingState.releaseBufferListener,
mBufferInfo.mBuffer->getBuffer(), mBufferInfo.mFrameNumber,
- mBufferInfo.mFence,
- mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(
- mOwnerUid));
+ mBufferInfo.mFence);
}
if (!isClone()) {
// The original layer and the clone layer share the same texture. Therefore, only one of
@@ -254,7 +252,7 @@
}
if (hasTrustedPresentationListener()) {
mFlinger->mNumTrustedPresentationListeners--;
- updateTrustedPresentationState(nullptr, -1 /* time_in_ms */, true /* leaveState*/);
+ updateTrustedPresentationState(nullptr, nullptr, -1 /* time_in_ms */, true /* leaveState*/);
}
}
@@ -285,7 +283,7 @@
mRemovedFromDrawingState = true;
mFlinger->mScheduler->deregisterLayer(this);
}
- updateTrustedPresentationState(nullptr, -1 /* time_in_ms */, true /* leaveState*/);
+ updateTrustedPresentationState(nullptr, nullptr, -1 /* time_in_ms */, true /* leaveState*/);
mFlinger->markLayerPendingRemovalLocked(sp<Layer>::fromExisting(this));
}
@@ -384,8 +382,9 @@
}
// No early returns.
-void Layer::updateTrustedPresentationState(const DisplayDevice* display, int64_t time_in_ms,
- bool leaveState) {
+void Layer::updateTrustedPresentationState(const DisplayDevice* display,
+ const frontend::LayerSnapshot* snapshot,
+ int64_t time_in_ms, bool leaveState) {
if (!hasTrustedPresentationListener()) {
return;
}
@@ -394,12 +393,13 @@
if (!leaveState) {
const auto outputLayer = findOutputLayerForDisplay(display);
- if (outputLayer != nullptr) {
+ if (outputLayer != nullptr && snapshot != nullptr) {
mLastComputedTrustedPresentationState =
- computeTrustedPresentationState(mBounds, mSourceBounds,
+ computeTrustedPresentationState(snapshot->geomLayerBounds,
+ snapshot->sourceBounds(),
outputLayer->getState().coveredRegion,
- mScreenBounds, getCompositionState()->alpha,
- getCompositionState()->geomLayerTransform,
+ snapshot->transformedBounds, snapshot->alpha,
+ snapshot->geomLayerTransform,
mTrustedPresentationThresholds);
}
}
@@ -622,6 +622,8 @@
snapshot->surfaceDamage = surfaceDamageRegion;
snapshot->hasProtectedContent = isProtected();
snapshot->dimmingEnabled = isDimmingEnabled();
+ snapshot->currentSdrHdrRatio = getCurrentSdrHdrRatio();
+ snapshot->desiredSdrHdrRatio = getDesiredSdrHdrRatio();
const bool usesRoundedCorners = hasRoundedCorners();
@@ -730,6 +732,40 @@
return (p != nullptr) ? p->isSecure() : false;
}
+void Layer::transferAvailableJankData(const std::deque<sp<CallbackHandle>>& handles,
+ std::vector<JankData>& jankData) {
+ if (mPendingJankClassifications.empty() ||
+ !mPendingJankClassifications.front()->getJankType()) {
+ return;
+ }
+
+ bool includeJankData = false;
+ for (const auto& handle : handles) {
+ for (const auto& cb : handle->callbackIds) {
+ if (cb.includeJankData) {
+ includeJankData = true;
+ break;
+ }
+ }
+
+ if (includeJankData) {
+ jankData.reserve(mPendingJankClassifications.size());
+ break;
+ }
+ }
+
+ while (!mPendingJankClassifications.empty() &&
+ mPendingJankClassifications.front()->getJankType()) {
+ if (includeJankData) {
+ std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame =
+ mPendingJankClassifications.front();
+ jankData.emplace_back(
+ JankData(surfaceFrame->getToken(), surfaceFrame->getJankType().value()));
+ }
+ mPendingJankClassifications.pop_front();
+ }
+}
+
// ----------------------------------------------------------------------------
// transaction
// ----------------------------------------------------------------------------
@@ -2694,12 +2730,13 @@
void Layer::callReleaseBufferCallback(const sp<ITransactionCompletedListener>& listener,
const sp<GraphicBuffer>& buffer, uint64_t framenumber,
- const sp<Fence>& releaseFence,
- uint32_t currentMaxAcquiredBufferCount) {
+ const sp<Fence>& releaseFence) {
if (!listener) {
return;
}
ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64, getDebugName(), framenumber);
+ uint32_t currentMaxAcquiredBufferCount =
+ mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mOwnerUid);
listener->onReleaseBuffer({buffer->getId(), framenumber},
releaseFence ? releaseFence : Fence::NO_FENCE,
currentMaxAcquiredBufferCount);
@@ -2794,16 +2831,7 @@
}
std::vector<JankData> jankData;
- jankData.reserve(mPendingJankClassifications.size());
- while (!mPendingJankClassifications.empty() &&
- mPendingJankClassifications.front()->getJankType()) {
- std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame =
- mPendingJankClassifications.front();
- mPendingJankClassifications.pop_front();
- jankData.emplace_back(
- JankData(surfaceFrame->getToken(), surfaceFrame->getJankType().value()));
- }
-
+ transferAvailableJankData(mDrawingState.callbackHandles, jankData);
mFlinger->getTransactionCallbackInvoker().addCallbackHandles(mDrawingState.callbackHandles,
jankData);
mDrawingState.callbackHandles = {};
@@ -2959,9 +2987,7 @@
// call any release buffer callbacks if set.
callReleaseBufferCallback(mDrawingState.releaseBufferListener,
mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber,
- mDrawingState.acquireFence,
- mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(
- mOwnerUid));
+ mDrawingState.acquireFence);
decrementPendingBufferCount();
if (mDrawingState.bufferSurfaceFrameTX != nullptr &&
mDrawingState.bufferSurfaceFrameTX->getPresentState() != PresentState::Presented) {
@@ -2971,13 +2997,12 @@
} else if (EARLY_RELEASE_ENABLED && mLastClientCompositionFence != nullptr) {
callReleaseBufferCallback(mDrawingState.releaseBufferListener,
mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber,
- mLastClientCompositionFence,
- mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(
- mOwnerUid));
+ mLastClientCompositionFence);
mLastClientCompositionFence = nullptr;
}
}
+ mDrawingState.producerId = bufferData.producerId;
mDrawingState.frameNumber = frameNumber;
mDrawingState.releaseBufferListener = bufferData.releaseBufferListener;
mDrawingState.buffer = std::move(buffer);
@@ -3041,6 +3066,17 @@
return true;
}
+bool Layer::setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio) {
+ if (mDrawingState.currentSdrHdrRatio == currentBufferRatio &&
+ mDrawingState.desiredSdrHdrRatio == desiredRatio)
+ return false;
+ mDrawingState.currentSdrHdrRatio = currentBufferRatio;
+ mDrawingState.desiredSdrHdrRatio = desiredRatio;
+ mDrawingState.modified = true;
+ setTransactionFlags(eTransactionNeeded);
+ return true;
+}
+
bool Layer::setHdrMetadata(const HdrMetadata& hdrMetadata) {
if (mDrawingState.hdrMetadata == hdrMetadata) return false;
mDrawingState.hdrMetadata = hdrMetadata;
@@ -3083,15 +3119,15 @@
return true;
}
-bool Layer::setTransactionCompletedListeners(const std::vector<sp<CallbackHandle>>& handles) {
+bool Layer::setTransactionCompletedListeners(const std::vector<sp<CallbackHandle>>& handles,
+ bool willPresent) {
// If there is no handle, we will not send a callback so reset mReleasePreviousBuffer and return
if (handles.empty()) {
mReleasePreviousBuffer = false;
return false;
}
- const bool willPresent = willPresentCurrentTransaction();
-
+ std::deque<sp<CallbackHandle>> remainingHandles;
for (const auto& handle : handles) {
// If this transaction set a buffer on this layer, release its previous buffer
handle->releasePreviousBuffer = mReleasePreviousBuffer;
@@ -3106,11 +3142,19 @@
mDrawingState.callbackHandles.push_back(handle);
} else { // If this layer will NOT need to be relatched and presented this frame
- // Notify the transaction completed thread this handle is done
- mFlinger->getTransactionCallbackInvoker().registerUnpresentedCallbackHandle(handle);
+ // Queue this handle to be notified below.
+ remainingHandles.push_back(handle);
}
}
+ if (!remainingHandles.empty()) {
+ // Notify the transaction completed threads these handles are done. These are only the
+ // handles that were not added to the mDrawingState, which will be notified later.
+ std::vector<JankData> jankData;
+ transferAvailableJankData(remainingHandles, jankData);
+ mFlinger->getTransactionCallbackInvoker().addCallbackHandles(remainingHandles, jankData);
+ }
+
mReleasePreviousBuffer = false;
mCallbackHandleAcquireTimeOrFence = -1;
@@ -3165,11 +3209,10 @@
return fenceSignaled;
}
-bool Layer::onPreComposition(nsecs_t refreshStartTime) {
+void Layer::onPreComposition(nsecs_t refreshStartTime) {
for (const auto& handle : mDrawingState.callbackHandles) {
handle->refreshStartTime = refreshStartTime;
}
- return hasReadyFrame();
}
void Layer::setAutoRefresh(bool autoRefresh) {
@@ -3272,7 +3315,11 @@
auto lastDataspace = mBufferInfo.mDataspace;
mBufferInfo.mDataspace = translateDataspace(mDrawingState.dataspace);
if (lastDataspace != mBufferInfo.mDataspace) {
- mFlinger->mSomeDataspaceChanged = true;
+ mFlinger->mHdrLayerInfoChanged = true;
+ }
+ if (mBufferInfo.mDesiredSdrHdrRatio != mDrawingState.desiredSdrHdrRatio) {
+ mBufferInfo.mDesiredSdrHdrRatio = mDrawingState.desiredSdrHdrRatio;
+ mFlinger->mHdrLayerInfoChanged = true;
}
mBufferInfo.mCrop = computeBufferCrop(mDrawingState);
mBufferInfo.mScaleMode = NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW;
@@ -3302,39 +3349,6 @@
return layer;
}
-bool Layer::bufferNeedsFiltering() const {
- const State& s(getDrawingState());
- if (!s.buffer) {
- return false;
- }
-
- int32_t bufferWidth = static_cast<int32_t>(s.buffer->getWidth());
- int32_t bufferHeight = static_cast<int32_t>(s.buffer->getHeight());
-
- // Undo any transformations on the buffer and return the result.
- if (s.bufferTransform & ui::Transform::ROT_90) {
- std::swap(bufferWidth, bufferHeight);
- }
-
- if (s.transformToDisplayInverse) {
- uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags();
- if (invTransform & ui::Transform::ROT_90) {
- std::swap(bufferWidth, bufferHeight);
- }
- }
-
- const Rect layerSize{getBounds()};
- int32_t layerWidth = layerSize.getWidth();
- int32_t layerHeight = layerSize.getHeight();
-
- // Align the layer orientation with the buffer before comparism
- if (mTransformHint & ui::Transform::ROT_90) {
- std::swap(layerWidth, layerHeight);
- }
-
- return layerWidth != bufferWidth || layerHeight != bufferHeight;
-}
-
void Layer::decrementPendingBufferCount() {
int32_t pendingBuffers = --mPendingBufferTransactions;
tracePendingBufferCount(pendingBuffers);
@@ -3563,6 +3577,14 @@
}
}
+ if (s.what & layer_state_t::eExtendedRangeBrightnessChanged) {
+ if (mDrawingState.currentSdrHdrRatio != s.currentSdrHdrRatio ||
+ mDrawingState.desiredSdrHdrRatio != s.desiredSdrHdrRatio) {
+ ALOGV("%s: false [eDimmingEnabledChanged changed]", __func__);
+ return false;
+ }
+ }
+
ALOGV("%s: true", __func__);
return true;
}
@@ -3576,7 +3598,7 @@
sp<LayerFE> Layer::getCompositionEngineLayerFE() const {
// There's no need to get a CE Layer if the layer isn't going to draw anything.
- return hasSomethingToDraw() ? mLayerFE : nullptr;
+ return hasSomethingToDraw() ? mLegacyLayerFE : nullptr;
}
const LayerSnapshot* Layer::getLayerSnapshot() const {
@@ -3587,16 +3609,36 @@
return mSnapshot.get();
}
+std::unique_ptr<frontend::LayerSnapshot> Layer::stealLayerSnapshot() {
+ return std::move(mSnapshot);
+}
+
+void Layer::updateLayerSnapshot(std::unique_ptr<frontend::LayerSnapshot> snapshot) {
+ mSnapshot = std::move(snapshot);
+}
+
const compositionengine::LayerFECompositionState* Layer::getCompositionState() const {
return mSnapshot.get();
}
sp<LayerFE> Layer::copyCompositionEngineLayerFE() const {
- auto result = mFlinger->getFactory().createLayerFE(mLayerFE->getDebugName());
+ auto result = mFlinger->getFactory().createLayerFE(mName);
result->mSnapshot = std::make_unique<LayerSnapshot>(*mSnapshot);
return result;
}
+sp<LayerFE> Layer::getCompositionEngineLayerFE(
+ const frontend::LayerHierarchy::TraversalPath& path) {
+ for (auto& [p, layerFE] : mLayerFEs) {
+ if (p == path) {
+ return layerFE;
+ }
+ }
+ auto layerFE = mFlinger->getFactory().createLayerFE(mName);
+ mLayerFEs.emplace_back(path, layerFE);
+ return layerFE;
+}
+
void Layer::useSurfaceDamage() {
if (mFlinger->mForceFullDamage) {
surfaceDamageRegion = Region::INVALID_REGION;
@@ -3796,54 +3838,6 @@
(mBufferInfo.mBuffer->getUsage() & GRALLOC_USAGE_PROTECTED);
}
-bool Layer::needsFiltering(const DisplayDevice* display) const {
- if (!hasBufferOrSidebandStream()) {
- return false;
- }
- const auto outputLayer = findOutputLayerForDisplay(display);
- if (outputLayer == nullptr) {
- return false;
- }
-
- // We need filtering if the sourceCrop rectangle size does not match the
- // displayframe rectangle size (not a 1:1 render)
- const auto& compositionState = outputLayer->getState();
- const auto displayFrame = compositionState.displayFrame;
- const auto sourceCrop = compositionState.sourceCrop;
- return sourceCrop.getHeight() != displayFrame.getHeight() ||
- sourceCrop.getWidth() != displayFrame.getWidth();
-}
-
-bool Layer::needsFilteringForScreenshots(const DisplayDevice* display,
- const ui::Transform& inverseParentTransform) const {
- if (!hasBufferOrSidebandStream()) {
- return false;
- }
- const auto outputLayer = findOutputLayerForDisplay(display);
- if (outputLayer == nullptr) {
- return false;
- }
-
- // We need filtering if the sourceCrop rectangle size does not match the
- // viewport rectangle size (not a 1:1 render)
- const auto& compositionState = outputLayer->getState();
- const ui::Transform& displayTransform = display->getTransform();
- const ui::Transform inverseTransform = inverseParentTransform * displayTransform.inverse();
- // Undo the transformation of the displayFrame so that we're back into
- // layer-stack space.
- const Rect frame = inverseTransform.transform(compositionState.displayFrame);
- const FloatRect sourceCrop = compositionState.sourceCrop;
-
- int32_t frameHeight = frame.getHeight();
- int32_t frameWidth = frame.getWidth();
- // If the display transform had a rotational component then undo the
- // rotation so that the orientation matches the source crop.
- if (displayTransform.getOrientation() & ui::Transform::ROT_90) {
- std::swap(frameHeight, frameWidth);
- }
- return sourceCrop.getHeight() != frameHeight || sourceCrop.getWidth() != frameWidth;
-}
-
void Layer::latchAndReleaseBuffer() {
if (hasReadyFrame()) {
bool ignored = false;
@@ -3989,7 +3983,6 @@
snapshot->layerOpaqueFlagSet =
(mDrawingState.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque;
snapshot->isHdrY410 = isHdrY410();
- snapshot->bufferNeedsFiltering = bufferNeedsFiltering();
sp<Layer> p = mDrawingParent.promote();
if (p != nullptr) {
snapshot->parentTransform = p->getTransform();
@@ -4041,28 +4034,6 @@
}
}
-LayerSnapshotGuard::LayerSnapshotGuard(Layer* layer) : mLayer(layer) {
- if (mLayer) {
- mLayer->mLayerFE->mSnapshot = std::move(mLayer->mSnapshot);
- }
-}
-
-LayerSnapshotGuard::~LayerSnapshotGuard() {
- if (mLayer) {
- mLayer->mSnapshot = std::move(mLayer->mLayerFE->mSnapshot);
- }
-}
-
-LayerSnapshotGuard::LayerSnapshotGuard(LayerSnapshotGuard&& other) : mLayer(other.mLayer) {
- other.mLayer = nullptr;
-}
-
-LayerSnapshotGuard& LayerSnapshotGuard::operator=(LayerSnapshotGuard&& other) {
- mLayer = other.mLayer;
- other.mLayer = nullptr;
- return *this;
-}
-
void Layer::setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds,
TrustedPresentationListener const& listener) {
bool hadTrustedPresentationListener = hasTrustedPresentationListener();
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 7631f5d..bf8cc7e 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -141,6 +141,8 @@
uint64_t frameNumber;
ui::Transform transform;
+
+ uint32_t producerId = 0;
uint32_t bufferTransform;
bool transformToDisplayInverse;
Region transparentRegionHint;
@@ -223,6 +225,8 @@
gui::DropInputMode dropInputMode;
bool autoRefresh = false;
bool dimmingEnabled = true;
+ float currentSdrHdrRatio = 1.f;
+ float desiredSdrHdrRatio = 1.f;
};
explicit Layer(const LayerCreationArgs& args);
@@ -289,7 +293,9 @@
virtual mat4 getColorTransform() const;
virtual bool hasColorTransform() const;
virtual bool isColorSpaceAgnostic() const { return mDrawingState.colorSpaceAgnostic; }
- virtual bool isDimmingEnabled() const { return getDrawingState().dimmingEnabled; };
+ virtual bool isDimmingEnabled() const { return getDrawingState().dimmingEnabled; }
+ float getDesiredSdrHdrRatio() const { return getDrawingState().desiredSdrHdrRatio; }
+ float getCurrentSdrHdrRatio() const { return getDrawingState().currentSdrHdrRatio; }
bool setTransform(uint32_t /*transform*/);
bool setTransformToDisplayInverse(bool /*transformToDisplayInverse*/);
@@ -298,11 +304,13 @@
nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
std::optional<nsecs_t> /* dequeueTime */, const FrameTimelineInfo& /*info*/);
bool setDataspace(ui::Dataspace /*dataspace*/);
+ bool setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio);
bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/);
bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/);
bool setApi(int32_t /*api*/);
bool setSidebandStream(const sp<NativeHandle>& /*sidebandStream*/);
- bool setTransactionCompletedListeners(const std::vector<sp<CallbackHandle>>& /*handles*/);
+ bool setTransactionCompletedListeners(const std::vector<sp<CallbackHandle>>& /*handles*/,
+ bool willPresent);
virtual bool setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace);
virtual bool setColorSpaceAgnostic(const bool agnostic);
virtual bool setDimmingEnabled(const bool dimmingEnabled);
@@ -323,9 +331,12 @@
virtual sp<LayerFE> getCompositionEngineLayerFE() const;
virtual sp<LayerFE> copyCompositionEngineLayerFE() const;
+ sp<LayerFE> getCompositionEngineLayerFE(const frontend::LayerHierarchy::TraversalPath&);
const frontend::LayerSnapshot* getLayerSnapshot() const;
frontend::LayerSnapshot* editLayerSnapshot();
+ std::unique_ptr<frontend::LayerSnapshot> stealLayerSnapshot();
+ void updateLayerSnapshot(std::unique_ptr<frontend::LayerSnapshot> snapshot);
// If we have received a new buffer this frame, we will pass its surface
// damage down to hardware composer. Otherwise, we must send a region with
@@ -499,6 +510,7 @@
uint64_t mFrameNumber;
bool mFrameLatencyNeeded{false};
+ float mDesiredSdrHdrRatio = 1.f;
};
BufferInfo mBufferInfo;
@@ -506,7 +518,7 @@
// implements compositionengine::LayerFE
const compositionengine::LayerFECompositionState* getCompositionState() const;
bool fenceHasSignaled() const;
- bool onPreComposition(nsecs_t refreshStartTime);
+ void onPreComposition(nsecs_t refreshStartTime);
void onLayerDisplayed(ftl::SharedFuture<FenceResult>);
void setWasClientComposed(const sp<Fence>& fence) {
@@ -533,7 +545,8 @@
const FloatRect& screenBounds, float,
const ui::Transform&,
const TrustedPresentationThresholds&);
- void updateTrustedPresentationState(const DisplayDevice* display, int64_t time_in_ms,
+ void updateTrustedPresentationState(const DisplayDevice* display,
+ const frontend::LayerSnapshot* snapshot, int64_t time_in_ms,
bool leaveState);
inline bool hasTrustedPresentationListener() {
@@ -825,6 +838,11 @@
void updateMetadataSnapshot(const LayerMetadata& parentMetadata);
void updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata,
std::unordered_set<Layer*>& visited);
+ bool willPresentCurrentTransaction() const;
+
+ void callReleaseBufferCallback(const sp<ITransactionCompletedListener>& listener,
+ const sp<GraphicBuffer>& buffer, uint64_t framenumber,
+ const sp<Fence>& releaseFence);
protected:
// For unit tests
@@ -1030,17 +1048,15 @@
// Crop that applies to the buffer
Rect computeBufferCrop(const State& s);
- bool willPresentCurrentTransaction() const;
-
- // Returns true if the transformed buffer size does not match the layer size and we need
- // to apply filtering.
- bool bufferNeedsFiltering() const;
-
void callReleaseBufferCallback(const sp<ITransactionCompletedListener>& listener,
const sp<GraphicBuffer>& buffer, uint64_t framenumber,
const sp<Fence>& releaseFence,
uint32_t currentMaxAcquiredBufferCount);
+ // Returns true if the transformed buffer size does not match the layer size and we need
+ // to apply filtering.
+ bool bufferNeedsFiltering() const;
+
// Returns true if there is a valid color to fill.
bool fillsColor() const;
// Returns true if this layer has a blur value.
@@ -1058,6 +1074,11 @@
void updateChildrenSnapshots(bool updateGeometry);
+ // Fills the provided vector with the currently available JankData and removes the processed
+ // JankData from the pending list.
+ void transferAvailableJankData(const std::deque<sp<CallbackHandle>>& handles,
+ std::vector<JankData>& jankData);
+
// Cached properties computed from drawing state
// Effective transform taking into account parent transforms and any parent scaling, which is
// a transform from the current layer coordinate space to display(screen) coordinate space.
@@ -1143,34 +1164,10 @@
// not specify a destination frame.
ui::Transform mRequestedTransform;
- sp<LayerFE> mLayerFE;
+ sp<LayerFE> mLegacyLayerFE;
+ std::vector<std::pair<frontend::LayerHierarchy::TraversalPath, sp<LayerFE>>> mLayerFEs;
std::unique_ptr<frontend::LayerSnapshot> mSnapshot =
std::make_unique<frontend::LayerSnapshot>();
-
- friend class LayerSnapshotGuard;
-};
-
-// LayerSnapshotGuard manages the movement of LayerSnapshot between a Layer and its corresponding
-// LayerFE. This class must be used whenever LayerFEs are passed to CompositionEngine. Instances of
-// LayerSnapshotGuard should only be constructed on the main thread and should not be moved outside
-// the main thread.
-//
-// Moving the snapshot instead of sharing common state prevents use of LayerFE outside the main
-// thread by making errors obvious (i.e. use outside the main thread results in SEGFAULTs due to
-// nullptr dereference).
-class LayerSnapshotGuard {
-public:
- LayerSnapshotGuard(Layer* layer) REQUIRES(kMainThreadContext);
- ~LayerSnapshotGuard() REQUIRES(kMainThreadContext);
-
- LayerSnapshotGuard(const LayerSnapshotGuard&) = delete;
- LayerSnapshotGuard& operator=(const LayerSnapshotGuard&) = delete;
-
- LayerSnapshotGuard(LayerSnapshotGuard&& other) REQUIRES(kMainThreadContext);
- LayerSnapshotGuard& operator=(LayerSnapshotGuard&& other) REQUIRES(kMainThreadContext);
-
-private:
- Layer* mLayer;
};
std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate);
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index c31a2e3..b9c8b78 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -249,14 +249,11 @@
layerSettings.frameNumber = mSnapshot->frameNumber;
layerSettings.bufferId = mSnapshot->externalTexture->getId();
- const bool useFiltering = targetSettings.needsFiltering ||
- mSnapshot->geomLayerTransform.needsBilinearFiltering() ||
- mSnapshot->bufferNeedsFiltering;
-
// Query the texture matrix given our current filtering mode.
float textureMatrix[16];
getDrawingTransformMatrix(layerSettings.source.buffer.buffer, mSnapshot->geomContentCrop,
- mSnapshot->geomBufferTransform, useFiltering, textureMatrix);
+ mSnapshot->geomBufferTransform, targetSettings.needsFiltering,
+ textureMatrix);
if (mSnapshot->geomBufferUsesDisplayInverseTransform) {
/*
@@ -306,7 +303,7 @@
mat4::translate(vec4(translateX, translateY, 0.f, 1.f)) *
mat4::scale(vec4(scaleWidth, scaleHeight, 1.0f, 1.0f));
- layerSettings.source.buffer.useTextureFiltering = useFiltering;
+ layerSettings.source.buffer.useTextureFiltering = targetSettings.needsFiltering;
layerSettings.source.buffer.textureTransform =
mat4(static_cast<const float*>(textureMatrix)) * tr;
diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp
index 554fae4..03a7f22 100644
--- a/services/surfaceflinger/LayerRenderArea.cpp
+++ b/services/surfaceflinger/LayerRenderArea.cpp
@@ -39,8 +39,8 @@
LayerRenderArea::LayerRenderArea(SurfaceFlinger& flinger, sp<Layer> layer, const Rect& crop,
ui::Size reqSize, ui::Dataspace reqDataSpace, bool childrenOnly,
- const Rect& layerStackRect, bool allowSecureLayers)
- : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, layerStackRect, allowSecureLayers),
+ bool allowSecureLayers)
+ : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, allowSecureLayers),
mLayer(std::move(layer)),
mCrop(crop),
mFlinger(flinger),
@@ -50,33 +50,17 @@
return mTransform;
}
-Rect LayerRenderArea::getBounds() const {
- return mLayer->getBufferSize(mLayer->getDrawingState());
-}
-
-int LayerRenderArea::getHeight() const {
- return mLayer->getBufferSize(mLayer->getDrawingState()).getHeight();
-}
-
-int LayerRenderArea::getWidth() const {
- return mLayer->getBufferSize(mLayer->getDrawingState()).getWidth();
-}
-
bool LayerRenderArea::isSecure() const {
return mAllowSecureLayers;
}
-bool LayerRenderArea::needsFiltering() const {
- return mNeedsFiltering;
-}
-
sp<const DisplayDevice> LayerRenderArea::getDisplayDevice() const {
return nullptr;
}
Rect LayerRenderArea::getSourceCrop() const {
if (mCrop.isEmpty()) {
- return getBounds();
+ return mLayer->getBufferSize(mLayer->getDrawingState());
} else {
return mCrop;
}
@@ -85,10 +69,14 @@
void LayerRenderArea::render(std::function<void()> drawLayers) {
using namespace std::string_literals;
- const Rect sourceCrop = getSourceCrop();
- // no need to check rotation because there is none
- mNeedsFiltering = sourceCrop.width() != getReqWidth() || sourceCrop.height() != getReqHeight();
+ if (!mChildrenOnly) {
+ mTransform = mLayer->getTransform().inverse();
+ }
+ if (mFlinger.mLayerLifecycleManagerEnabled) {
+ drawLayers();
+ return;
+ }
// If layer is offscreen, update mirroring info if it exists
if (mLayer->isRemovedFromCurrentState()) {
mLayer->traverse(LayerVector::StateSet::Drawing,
@@ -98,7 +86,6 @@
}
if (!mChildrenOnly) {
- mTransform = mLayer->getTransform().inverse();
// If the layer is offscreen, compute bounds since we don't compute bounds for offscreen
// layers in a regular cycles.
if (mLayer->isRemovedFromCurrentState()) {
@@ -116,7 +103,7 @@
LayerMetadata()});
{
Mutex::Autolock _l(mFlinger.mStateLock);
- reparentForDrawing(mLayer, screenshotParentLayer, sourceCrop);
+ reparentForDrawing(mLayer, screenshotParentLayer, getSourceCrop());
}
drawLayers();
{
diff --git a/services/surfaceflinger/LayerRenderArea.h b/services/surfaceflinger/LayerRenderArea.h
index 41273e0..322dbd1 100644
--- a/services/surfaceflinger/LayerRenderArea.h
+++ b/services/surfaceflinger/LayerRenderArea.h
@@ -33,15 +33,10 @@
class LayerRenderArea : public RenderArea {
public:
LayerRenderArea(SurfaceFlinger& flinger, sp<Layer> layer, const Rect& crop, ui::Size reqSize,
- ui::Dataspace reqDataSpace, bool childrenOnly, const Rect& layerStackRect,
- bool allowSecureLayers);
+ ui::Dataspace reqDataSpace, bool childrenOnly, bool allowSecureLayers);
const ui::Transform& getTransform() const override;
- Rect getBounds() const override;
- int getHeight() const override;
- int getWidth() const override;
bool isSecure() const override;
- bool needsFiltering() const override;
sp<const DisplayDevice> getDisplayDevice() const override;
Rect getSourceCrop() const override;
@@ -53,7 +48,6 @@
const Rect mCrop;
ui::Transform mTransform;
- bool mNeedsFiltering = false;
SurfaceFlinger& mFlinger;
const bool mChildrenOnly;
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index 6e64e0a..839500f 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -344,12 +344,13 @@
renderengine::impl::ExternalTexture::Usage::
WRITEABLE);
}
+ auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
constexpr bool kRegionSampling = true;
constexpr bool kGrayscale = false;
if (const auto fenceResult =
- mFlinger.captureScreenCommon(std::move(renderAreaFuture), traverseLayers, buffer,
+ mFlinger.captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, buffer,
kRegionSampling, kGrayscale, nullptr)
.get();
fenceResult.ok()) {
diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h
index 387364c..910fce0 100644
--- a/services/surfaceflinger/RenderArea.h
+++ b/services/surfaceflinger/RenderArea.h
@@ -25,14 +25,27 @@
static float getCaptureFillValue(CaptureFill captureFill);
RenderArea(ui::Size reqSize, CaptureFill captureFill, ui::Dataspace reqDataSpace,
- const Rect& layerStackRect, bool allowSecureLayers = false,
- RotationFlags rotation = ui::Transform::ROT_0)
+ bool allowSecureLayers = false, RotationFlags rotation = ui::Transform::ROT_0)
: mAllowSecureLayers(allowSecureLayers),
mReqSize(reqSize),
mReqDataSpace(reqDataSpace),
mCaptureFill(captureFill),
- mRotationFlags(rotation),
- mLayerStackSpaceRect(layerStackRect) {}
+ mRotationFlags(rotation) {}
+
+ static std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> fromTraverseLayersLambda(
+ std::function<void(const LayerVector::Visitor&)> traverseLayers) {
+ return [traverseLayers = std::move(traverseLayers)]() {
+ std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
+ traverseLayers([&](Layer* layer) {
+ // Layer::prepareClientComposition uses the layer's snapshot to populate the
+ // resulting LayerSettings. Calling Layer::updateSnapshot ensures that LayerSettings
+ // are generated with the layer's current buffer and geometry.
+ layer->updateSnapshot(true /* updateGeometry */);
+ layers.emplace_back(layer, layer->copyCompositionEngineLayerFE());
+ });
+ return layers;
+ };
+ }
virtual ~RenderArea() = default;
@@ -43,20 +56,10 @@
// blacked out / skipped when rendered to an insecure render area.
virtual bool isSecure() const = 0;
- // Returns true if the otherwise disabled layer filtering should be
- // enabled when rendering to this render area.
- virtual bool needsFiltering() const = 0;
-
// Returns the transform to be applied on layers to transform them into
// the logical render area.
virtual const ui::Transform& getTransform() const = 0;
- // Returns the size of the logical render area. Layers are clipped to the
- // logical render area.
- virtual int getWidth() const = 0;
- virtual int getHeight() const = 0;
- virtual Rect getBounds() const = 0;
-
// Returns the source crop of the render area. The source crop defines
// how layers are projected from the logical render area onto the physical
// render area. It can be larger than the logical render area. It can
@@ -83,9 +86,6 @@
virtual sp<const DisplayDevice> getDisplayDevice() const = 0;
- // Returns the source display viewport.
- const Rect& getLayerStackSpaceRect() const { return mLayerStackSpaceRect; }
-
// If this is a LayerRenderArea, return the root layer of the
// capture operation.
virtual sp<Layer> getParentLayer() const { return nullptr; }
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index a902a8e..eb6d7e4 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -541,6 +541,13 @@
bool EventThread::shouldConsumeEvent(const DisplayEventReceiver::Event& event,
const sp<EventThreadConnection>& connection) const {
const auto throttleVsync = [&] {
+ const auto& vsyncData = event.vsync.vsyncData;
+ if (connection->frameRate.isValid()) {
+ return !mVsyncSchedule.getTracker()
+ .isVSyncInPhase(vsyncData.preferredExpectedPresentationTime(),
+ connection->frameRate);
+ }
+
return mThrottleVsyncCallback &&
mThrottleVsyncCallback(event.vsync.vsyncData.preferredExpectedPresentationTime(),
connection->mOwnerUid);
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index ab9085e..347dc4a 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -87,6 +87,9 @@
const uid_t mOwnerUid;
const EventRegistrationFlags mEventRegistration;
+ /** The frame rate set to the attached choreographer. */
+ Fps frameRate;
+
private:
virtual void onFirstRef();
EventThread* const mEventThread;
diff --git a/services/surfaceflinger/Scheduler/ISchedulerCallback.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
new file mode 100644
index 0000000..c4de749
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include "Display/DisplayModeRequest.h"
+
+namespace android::scheduler {
+
+struct ISchedulerCallback {
+ virtual void setVsyncEnabled(bool) = 0;
+ virtual void requestDisplayModes(std::vector<display::DisplayModeRequest>) = 0;
+ virtual void kernelTimerChanged(bool expired) = 0;
+ virtual void triggerOnFrameRateOverridesChanged() = 0;
+
+protected:
+ ~ISchedulerCallback() = default;
+};
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 55fa402..e853833 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -32,6 +32,7 @@
#include <utility>
#include "../Layer.h"
+#include "EventThread.h"
#include "LayerInfo.h"
namespace android::scheduler {
@@ -140,6 +141,22 @@
info->setLastPresentTime(presentTime, now, updateType, mModeChangePending, layerProps);
+ // Set frame rate to attached choreographer.
+ // TODO(b/260898223): Change to use layer hierarchy and handle frame rate vote.
+ if (updateType == LayerUpdateType::SetFrameRate) {
+ auto range = mAttachedChoreographers.equal_range(id);
+ auto it = range.first;
+ while (it != range.second) {
+ sp<EventThreadConnection> choreographerConnection = it->second.promote();
+ if (choreographerConnection) {
+ choreographerConnection->frameRate = layer->getFrameRateForLayerTree().rate;
+ it++;
+ } else {
+ it = mAttachedChoreographers.erase(it);
+ }
+ }
+ }
+
// Activate layer if inactive.
if (found == LayerStatus::LayerInInactiveMap) {
mActiveLayerInfos.insert(
@@ -294,6 +311,12 @@
return 0.f;
}
+void LayerHistory::attachChoreographer(int32_t layerId,
+ const sp<EventThreadConnection>& choreographerConnection) {
+ std::lock_guard lock(mLock);
+ mAttachedChoreographers.insert({layerId, wp<EventThreadConnection>(choreographerConnection)});
+}
+
auto LayerHistory::findLayer(int32_t id) -> std::pair<LayerStatus, LayerPair*> {
// the layer could be in either the active or inactive map, try both
auto it = mActiveLayerInfos.find(id);
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 5022906..68e7030 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -27,6 +27,8 @@
#include <utility>
#include <vector>
+#include "EventThread.h"
+
#include "RefreshRateSelector.h"
namespace android {
@@ -80,6 +82,9 @@
// return the frames per second of the layer with the given sequence id.
float getLayerFramerate(nsecs_t now, int32_t id) const;
+ void attachChoreographer(int32_t layerId,
+ const sp<EventThreadConnection>& choreographerConnection);
+
private:
friend class LayerHistoryTest;
friend class TestableScheduler;
@@ -117,6 +122,10 @@
LayerInfos mActiveLayerInfos GUARDED_BY(mLock);
LayerInfos mInactiveLayerInfos GUARDED_BY(mLock);
+ // Map keyed by layer ID (sequence) to choreographer connections.
+ std::unordered_multimap<int32_t, wp<EventThreadConnection>> mAttachedChoreographers
+ GUARDED_BY(mLock);
+
uint32_t mDisplayArea = 0;
// Whether to emit systrace output and debug logs.
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp
index 9b04497..dec8f59 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.cpp
+++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp
@@ -17,12 +17,12 @@
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include <binder/IPCThreadState.h>
-
+#include <gui/DisplayEventReceiver.h>
#include <utils/Log.h>
#include <utils/Timers.h>
#include <utils/threads.h>
-#include <gui/DisplayEventReceiver.h>
+#include <scheduler/interface/ICompositor.h>
#include "EventThread.h"
#include "FrameTimeline.h"
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h
index ad0ea72..0d59337 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.h
+++ b/services/surfaceflinger/Scheduler/MessageQueue.h
@@ -37,15 +37,7 @@
namespace android {
-struct ICompositor {
- virtual void configure() = 0;
- virtual bool commit(TimePoint frameTime, VsyncId, TimePoint expectedVsyncTime) = 0;
- virtual void composite(TimePoint frameTime, VsyncId) = 0;
- virtual void sample() = 0;
-
-protected:
- ~ICompositor() = default;
-};
+struct ICompositor;
template <typename F>
class Task : public MessageHandler {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 30821d8..f6fe468 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -238,7 +238,6 @@
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 (!ScoredFrameRate::scoresEqual(overallScore, rhs.overallScore)) {
return overallScore > rhs.overallScore;
@@ -929,7 +928,28 @@
RefreshRateOrder refreshRateOrder,
std::optional<DisplayModeId> preferredDisplayModeOpt) const
-> FrameRateRanking {
+ using fps_approx_ops::operator<;
const char* const whence = __func__;
+
+ // find the highest frame rate for each display mode
+ ftl::SmallMap<DisplayModeId, Fps, 8> maxRenderRateForMode;
+ const bool ascending = (refreshRateOrder == RefreshRateOrder::Ascending);
+ if (ascending) {
+ // TODO(b/266481656): Once this bug is fixed, we can remove this workaround and actually
+ // use a lower frame rate when we want Ascending frame rates.
+ for (const auto& frameRateMode : mPrimaryFrameRates) {
+ if (anchorGroupOpt && frameRateMode.modePtr->getGroup() != anchorGroupOpt) {
+ continue;
+ }
+
+ const auto [iter, _] = maxRenderRateForMode.try_emplace(frameRateMode.modePtr->getId(),
+ frameRateMode.fps);
+ if (iter->second < frameRateMode.fps) {
+ iter->second = frameRateMode.fps;
+ }
+ }
+ }
+
std::deque<ScoredFrameRate> ranking;
const auto rankFrameRate = [&](const FrameRateMode& frameRateMode) REQUIRES(mLock) {
const auto& modePtr = frameRateMode.modePtr;
@@ -937,9 +957,17 @@
return;
}
+ const bool ascending = (refreshRateOrder == RefreshRateOrder::Ascending);
+ const auto id = frameRateMode.modePtr->getId();
+ if (ascending && frameRateMode.fps < *maxRenderRateForMode.get(id)) {
+ // TODO(b/266481656): Once this bug is fixed, we can remove this workaround and actually
+ // use a lower frame rate when we want Ascending frame rates.
+ return;
+ }
+
float score = calculateDistanceScoreFromMax(frameRateMode.fps);
- const bool inverseScore = (refreshRateOrder == RefreshRateOrder::Ascending);
- if (inverseScore) {
+
+ if (ascending) {
score = 1.0f / score;
}
if (preferredDisplayModeOpt) {
@@ -951,6 +979,7 @@
constexpr float kNonPreferredModePenalty = 0.95f;
score *= kNonPreferredModePenalty;
}
+
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});
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 1fc1519..17cdff9 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -28,12 +28,14 @@
#include <ftl/enum.h>
#include <ftl/fake_guard.h>
#include <ftl/small_map.h>
+#include <gui/TraceUtils.h>
#include <gui/WindowInfo.h>
#include <system/window.h>
#include <utils/Timers.h>
-#include <utils/Trace.h>
#include <FrameTimeline/FrameTimeline.h>
+#include <scheduler/interface/ICompositor.h>
+
#include <algorithm>
#include <cinttypes>
#include <cstdint>
@@ -45,6 +47,7 @@
#include "Display/DisplayMap.h"
#include "EventThread.h"
#include "FrameRateOverrideMappings.h"
+#include "FrontEnd/LayerHandle.h"
#include "OneShotTimer.h"
#include "SurfaceFlingerProperties.h"
#include "VSyncPredictor.h"
@@ -125,6 +128,11 @@
std::scoped_lock lock(mDisplayLock);
mRefreshRateSelectors.erase(displayId);
+ // Do not allow removing the final display. Code in the scheduler expects
+ // there to be at least one display. (This may be relaxed in the future with
+ // headless virtual display.)
+ LOG_ALWAYS_FATAL_IF(mRefreshRateSelectors.empty(), "Cannot unregister all displays!");
+
promoteLeaderDisplay();
}
@@ -147,7 +155,7 @@
}
void Scheduler::createVsyncSchedule(FeatureFlags features) {
- mVsyncSchedule.emplace(features);
+ mVsyncSchedule = std::make_unique<VsyncSchedule>(features);
}
std::optional<Fps> Scheduler::getFrameRateOverride(uid_t uid) const {
@@ -163,9 +171,14 @@
return true;
}
+ ATRACE_FORMAT("%s uid: %d frameRate: %s", __func__, uid, to_string(*frameRate).c_str());
return mVsyncSchedule->getTracker().isVSyncInPhase(expectedVsyncTimestamp.ns(), *frameRate);
}
+bool Scheduler::isVsyncInPhase(TimePoint timePoint, const Fps frameRate) const {
+ return mVsyncSchedule->getTracker().isVSyncInPhase(timePoint.ns(), frameRate);
+}
+
impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() const {
return [this](nsecs_t expectedVsyncTimestamp, uid_t uid) {
return !isVsyncValid(TimePoint::fromNs(expectedVsyncTimestamp), uid);
@@ -217,15 +230,21 @@
}
sp<EventThreadConnection> Scheduler::createConnectionInternal(
- EventThread* eventThread, EventRegistrationFlags eventRegistration) {
- return eventThread->createEventConnection([&] { resync(); }, eventRegistration);
+ EventThread* eventThread, EventRegistrationFlags eventRegistration,
+ const sp<IBinder>& layerHandle) {
+ int32_t layerId = static_cast<int32_t>(LayerHandle::getLayerId(layerHandle));
+ auto connection = eventThread->createEventConnection([&] { resync(); }, eventRegistration);
+ mLayerHistory.attachChoreographer(layerId, connection);
+ return connection;
}
sp<IDisplayEventConnection> Scheduler::createDisplayEventConnection(
- ConnectionHandle handle, EventRegistrationFlags eventRegistration) {
+ ConnectionHandle handle, EventRegistrationFlags eventRegistration,
+ const sp<IBinder>& layerHandle) {
std::lock_guard<std::mutex> lock(mConnectionsLock);
RETURN_IF_INVALID_HANDLE(handle, nullptr);
- return createConnectionInternal(mConnections[handle].thread.get(), eventRegistration);
+ return createConnectionInternal(mConnections[handle].thread.get(), eventRegistration,
+ layerHandle);
}
sp<EventThreadConnection> Scheduler::getEventConnection(ConnectionHandle handle) {
@@ -254,7 +273,6 @@
thread = mConnections[handle].thread.get();
}
thread->onScreenAcquired();
- mScreenAcquired = true;
}
void Scheduler::onScreenReleased(ConnectionHandle handle) {
@@ -265,7 +283,6 @@
thread = mConnections[handle].thread.get();
}
thread->onScreenReleased();
- mScreenAcquired = false;
}
void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId) {
@@ -377,38 +394,17 @@
}
void Scheduler::enableHardwareVsync() {
- std::lock_guard<std::mutex> lock(mHWVsyncLock);
- if (!mPrimaryHWVsyncEnabled && mHWVsyncAvailable) {
- mVsyncSchedule->getTracker().resetModel();
- mSchedulerCallback.setVsyncEnabled(true);
- mPrimaryHWVsyncEnabled = true;
- }
+ mVsyncSchedule->enableHardwareVsync(mSchedulerCallback);
}
-void Scheduler::disableHardwareVsync(bool makeUnavailable) {
- std::lock_guard<std::mutex> lock(mHWVsyncLock);
- if (mPrimaryHWVsyncEnabled) {
- mSchedulerCallback.setVsyncEnabled(false);
- mPrimaryHWVsyncEnabled = false;
- }
- if (makeUnavailable) {
- mHWVsyncAvailable = false;
- }
+void Scheduler::disableHardwareVsync(bool disallow) {
+ mVsyncSchedule->disableHardwareVsync(mSchedulerCallback, disallow);
}
-void Scheduler::resyncToHardwareVsync(bool makeAvailable, Fps refreshRate) {
- {
- std::lock_guard<std::mutex> lock(mHWVsyncLock);
- if (makeAvailable) {
- mHWVsyncAvailable = makeAvailable;
- } else if (!mHWVsyncAvailable) {
- // Hardware vsync is not currently available, so abort the resync
- // attempt for now
- return;
- }
+void Scheduler::resyncToHardwareVsync(bool allowToEnable, Fps refreshRate) {
+ if (mVsyncSchedule->isHardwareVsyncAllowed(allowToEnable) && refreshRate.isValid()) {
+ mVsyncSchedule->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod());
}
-
- setVsyncPeriod(refreshRate.getPeriodNsecs());
}
void Scheduler::setRenderRate(Fps renderFrameRate) {
@@ -441,37 +437,12 @@
}
}
-void Scheduler::setVsyncPeriod(nsecs_t period) {
- if (period <= 0) return;
-
- std::lock_guard<std::mutex> lock(mHWVsyncLock);
- mVsyncSchedule->getController().startPeriodTransition(period);
-
- if (!mPrimaryHWVsyncEnabled) {
- mVsyncSchedule->getTracker().resetModel();
- mSchedulerCallback.setVsyncEnabled(true);
- mPrimaryHWVsyncEnabled = true;
- }
-}
-
-void Scheduler::addResyncSample(nsecs_t timestamp, std::optional<nsecs_t> hwcVsyncPeriod,
- bool* periodFlushed) {
- bool needsHwVsync = false;
- *periodFlushed = false;
- { // Scope for the lock
- std::lock_guard<std::mutex> lock(mHWVsyncLock);
- if (mPrimaryHWVsyncEnabled) {
- needsHwVsync =
- mVsyncSchedule->getController().addHwVsyncTimestamp(timestamp, hwcVsyncPeriod,
- periodFlushed);
- }
- }
-
- if (needsHwVsync) {
- enableHardwareVsync();
- } else {
- disableHardwareVsync(false);
- }
+bool Scheduler::addResyncSample(nsecs_t timestamp, std::optional<nsecs_t> hwcVsyncPeriodIn) {
+ const auto hwcVsyncPeriod = ftl::Optional(hwcVsyncPeriodIn).transform([](nsecs_t nanos) {
+ return Period::fromNs(nanos);
+ });
+ return mVsyncSchedule->addResyncSample(mSchedulerCallback, TimePoint::fromNs(timestamp),
+ hwcVsyncPeriod);
}
void Scheduler::addPresentFence(std::shared_ptr<FenceTime> fence) {
@@ -619,15 +590,6 @@
mFrameRateOverrideMappings.dump(dumper);
dumper.eol();
-
- {
- utils::Dumper::Section section(dumper, "Hardware VSYNC"sv);
-
- std::lock_guard lock(mHWVsyncLock);
- dumper.dump("screenAcquired"sv, mScreenAcquired.load());
- dumper.dump("hwVsyncAvailable"sv, mHWVsyncAvailable);
- dumper.dump("hwVsyncEnabled"sv, mPrimaryHWVsyncEnabled);
- }
}
void Scheduler::dumpVsync(std::string& out) const {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index ef7d0cf..a340919 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -43,6 +43,7 @@
#include "Display/DisplayModeRequest.h"
#include "EventThread.h"
#include "FrameRateOverrideMappings.h"
+#include "ISchedulerCallback.h"
#include "LayerHistory.h"
#include "MessageQueue.h"
#include "OneShotTimer.h"
@@ -92,16 +93,6 @@
using GlobalSignals = RefreshRateSelector::GlobalSignals;
-struct ISchedulerCallback {
- virtual void setVsyncEnabled(bool) = 0;
- virtual void requestDisplayModes(std::vector<display::DisplayModeRequest>) = 0;
- virtual void kernelTimerChanged(bool expired) = 0;
- virtual void triggerOnFrameRateOverridesChanged() = 0;
-
-protected:
- ~ISchedulerCallback() = default;
-};
-
class Scheduler : android::impl::MessageQueue {
using Impl = android::impl::MessageQueue;
@@ -158,7 +149,8 @@
std::chrono::nanoseconds readyDuration);
sp<IDisplayEventConnection> createDisplayEventConnection(
- ConnectionHandle, EventRegistrationFlags eventRegistration = {});
+ ConnectionHandle, EventRegistrationFlags eventRegistration = {},
+ const sp<IBinder>& layerHandle = nullptr);
sp<EventThreadConnection> getEventConnection(ConnectionHandle);
@@ -191,20 +183,20 @@
void setRenderRate(Fps);
void enableHardwareVsync();
- void disableHardwareVsync(bool makeUnavailable);
+ void disableHardwareVsync(bool disallow);
// Resyncs the scheduler to hardware vsync.
- // If makeAvailable is true, then hardware vsync will be turned on.
+ // If allowToEnable is true, then hardware vsync will be turned on.
// Otherwise, if hardware vsync is not already enabled then this method will
// no-op.
- void resyncToHardwareVsync(bool makeAvailable, Fps refreshRate);
+ void resyncToHardwareVsync(bool allowToEnable, Fps refreshRate);
void resync() EXCLUDES(mDisplayLock);
void forceNextResync() { mLastResyncTime = 0; }
- // Passes a vsync sample to VsyncController. periodFlushed will be true if
- // VsyncController detected that the vsync period changed, and false otherwise.
- void addResyncSample(nsecs_t timestamp, std::optional<nsecs_t> hwcVsyncPeriod,
- bool* periodFlushed);
+ // Passes a vsync sample to VsyncController. Returns true if
+ // VsyncController detected that the vsync period changed and false
+ // otherwise.
+ bool addResyncSample(nsecs_t timestamp, std::optional<nsecs_t> hwcVsyncPeriod);
void addPresentFence(std::shared_ptr<FenceTime>);
// Layers are registered on creation, and unregistered when the weak reference expires.
@@ -231,6 +223,9 @@
// for a given uid
bool isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const;
+ // Checks if a vsync timestamp is in phase for a frame rate
+ bool isVsyncInPhase(TimePoint timePoint, const Fps frameRate) const;
+
void dump(utils::Dumper&) const;
void dump(ConnectionHandle, std::string&) const;
void dumpVsync(std::string&) const;
@@ -262,6 +257,10 @@
return leaderSelectorPtr()->getActiveMode().fps.getPeriod();
}
+ Fps getLeaderRefreshRate() const EXCLUDES(mDisplayLock) {
+ return leaderSelectorPtr()->getActiveMode().fps;
+ }
+
// Returns the framerate of the layer with the given sequence ID
float getLayerFramerate(nsecs_t now, int32_t id) const {
return mLayerHistory.getLayerFramerate(now, id);
@@ -280,7 +279,8 @@
// Create a connection on the given EventThread.
ConnectionHandle createConnection(std::unique_ptr<EventThread>);
sp<EventThreadConnection> createConnectionInternal(
- EventThread*, EventRegistrationFlags eventRegistration = {});
+ EventThread*, EventRegistrationFlags eventRegistration = {},
+ const sp<IBinder>& layerHandle = nullptr);
// Update feature state machine to given state when corresponding timer resets or expires.
void kernelIdleTimerCallback(TimerState) EXCLUDES(mDisplayLock);
@@ -288,7 +288,6 @@
void touchTimerCallback(TimerState);
void displayPowerTimerCallback(TimerState);
- void setVsyncPeriod(nsecs_t period);
void setVsyncConfig(const VsyncConfig&, Period vsyncPeriod);
// Chooses a leader among the registered displays, unless `leaderIdOpt` is specified. The new
@@ -353,14 +352,10 @@
ConnectionHandle mAppConnectionHandle;
ConnectionHandle mSfConnectionHandle;
- mutable std::mutex mHWVsyncLock;
- bool mPrimaryHWVsyncEnabled GUARDED_BY(mHWVsyncLock) = false;
- bool mHWVsyncAvailable GUARDED_BY(mHWVsyncLock) = false;
-
std::atomic<nsecs_t> mLastResyncTime = 0;
const FeatureFlags mFeatures;
- std::optional<VsyncSchedule> mVsyncSchedule;
+ std::unique_ptr<VsyncSchedule> mVsyncSchedule;
// Shifts the VSYNC phase during certain transactions and refresh rate changes.
const sp<VsyncModulator> mVsyncModulator;
@@ -430,9 +425,6 @@
static constexpr std::chrono::nanoseconds MAX_VSYNC_APPLIED_TIME = 200ms;
FrameRateOverrideMappings mFrameRateOverrideMappings;
-
- // Keeps track of whether the screen is acquired for debug
- std::atomic<bool> mScreenAcquired = false;
};
} // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 02e12fd..f8cb323 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -31,8 +31,8 @@
#include <android-base/stringprintf.h>
#include <cutils/compiler.h>
#include <cutils/properties.h>
+#include <gui/TraceUtils.h>
#include <utils/Log.h>
-#include <utils/Trace.h>
#include "RefreshRateSelector.h"
#include "VSyncPredictor.h"
@@ -282,6 +282,13 @@
}
bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const {
+ const TimePoint now = TimePoint::now();
+ const auto getTimePointIn = [](TimePoint now, nsecs_t timePoint) -> float {
+ return ticks<std::milli, float>(TimePoint::fromNs(timePoint) - now);
+ };
+ ATRACE_FORMAT("%s timePoint in: %.2f divisor: %zu", __func__, getTimePointIn(now, timePoint),
+ divisor);
+
struct VsyncError {
nsecs_t vsyncTimestamp;
float error;
@@ -304,6 +311,7 @@
if (knownTimestampIter == mRateDivisorKnownTimestampMap.end()) {
const auto vsync = nextAnticipatedVSyncTimeFromLocked(justBeforeTimePoint);
mRateDivisorKnownTimestampMap[dividedPeriod] = vsync;
+ ATRACE_FORMAT_INSTANT("(first) knownVsync in: %.2f", getTimePointIn(now, vsync));
return true;
}
@@ -323,6 +331,8 @@
const auto minVsyncError = std::min_element(vsyncs.begin(), vsyncs.end());
mRateDivisorKnownTimestampMap[dividedPeriod] = minVsyncError->vsyncTimestamp;
+ ATRACE_FORMAT_INSTANT("knownVsync in: %.2f",
+ getTimePointIn(now, minVsyncError->vsyncTimestamp));
return std::abs(minVsyncError->vsyncTimestamp - timePoint) < period / 2;
}
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
index 95bc31f..5245556 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -16,11 +16,14 @@
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <ftl/fake_guard.h>
#include <scheduler/Fps.h>
#include <scheduler/Timer.h>
#include "VsyncSchedule.h"
+#include "ISchedulerCallback.h"
+#include "Utils/Dumper.h"
#include "VSyncDispatchTimerQueue.h"
#include "VSyncPredictor.h"
#include "VSyncReactor.h"
@@ -54,18 +57,16 @@
VsyncSchedule::VsyncSchedule(FeatureFlags features)
: mTracker(createTracker()),
mDispatch(createDispatch(*mTracker)),
- mController(createController(*mTracker, features)) {
- if (features.test(Feature::kTracePredictedVsync)) {
- mTracer = std::make_unique<PredictedVsyncTracer>(*mDispatch);
- }
-}
+ mController(createController(*mTracker, features)),
+ mTracer(features.test(Feature::kTracePredictedVsync)
+ ? std::make_unique<PredictedVsyncTracer>(*mDispatch)
+ : nullptr) {}
VsyncSchedule::VsyncSchedule(TrackerPtr tracker, DispatchPtr dispatch, ControllerPtr controller)
: mTracker(std::move(tracker)),
mDispatch(std::move(dispatch)),
mController(std::move(controller)) {}
-VsyncSchedule::VsyncSchedule(VsyncSchedule&&) = default;
VsyncSchedule::~VsyncSchedule() = default;
Period VsyncSchedule::period() const {
@@ -77,6 +78,16 @@
}
void VsyncSchedule::dump(std::string& out) const {
+ utils::Dumper dumper(out);
+ {
+ std::lock_guard<std::mutex> lock(mHwVsyncLock);
+ dumper.dump("hwVsyncState", ftl::enum_string(mHwVsyncState));
+
+ ftl::FakeGuard guard(kMainThreadContext);
+ dumper.dump("pendingHwVsyncState", ftl::enum_string(mPendingHwVsyncState));
+ dumper.eol();
+ }
+
out.append("VsyncController:\n");
mController->dump(out);
@@ -120,4 +131,67 @@
return reactor;
}
+void VsyncSchedule::startPeriodTransition(ISchedulerCallback& callback, Period period) {
+ std::lock_guard<std::mutex> lock(mHwVsyncLock);
+ mController->startPeriodTransition(period.ns());
+ enableHardwareVsyncLocked(callback);
+}
+
+bool VsyncSchedule::addResyncSample(ISchedulerCallback& callback, TimePoint timestamp,
+ ftl::Optional<Period> hwcVsyncPeriod) {
+ bool needsHwVsync = false;
+ bool periodFlushed = false;
+ {
+ std::lock_guard<std::mutex> lock(mHwVsyncLock);
+ if (mHwVsyncState == HwVsyncState::Enabled) {
+ needsHwVsync = mController->addHwVsyncTimestamp(timestamp.ns(),
+ hwcVsyncPeriod.transform(&Period::ns),
+ &periodFlushed);
+ }
+ }
+ if (needsHwVsync) {
+ enableHardwareVsync(callback);
+ } else {
+ disableHardwareVsync(callback, false /* disallow */);
+ }
+ return periodFlushed;
+}
+
+void VsyncSchedule::enableHardwareVsync(ISchedulerCallback& callback) {
+ std::lock_guard<std::mutex> lock(mHwVsyncLock);
+ enableHardwareVsyncLocked(callback);
+}
+
+void VsyncSchedule::enableHardwareVsyncLocked(ISchedulerCallback& callback) {
+ if (mHwVsyncState == HwVsyncState::Disabled) {
+ getTracker().resetModel();
+ callback.setVsyncEnabled(true);
+ mHwVsyncState = HwVsyncState::Enabled;
+ }
+}
+
+void VsyncSchedule::disableHardwareVsync(ISchedulerCallback& callback, bool disallow) {
+ std::lock_guard<std::mutex> lock(mHwVsyncLock);
+ if (mHwVsyncState == HwVsyncState::Enabled) {
+ callback.setVsyncEnabled(false);
+ }
+ mHwVsyncState = disallow ? HwVsyncState::Disallowed : HwVsyncState::Disabled;
+}
+
+bool VsyncSchedule::isHardwareVsyncAllowed(bool makeAllowed) {
+ std::lock_guard<std::mutex> lock(mHwVsyncLock);
+ if (makeAllowed && mHwVsyncState == HwVsyncState::Disallowed) {
+ mHwVsyncState = HwVsyncState::Disabled;
+ }
+ return mHwVsyncState != HwVsyncState::Disallowed;
+}
+
+void VsyncSchedule::setPendingHardwareVsyncState(bool enabled) {
+ mPendingHwVsyncState = enabled ? HwVsyncState::Enabled : HwVsyncState::Disabled;
+}
+
+bool VsyncSchedule::getPendingHardwareVsyncState() const {
+ return mPendingHwVsyncState == HwVsyncState::Enabled;
+}
+
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index 173b1d0..d88f1d1 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -19,6 +19,9 @@
#include <memory>
#include <string>
+#include <ThreadContext.h>
+#include <ftl/enum.h>
+#include <ftl/optional.h>
#include <scheduler/Features.h>
#include <scheduler/Time.h>
@@ -32,6 +35,8 @@
namespace android::scheduler {
+struct ISchedulerCallback;
+
// TODO(b/185535769): Rename classes, and remove aliases.
class VSyncDispatch;
class VSyncTracker;
@@ -44,12 +49,24 @@
class VsyncSchedule {
public:
explicit VsyncSchedule(FeatureFlags);
- VsyncSchedule(VsyncSchedule&&);
~VsyncSchedule();
Period period() const;
TimePoint vsyncDeadlineAfter(TimePoint) const;
+ // Inform the schedule that the period is changing and the schedule needs to recalibrate
+ // itself. The schedule will end the period transition internally. This will
+ // enable hardware VSYNCs in order to calibrate.
+ //
+ // \param [in] period The period that the system is changing into.
+ void startPeriodTransition(ISchedulerCallback&, Period period);
+
+ // Pass a VSYNC sample to VsyncController. Return true if
+ // VsyncController detected that the VSYNC period changed. Enable or disable
+ // hardware VSYNCs depending on whether more samples are needed.
+ bool addResyncSample(ISchedulerCallback&, TimePoint timestamp,
+ ftl::Optional<Period> hwcVsyncPeriod);
+
// TODO(b/185535769): Hide behind API.
const VsyncTracker& getTracker() const { return *mTracker; }
VsyncTracker& getTracker() { return *mTracker; }
@@ -60,6 +77,22 @@
void dump(std::string&) const;
+ // Turn on hardware VSYNCs, unless mHwVsyncState is Disallowed, in which
+ // case this call is ignored.
+ void enableHardwareVsync(ISchedulerCallback&) EXCLUDES(mHwVsyncLock);
+
+ // Disable hardware VSYNCs. If `disallow` is true, future calls to
+ // enableHardwareVsync are ineffective until allowHardwareVsync is called.
+ void disableHardwareVsync(ISchedulerCallback&, bool disallow) EXCLUDES(mHwVsyncLock);
+
+ // If true, enableHardwareVsync can enable hardware VSYNC (if not already
+ // enabled). If false, enableHardwareVsync does nothing.
+ bool isHardwareVsyncAllowed(bool makeAllowed) EXCLUDES(mHwVsyncLock);
+
+ void setPendingHardwareVsyncState(bool enabled) REQUIRES(kMainThreadContext);
+
+ bool getPendingHardwareVsyncState() const REQUIRES(kMainThreadContext);
+
private:
friend class TestableScheduler;
friend class android::EventThreadTest;
@@ -76,14 +109,36 @@
static DispatchPtr createDispatch(VsyncTracker&);
static ControllerPtr createController(VsyncTracker&, FeatureFlags);
+ void enableHardwareVsyncLocked(ISchedulerCallback&) REQUIRES(mHwVsyncLock);
+
+ mutable std::mutex mHwVsyncLock;
+ enum class HwVsyncState {
+ // Hardware VSYNCs are currently enabled.
+ Enabled,
+
+ // Hardware VSYNCs are currently disabled. They can be enabled by a call
+ // to `enableHardwareVsync`.
+ Disabled,
+
+ // Hardware VSYNCs are not currently allowed (e.g. because the display
+ // is off).
+ Disallowed,
+
+ ftl_last = Disallowed,
+ };
+ HwVsyncState mHwVsyncState GUARDED_BY(mHwVsyncLock) = HwVsyncState::Disallowed;
+
+ // Pending state, in case an attempt is made to set the state while the
+ // device is off.
+ HwVsyncState mPendingHwVsyncState GUARDED_BY(kMainThreadContext) = HwVsyncState::Disabled;
+
class PredictedVsyncTracer;
using TracerPtr = std::unique_ptr<PredictedVsyncTracer>;
- // Effectively const except in move constructor.
- TrackerPtr mTracker;
- DispatchPtr mDispatch;
- ControllerPtr mController;
- TracerPtr mTracer;
+ const TrackerPtr mTracker;
+ const DispatchPtr mDispatch;
+ const ControllerPtr mController;
+ const TracerPtr mTracer;
};
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Time.h b/services/surfaceflinger/Scheduler/include/scheduler/Time.h
index bd4e3c2..ba1459a 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Time.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Time.h
@@ -26,7 +26,7 @@
namespace scheduler {
// TODO(b/185535769): Pull Clock.h to libscheduler to reuse this.
-using SchedulerClock = std::chrono::high_resolution_clock;
+using SchedulerClock = std::chrono::steady_clock;
static_assert(SchedulerClock::is_steady);
} // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h b/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h
new file mode 100644
index 0000000..3d0f1a9
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+#include <ftl/flags.h>
+
+namespace android {
+
+// Whether composition was covered by HWC and/or GPU.
+enum class CompositionCoverage : std::uint8_t {
+ Hwc = 1 << 0,
+
+ // Mutually exclusive: The composition either used the GPU, or reused a buffer that had been
+ // composited on the GPU.
+ Gpu = 1 << 1,
+ GpuReuse = 1 << 2,
+};
+
+using CompositionCoverageFlags = ftl::Flags<CompositionCoverage>;
+
+} // namespace android
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
new file mode 100644
index 0000000..cc41925
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <scheduler/Time.h>
+#include <scheduler/VsyncId.h>
+
+namespace android {
+
+struct ICompositor {
+ // Configures physical displays, processing hotplug and/or mode setting via the Composer HAL.
+ virtual void configure() = 0;
+
+ // Commits transactions for layers and displays. Returns whether any state has been invalidated,
+ // i.e. whether a frame should be composited for each display.
+ virtual bool commit(TimePoint frameTime, VsyncId, TimePoint expectedVsyncTime) = 0;
+
+ // Composites a frame for each display. CompositionEngine performs GPU and/or HAL composition
+ // via RenderEngine and the Composer HAL, respectively.
+ virtual void composite(TimePoint frameTime, VsyncId) = 0;
+
+ // Samples the composited frame via RegionSamplingThread.
+ virtual void sample() = 0;
+
+protected:
+ ~ICompositor() = default;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/ScreenCaptureOutput.cpp b/services/surfaceflinger/ScreenCaptureOutput.cpp
index 6d195b9..a1d5cd7 100644
--- a/services/surfaceflinger/ScreenCaptureOutput.cpp
+++ b/services/surfaceflinger/ScreenCaptureOutput.cpp
@@ -27,11 +27,8 @@
std::shared_ptr<ScreenCaptureOutput> createScreenCaptureOutput(ScreenCaptureOutputArgs args) {
std::shared_ptr<ScreenCaptureOutput> output = compositionengine::impl::createOutputTemplated<
ScreenCaptureOutput, compositionengine::CompositionEngine, const RenderArea&,
- std::unordered_set<compositionengine::LayerFE*>,
const compositionengine::Output::ColorProfile&, bool>(args.compositionEngine,
args.renderArea,
- std::move(
- args.filterForScreenshot),
args.colorProfile,
args.regionSampling);
output->editState().isSecure = args.renderArea.isSecure();
@@ -45,12 +42,11 @@
.setHasWideColorGamut(true)
.Build()));
- ui::Rotation orientation = ui::Transform::toRotation(args.renderArea.getRotationFlags());
- Rect orientedDisplaySpaceRect{args.renderArea.getReqWidth(), args.renderArea.getReqHeight()};
- output->setProjection(orientation, args.renderArea.getLayerStackSpaceRect(),
- orientedDisplaySpaceRect);
-
- Rect sourceCrop = args.renderArea.getSourceCrop();
+ const Rect& sourceCrop = args.renderArea.getSourceCrop();
+ const ui::Rotation orientation = ui::Transform::toRotation(args.renderArea.getRotationFlags());
+ const Rect orientedDisplaySpaceRect{args.renderArea.getReqWidth(),
+ args.renderArea.getReqHeight()};
+ output->setProjection(orientation, sourceCrop, orientedDisplaySpaceRect);
output->setDisplaySize({sourceCrop.getWidth(), sourceCrop.getHeight()});
{
@@ -64,13 +60,9 @@
}
ScreenCaptureOutput::ScreenCaptureOutput(
- const RenderArea& renderArea,
- std::unordered_set<compositionengine::LayerFE*> filterForScreenshot,
- const compositionengine::Output::ColorProfile& colorProfile, bool regionSampling)
- : mRenderArea(renderArea),
- mFilterForScreenshot(std::move(filterForScreenshot)),
- mColorProfile(colorProfile),
- mRegionSampling(regionSampling) {}
+ const RenderArea& renderArea, const compositionengine::Output::ColorProfile& colorProfile,
+ bool regionSampling)
+ : mRenderArea(renderArea), mColorProfile(colorProfile), mRegionSampling(regionSampling) {}
void ScreenCaptureOutput::updateColorProfile(const compositionengine::CompositionRefreshArgs&) {
auto& outputState = editState();
@@ -115,9 +107,4 @@
return clientCompositionLayers;
}
-bool ScreenCaptureOutput::layerNeedsFiltering(const compositionengine::OutputLayer* layer) const {
- return mRenderArea.needsFiltering() ||
- mFilterForScreenshot.find(&layer->getLayerFE()) != mFilterForScreenshot.end();
-}
-
} // namespace android
diff --git a/services/surfaceflinger/ScreenCaptureOutput.h b/services/surfaceflinger/ScreenCaptureOutput.h
index 5dffc1d..4e5a0cc 100644
--- a/services/surfaceflinger/ScreenCaptureOutput.h
+++ b/services/surfaceflinger/ScreenCaptureOutput.h
@@ -33,7 +33,6 @@
std::shared_ptr<renderengine::ExternalTexture> buffer;
float sdrWhitePointNits;
float displayBrightnessNits;
- std::unordered_set<compositionengine::LayerFE*> filterForScreenshot;
bool regionSampling;
};
@@ -44,7 +43,6 @@
class ScreenCaptureOutput : public compositionengine::impl::Output {
public:
ScreenCaptureOutput(const RenderArea& renderArea,
- std::unordered_set<compositionengine::LayerFE*> filterForScreenshot,
const compositionengine::Output::ColorProfile& colorProfile,
bool regionSampling);
@@ -54,15 +52,12 @@
bool supportsProtectedContent, ui::Dataspace outputDataspace,
std::vector<compositionengine::LayerFE*>& outLayerFEs) override;
- bool layerNeedsFiltering(const compositionengine::OutputLayer*) const override;
-
protected:
bool getSkipColorTransform() const override { return false; }
renderengine::DisplaySettings generateClientCompositionDisplaySettings() const override;
private:
const RenderArea& mRenderArea;
- const std::unordered_set<compositionengine::LayerFE*> mFilterForScreenshot;
const compositionengine::Output::ColorProfile& mColorProfile;
const bool mRegionSampling;
};
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index c7c980d..d818b8e 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -130,6 +130,7 @@
#include "FrameTracer/FrameTracer.h"
#include "FrontEnd/LayerCreationArgs.h"
#include "FrontEnd/LayerHandle.h"
+#include "FrontEnd/LayerSnapshot.h"
#include "HdrLayerInfoReporter.h"
#include "Layer.h"
#include "LayerProtoHelper.h"
@@ -172,6 +173,8 @@
using namespace hardware::configstore;
using namespace hardware::configstore::V1_0;
using namespace sysprop;
+using ftl::Flags;
+using namespace ftl::flag_operators;
using aidl::android::hardware::graphics::common::DisplayDecorationSupport;
using aidl::android::hardware::graphics::composer3::Capability;
@@ -407,15 +410,8 @@
mDebugFlashDelay = base::GetUintProperty("debug.sf.showupdates"s, 0u);
- // DDMS debugging deprecated (b/120782499)
- property_get("debug.sf.ddms", value, "0");
- int debugDdms = atoi(value);
- ALOGI_IF(debugDdms, "DDMS debugging not supported");
-
- property_get("debug.sf.enable_gl_backpressure", value, "1");
- mPropagateBackpressureClientComposition = atoi(value);
- ALOGI_IF(mPropagateBackpressureClientComposition,
- "Enabling backpressure propagation for Client Composition");
+ mBackpressureGpuComposition = base::GetBoolProperty("debug.sf.enable_gl_backpressure"s, true);
+ ALOGI_IF(mBackpressureGpuComposition, "Enabling backpressure for GPU composition");
property_get("ro.surface_flinger.supports_background_blur", value, "0");
bool supportsBlurs = atoi(value);
@@ -476,6 +472,10 @@
mPowerHintSessionMode =
{.late = base::GetBoolProperty("debug.sf.send_late_power_session_hint"s, true),
.early = base::GetBoolProperty("debug.sf.send_early_power_session_hint"s, false)};
+ mLayerLifecycleManagerEnabled =
+ base::GetBoolProperty("debug.sf.enable_layer_lifecycle_manager"s, false);
+ mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled ||
+ base::GetBoolProperty("debug.sf.enable_legacy_frontend"s, true);
}
LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() {
@@ -511,7 +511,8 @@
mScheduler->run();
}
-sp<IBinder> SurfaceFlinger::createDisplay(const String8& displayName, bool secure) {
+sp<IBinder> SurfaceFlinger::createDisplay(const String8& displayName, bool secure,
+ float requestedRefreshRate) {
// onTransact already checks for some permissions, but adding an additional check here.
// This is to ensure that only system and graphics can request to create a secure
// display. Secure displays can show secure content so we add an additional restriction on it.
@@ -542,6 +543,7 @@
DisplayDeviceState state;
state.isSecure = secure;
state.displayName = displayName;
+ state.requestedRefreshRate = Fps::fromValue(requestedRefreshRate);
mCurrentState.displays.add(token, state);
return token;
}
@@ -1975,13 +1977,14 @@
// ----------------------------------------------------------------------------
sp<IDisplayEventConnection> SurfaceFlinger::createDisplayEventConnection(
- gui::ISurfaceComposer::VsyncSource vsyncSource, EventRegistrationFlags eventRegistration) {
+ gui::ISurfaceComposer::VsyncSource vsyncSource, EventRegistrationFlags eventRegistration,
+ const sp<IBinder>& layerHandle) {
const auto& handle =
vsyncSource == gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger
? mSfConnectionHandle
: mAppConnectionHandle;
- return mScheduler->createDisplayEventConnection(handle, eventRegistration);
+ return mScheduler->createDisplayEventConnection(handle, eventRegistration, layerHandle);
}
void SurfaceFlinger::scheduleCommit(FrameHint hint) {
@@ -2028,18 +2031,13 @@
Mutex::Autolock lock(mStateLock);
- if (!getHwComposer().onVsync(hwcDisplayId, timestamp)) {
+ if (const auto displayIdOpt = getHwComposer().onVsync(hwcDisplayId, timestamp);
+ displayIdOpt != mActiveDisplayId) {
+ // Ignore VSYNC for invalid/inactive displays.
return;
}
- if (const auto displayId = getHwComposer().toPhysicalDisplayId(hwcDisplayId);
- displayId != mActiveDisplayId) {
- // For now, we don't do anything with non active display vsyncs.
- return;
- }
-
- bool periodFlushed = false;
- mScheduler->addResyncSample(timestamp, vsyncPeriod, &periodFlushed);
+ const bool periodFlushed = mScheduler->addResyncSample(timestamp, vsyncPeriod);
if (periodFlushed) {
mScheduler->modulateVsync(&VsyncModulator::onRefreshRateChangeCompleted);
}
@@ -2082,16 +2080,23 @@
mScheduler->forceNextResync();
}
+void SurfaceFlinger::onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) {
+ // TODO(b/202734676) update refresh rate value on the RefreshRateOverlay
+}
+
void SurfaceFlinger::setVsyncEnabled(bool enabled) {
ATRACE_CALL();
// On main thread to avoid race conditions with display power state.
static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
- mHWCVsyncPendingState = enabled ? hal::Vsync::ENABLE : hal::Vsync::DISABLE;
+ {
+ ftl::FakeGuard guard(kMainThreadContext);
+ mScheduler->getVsyncSchedule().setPendingHardwareVsyncState(enabled);
+ }
if (const auto display = getDefaultDisplayDeviceLocked();
display && display->isPoweredOn()) {
- setHWCVsyncEnabled(display->getPhysicalId(), mHWCVsyncPendingState);
+ setHWCVsyncEnabled(display->getPhysicalId(), enabled);
}
}));
}
@@ -2134,6 +2139,110 @@
}
}
+bool SurfaceFlinger::updateLayerSnapshotsLegacy(VsyncId vsyncId, LifecycleUpdate& update,
+ bool transactionsFlushed,
+ bool& outTransactionsAreEmpty) {
+ bool needsTraversal = false;
+ if (transactionsFlushed) {
+ needsTraversal |= commitMirrorDisplays(vsyncId);
+ needsTraversal |= commitCreatedLayers(vsyncId, update.layerCreatedStates);
+ needsTraversal |= applyTransactions(update.transactions, vsyncId);
+ }
+ outTransactionsAreEmpty = !needsTraversal;
+ const bool shouldCommit = (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal;
+ if (shouldCommit) {
+ commitTransactions();
+ }
+
+ bool mustComposite = latchBuffers() || shouldCommit;
+ updateLayerGeometry();
+ return mustComposite;
+}
+
+bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, LifecycleUpdate& update,
+ bool transactionsFlushed, bool& outTransactionsAreEmpty) {
+ using Changes = frontend::RequestedLayerState::Changes;
+ ATRACE_NAME("updateLayerSnapshots");
+ {
+ mLayerLifecycleManager.addLayers(std::move(update.newLayers));
+ mLayerLifecycleManager.applyTransactions(update.transactions);
+ mLayerLifecycleManager.onHandlesDestroyed(update.destroyedHandles);
+ for (auto& legacyLayer : update.layerCreatedStates) {
+ sp<Layer> layer = legacyLayer.layer.promote();
+ if (layer) {
+ mLegacyLayers[layer->sequence] = layer;
+ }
+ }
+ }
+ if (mLayerLifecycleManager.getGlobalChanges().test(Changes::Hierarchy)) {
+ ATRACE_NAME("LayerHierarchyBuilder:update");
+ mLayerHierarchyBuilder.update(mLayerLifecycleManager.getLayers(),
+ mLayerLifecycleManager.getDestroyedLayers());
+ }
+
+ applyAndCommitDisplayTransactionStates(update.transactions);
+
+ {
+ ATRACE_NAME("LayerSnapshotBuilder:update");
+ frontend::LayerSnapshotBuilder::Args args{.root = mLayerHierarchyBuilder.getHierarchy(),
+ .layerLifecycleManager = mLayerLifecycleManager,
+ .displays = mFrontEndDisplayInfos,
+ .displayChanges = mFrontEndDisplayInfosChanged,
+ .globalShadowSettings =
+ mDrawingState.globalShadowSettings,
+ .supportsBlur = mSupportsBlur,
+ .forceFullDamage = mForceFullDamage};
+ mLayerSnapshotBuilder.update(args);
+ }
+
+ if (mLayerLifecycleManager.getGlobalChanges().any(Changes::Geometry | Changes::Input |
+ Changes::Hierarchy)) {
+ mUpdateInputInfo = true;
+ }
+ if (mLayerLifecycleManager.getGlobalChanges().any(Changes::VisibleRegion | Changes::Hierarchy |
+ Changes::Visibility)) {
+ mVisibleRegionsDirty = true;
+ }
+ outTransactionsAreEmpty = mLayerLifecycleManager.getGlobalChanges().get() == 0;
+ const bool mustComposite = mLayerLifecycleManager.getGlobalChanges().get() != 0;
+ {
+ ATRACE_NAME("LLM:commitChanges");
+ mLayerLifecycleManager.commitChanges();
+ }
+
+ if (!mLegacyFrontEndEnabled) {
+ ATRACE_NAME("DisplayCallbackAndStatsUpdates");
+ applyTransactions(update.transactions, vsyncId);
+
+ bool newDataLatched = false;
+ for (auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) {
+ if (!snapshot->changes.test(Changes::Buffer)) continue;
+ auto it = mLegacyLayers.find(snapshot->sequence);
+ LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s",
+ snapshot->getDebugString().c_str());
+ mLayersWithQueuedFrames.emplace(it->second);
+ newDataLatched = true;
+ if (!snapshot->isVisible) break;
+
+ Region visibleReg;
+ visibleReg.set(snapshot->transformedBoundsWithoutTransparentRegion);
+ invalidateLayerStack(snapshot->outputFilter, visibleReg);
+ }
+
+ for (auto& destroyedLayer : mLayerLifecycleManager.getDestroyedLayers()) {
+ mLegacyLayers.erase(destroyedLayer->id);
+ }
+
+ // enter boot animation on first buffer latch
+ if (CC_UNLIKELY(mBootStage == BootStage::BOOTLOADER && newDataLatched)) {
+ ALOGI("Enter boot animation");
+ mBootStage = BootStage::BOOTANIMATION;
+ }
+ commitTransactions();
+ }
+ return mustComposite;
+}
+
bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expectedVsyncTime)
FTL_FAKE_GUARD(kMainThreadContext) {
// The expectedVsyncTime, which was predicted when this frame was scheduled, is normally in the
@@ -2154,11 +2263,11 @@
const Period vsyncPeriod = mScheduler->getVsyncSchedule().period();
const FenceTimePtr& previousPresentFence = getPreviousPresentFence(frameTime, vsyncPeriod);
- // When Backpressure propagation is enabled we want to give a small grace period
+ // When backpressure propagation is enabled, we want to give a small grace period of 1ms
// for the present fence to fire instead of just giving up on this frame to handle cases
// where present fence is just about to get signaled.
- const int graceTimeForPresentFenceMs =
- (mPropagateBackpressureClientComposition || !mHadClientComposition) ? 1 : 0;
+ const int graceTimeForPresentFenceMs = static_cast<int>(
+ mBackpressureGpuComposition || !mCompositionCoverage.test(CompositionCoverage::Gpu));
// Pending frames may trigger backpressure propagation.
const TracedOrdinal<bool> framePending = {"PrevFramePending",
@@ -2181,9 +2290,14 @@
(lastScheduledPresentTime.ns() <
previousPresentTime - frameMissedSlop))};
const TracedOrdinal<bool> hwcFrameMissed = {"PrevHwcFrameMissed",
- mHadDeviceComposition && frameMissed};
+ frameMissed &&
+ mCompositionCoverage.test(
+ CompositionCoverage::Hwc)};
+
const TracedOrdinal<bool> gpuFrameMissed = {"PrevGpuFrameMissed",
- mHadClientComposition && frameMissed};
+ frameMissed &&
+ mCompositionCoverage.test(
+ CompositionCoverage::Gpu)};
if (frameMissed) {
mFrameMissedCount++;
@@ -2221,7 +2335,7 @@
}
if (framePending) {
- if ((hwcFrameMissed && !gpuFrameMissed) || mPropagateBackpressureClientComposition) {
+ if (mBackpressureGpuComposition || (hwcFrameMissed && !gpuFrameMissed)) {
scheduleCommit(FrameHint::kNone);
return false;
}
@@ -2268,45 +2382,34 @@
mFrameTimeline->setSfWakeUp(vsyncId.value, frameTime.ns(),
Fps::fromPeriodNsecs(vsyncPeriod.ns()));
- bool needsTraversal = false;
- if (clearTransactionFlags(eTransactionFlushNeeded)) {
- // Locking:
- // 1. to prevent onHandleDestroyed from being called while the state lock is held,
- // we must keep a copy of the transactions (specifically the composer
- // states) around outside the scope of the lock.
- // 2. Transactions and created layers do not share a lock. To prevent applying
- // transactions with layers still in the createdLayer queue, flush the transactions
- // before committing the created layers.
- std::vector<TransactionState> transactions = mTransactionHandler.flushTransactions();
- needsTraversal |= commitMirrorDisplays(vsyncId);
- needsTraversal |= commitCreatedLayers(vsyncId);
- needsTraversal |= applyTransactions(transactions, vsyncId);
+ const bool flushTransactions = clearTransactionFlags(eTransactionFlushNeeded);
+ LifecycleUpdate updates;
+ if (flushTransactions) {
+ updates = flushLifecycleUpdates();
}
-
- const bool shouldCommit =
- (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal;
- if (shouldCommit) {
- commitTransactions();
+ bool transactionsAreEmpty;
+ if (mLegacyFrontEndEnabled) {
+ mustComposite |= updateLayerSnapshotsLegacy(vsyncId, updates, flushTransactions,
+ transactionsAreEmpty);
+ }
+ if (mLayerLifecycleManagerEnabled) {
+ mustComposite |=
+ updateLayerSnapshots(vsyncId, updates, flushTransactions, transactionsAreEmpty);
}
if (transactionFlushNeeded()) {
setTransactionFlags(eTransactionFlushNeeded);
}
- mustComposite |= shouldCommit;
- mustComposite |= latchBuffers();
-
// This has to be called after latchBuffers because we want to include the layers that have
// been latched in the commit callback
- if (!needsTraversal) {
+ if (transactionsAreEmpty) {
// Invoke empty transaction callbacks early.
mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */);
} else {
// Invoke OnCommit callbacks.
mTransactionCallbackInvoker.sendCallbacks(true /* onCommitOnly */);
}
-
- updateLayerGeometry();
}
// Layers need to get updated (in the previous line) before we can use them for
@@ -2342,7 +2445,15 @@
refreshArgs.outputs.reserve(displays.size());
std::vector<DisplayId> displayIds;
for (const auto& [_, display] : displays) {
- refreshArgs.outputs.push_back(display->getCompositionDisplay());
+ bool dropFrame = false;
+ if (display->isVirtual()) {
+ Fps refreshRate = display->getAdjustedRefreshRate();
+ using fps_approx_ops::operator>;
+ dropFrame = (refreshRate > 0_Hz) && !mScheduler->isVsyncInPhase(frameTime, refreshRate);
+ }
+ if (!dropFrame) {
+ refreshArgs.outputs.push_back(display->getCompositionDisplay());
+ }
displayIds.push_back(display->getId());
}
mPowerAdvisor->setDisplays(displayIds);
@@ -2385,15 +2496,6 @@
refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty;
refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty;
- std::vector<Layer*> layers;
-
- mDrawingState.traverseInZOrder([&refreshArgs, &layers](Layer* layer) {
- if (auto layerFE = layer->getCompositionEngineLayerFE()) {
- layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame);
- refreshArgs.layers.push_back(layerFE);
- layers.push_back(layer);
- }
- });
refreshArgs.internalDisplayRotationFlags = DisplayDevice::getPrimaryDisplayRotationFlags();
if (CC_UNLIKELY(mDrawingState.colorMatrixChanged)) {
@@ -2420,17 +2522,13 @@
// the scheduler.
const auto presentTime = systemTime();
- {
- std::vector<LayerSnapshotGuard> layerSnapshotGuards;
- for (Layer* layer : layers) {
- layerSnapshotGuards.emplace_back(layer);
- }
- mCompositionEngine->present(refreshArgs);
- }
+ std::vector<std::pair<Layer*, LayerFE*>> layers =
+ moveSnapshotsToCompositionArgs(refreshArgs, /*cursorOnly=*/false, vsyncId.value);
+ mCompositionEngine->present(refreshArgs);
+ moveSnapshotsFromCompositionArgs(refreshArgs, layers);
- for (auto& layer : layers) {
- CompositionResult compositionResult{
- layer->getCompositionEngineLayerFE()->stealCompositionResult()};
+ for (auto [layer, layerFE] : layers) {
+ CompositionResult compositionResult{layerFE->stealCompositionResult()};
layer->onPreComposition(compositionResult.refreshStartTime);
for (auto releaseFence : compositionResult.releaseFences) {
layer->onLayerDisplayed(releaseFence);
@@ -2458,29 +2556,43 @@
postComposition(presentTime);
- const bool prevFrameHadClientComposition = mHadClientComposition;
+ const bool hadGpuComposited = mCompositionCoverage.test(CompositionCoverage::Gpu);
+ mCompositionCoverage.clear();
- mHadClientComposition = mHadDeviceComposition = mReusedClientComposition = false;
TimeStats::ClientCompositionRecord clientCompositionRecord;
for (const auto& [_, display] : displays) {
const auto& state = display->getCompositionDisplay()->getState();
- mHadClientComposition |= state.usesClientComposition && !state.reusedClientComposition;
- mHadDeviceComposition |= state.usesDeviceComposition;
- mReusedClientComposition |= state.reusedClientComposition;
+
+ if (state.usesDeviceComposition) {
+ mCompositionCoverage |= CompositionCoverage::Hwc;
+ }
+
+ if (state.reusedClientComposition) {
+ mCompositionCoverage |= CompositionCoverage::GpuReuse;
+ } else if (state.usesClientComposition) {
+ mCompositionCoverage |= CompositionCoverage::Gpu;
+ }
+
clientCompositionRecord.predicted |=
(state.strategyPrediction != CompositionStrategyPredictionState::DISABLED);
clientCompositionRecord.predictionSucceeded |=
(state.strategyPrediction == CompositionStrategyPredictionState::SUCCESS);
}
- clientCompositionRecord.hadClientComposition = mHadClientComposition;
- clientCompositionRecord.reused = mReusedClientComposition;
- clientCompositionRecord.changed = prevFrameHadClientComposition != mHadClientComposition;
+ const bool hasGpuComposited = mCompositionCoverage.test(CompositionCoverage::Gpu);
+
+ clientCompositionRecord.hadClientComposition = hasGpuComposited;
+ clientCompositionRecord.reused = mCompositionCoverage.test(CompositionCoverage::GpuReuse);
+ clientCompositionRecord.changed = hadGpuComposited != hasGpuComposited;
+
mTimeStats->pushCompositionStrategyState(clientCompositionRecord);
- // TODO: b/160583065 Enable skip validation when SF caches all client composition layers
- const bool usedGpuComposition = mHadClientComposition || mReusedClientComposition;
- mScheduler->modulateVsync(&VsyncModulator::onDisplayRefresh, usedGpuComposition);
+ using namespace ftl::flag_operators;
+
+ // TODO(b/160583065): Enable skip validation when SF caches all client composition layers.
+ const bool hasGpuUseOrReuse =
+ mCompositionCoverage.any(CompositionCoverage::Gpu | CompositionCoverage::GpuReuse);
+ mScheduler->modulateVsync(&VsyncModulator::onDisplayRefresh, hasGpuUseOrReuse);
mLayersWithQueuedFrames.clear();
if (mLayerTracingEnabled && mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
@@ -2488,7 +2600,7 @@
mLayerTracing.notify(mVisibleRegionsDirty, frameTime.ns(), vsyncId.value);
}
- mVisibleRegionsWereDirtyThisFrame = mVisibleRegionsDirty; // Cache value for use in post-comp
+ if (mVisibleRegionsDirty) mHdrLayerInfoChanged = true;
mVisibleRegionsDirty = false;
if (mCompositionEngine->needsAnotherUpdate()) {
@@ -2510,27 +2622,38 @@
for (auto& layer : mLayersPendingRefresh) {
Region visibleReg;
visibleReg.set(layer->getScreenBounds());
- invalidateLayerStack(layer, visibleReg);
+ invalidateLayerStack(layer->getOutputFilter(), visibleReg);
}
mLayersPendingRefresh.clear();
}
-bool SurfaceFlinger::isHdrLayer(Layer* layer) const {
- // Treat all layers as non-HDR if:
- // 1. They do not have a valid HDR dataspace. Currently we treat those as PQ or HLG. and
- // 2. The layer is allowed to be dimmed. WindowManager may disable dimming in order to
- // keep animations invoking SDR screenshots of HDR layers seamless. Treat such tagged
- // layers as HDR so that DisplayManagerService does not try to change the screen brightness
- if (!isHdrDataspace(layer->getDataSpace()) && layer->isDimmingEnabled()) {
- return false;
- }
+bool SurfaceFlinger::isHdrLayer(const frontend::LayerSnapshot& snapshot) const {
+ // Even though the camera layer may be using an HDR transfer function or otherwise be "HDR"
+ // the device may need to avoid boosting the brightness as a result of these layers to
+ // reduce power consumption during camera recording
if (mIgnoreHdrCameraLayers) {
- auto buffer = layer->getBuffer();
- if (buffer && (buffer->getUsage() & GRALLOC_USAGE_HW_CAMERA_WRITE) != 0) {
+ if (snapshot.externalTexture &&
+ (snapshot.externalTexture->getUsage() & GRALLOC_USAGE_HW_CAMERA_WRITE) != 0) {
return false;
}
}
- return true;
+ if (isHdrDataspace(snapshot.dataspace)) {
+ return true;
+ }
+ // If the layer is not allowed to be dimmed, treat it as HDR. WindowManager may disable
+ // dimming in order to keep animations invoking SDR screenshots of HDR layers seamless.
+ // Treat such tagged layers as HDR so that DisplayManagerService does not try to change
+ // the screen brightness
+ if (!snapshot.dimmingEnabled) {
+ return true;
+ }
+ // RANGE_EXTENDED layers may identify themselves as being "HDR" via a desired sdr/hdr ratio
+ if ((snapshot.dataspace & (int32_t)Dataspace::RANGE_MASK) ==
+ (int32_t)Dataspace::RANGE_EXTENDED &&
+ snapshot.desiredSdrHdrRatio > 1.01f) {
+ return true;
+ }
+ return false;
}
ui::Rotation SurfaceFlinger::getPhysicalDisplayOrientation(DisplayId displayId,
@@ -2574,12 +2697,13 @@
ATRACE_CALL();
ALOGV(__func__);
- const auto* display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get();
+ const auto* defaultDisplay = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get();
std::shared_ptr<FenceTime> glCompositionDoneFenceTime;
- if (display && display->getCompositionDisplay()->getState().usesClientComposition) {
+ if (defaultDisplay &&
+ defaultDisplay->getCompositionDisplay()->getState().usesClientComposition) {
glCompositionDoneFenceTime =
- std::make_shared<FenceTime>(display->getCompositionDisplay()
+ std::make_shared<FenceTime>(defaultDisplay->getCompositionDisplay()
->getRenderSurface()
->getClientTargetAcquireFence());
} else {
@@ -2588,8 +2712,9 @@
mPreviousPresentFences[1] = mPreviousPresentFences[0];
- auto presentFence =
- display ? getHwComposer().getPresentFence(display->getPhysicalId()) : Fence::NO_FENCE;
+ auto presentFence = defaultDisplay
+ ? getHwComposer().getPresentFence(defaultDisplay->getPhysicalId())
+ : Fence::NO_FENCE;
auto presentFenceTime = std::make_shared<FenceTime>(presentFence);
mPreviousPresentFences[0] = {presentFence, presentFenceTime};
@@ -2620,7 +2745,7 @@
presentLatency.ns());
for (const auto& layer: mLayersWithQueuedFrames) {
- layer->onPostComposition(display, glCompositionDoneFenceTime, presentFenceTime,
+ layer->onPostComposition(defaultDisplay, glCompositionDoneFenceTime, presentFenceTime,
compositorTiming);
layer->releasePendingBuffer(presentTime.ns());
}
@@ -2649,18 +2774,20 @@
mAddingHDRLayerInfoListener = false;
}
- if (haveNewListeners || mSomeDataspaceChanged || mVisibleRegionsWereDirtyThisFrame) {
+ if (haveNewListeners || mHdrLayerInfoChanged) {
for (auto& [compositionDisplay, listener] : hdrInfoListeners) {
HdrLayerInfoReporter::HdrLayerInfo info;
int32_t maxArea = 0;
mDrawingState.traverse([&, compositionDisplay = compositionDisplay](Layer* layer) {
const auto layerFe = layer->getCompositionEngineLayerFE();
- if (layer->isVisible() &&
- compositionDisplay->includesLayer(layer->getOutputFilter())) {
- if (isHdrLayer(layer)) {
+ const frontend::LayerSnapshot& snapshot = *layer->getLayerSnapshot();
+ if (snapshot.isVisible &&
+ compositionDisplay->includesLayer(snapshot.outputFilter)) {
+ if (isHdrLayer(snapshot)) {
const auto* outputLayer =
compositionDisplay->getOutputLayerForLayer(layerFe);
if (outputLayer) {
+ info.mergeDesiredRatio(snapshot.desiredSdrHdrRatio);
info.numberOfHdrLayers++;
const auto displayFrame = outputLayer->getState().displayFrame;
const int32_t area = displayFrame.width() * displayFrame.height();
@@ -2677,8 +2804,7 @@
}
}
- mSomeDataspaceChanged = false;
- mVisibleRegionsWereDirtyThisFrame = false;
+ mHdrLayerInfoChanged = false;
mTransactionCallbackInvoker.addPresentFence(std::move(presentFence));
mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */);
@@ -2687,22 +2813,22 @@
mTimeStats->incrementTotalFrames();
mTimeStats->setPresentFenceGlobal(presentFenceTime);
- const bool isInternalDisplay = display &&
+ const bool isInternalDisplay = defaultDisplay &&
FTL_FAKE_GUARD(mStateLock, mPhysicalDisplays)
- .get(display->getPhysicalId())
+ .get(defaultDisplay->getPhysicalId())
.transform(&PhysicalDisplay::isInternal)
.value_or(false);
- if (isInternalDisplay && display && display->getPowerMode() == hal::PowerMode::ON &&
+ if (isInternalDisplay && defaultDisplay && defaultDisplay->getPowerMode() == hal::PowerMode::ON &&
presentFenceTime->isValid()) {
mScheduler->addPresentFence(std::move(presentFenceTime));
}
const bool isDisplayConnected =
- display && getHwComposer().isConnected(display->getPhysicalId());
+ defaultDisplay && getHwComposer().isConnected(defaultDisplay->getPhysicalId());
if (!hasSyncFramework) {
- if (isDisplayConnected && display->isPoweredOn()) {
+ if (isDisplayConnected && defaultDisplay->isPoweredOn()) {
mScheduler->enableHardwareVsync();
}
}
@@ -2711,7 +2837,7 @@
const size_t appConnections = mScheduler->getEventThreadConnectionCount(mAppConnectionHandle);
mTimeStats->recordDisplayEventConnectionCount(sfConnections + appConnections);
- if (isDisplayConnected && !display->isPoweredOn()) {
+ if (isDisplayConnected && !defaultDisplay->isPoweredOn()) {
getRenderEngine().cleanupPostRender();
return;
}
@@ -2737,13 +2863,24 @@
}
if (mNumTrustedPresentationListeners > 0) {
+ display::DisplayMap<ui::LayerStack, const DisplayDevice*> layerStackToDisplay;
+ {
+ Mutex::Autolock lock(mStateLock);
+ for (const auto& [token, display] : mDisplays) {
+ layerStackToDisplay.emplace_or_replace(display->getLayerStack(), display.get());
+ }
+ }
+
// We avoid any reverse traversal upwards so this shouldn't be too expensive
mDrawingState.traverse([&](Layer* layer) {
if (!layer->hasTrustedPresentationListener()) {
return;
}
- layer->updateTrustedPresentationState(display, nanoseconds_to_milliseconds(callTime),
- false);
+ const std::optional<const DisplayDevice*> displayOpt =
+ layerStackToDisplay.get(layer->getLayerSnapshot()->outputFilter.layerStack);
+ const DisplayDevice* display = displayOpt.value_or(nullptr);
+ layer->updateTrustedPresentationState(display, layer->getLayerSnapshot(),
+ nanoseconds_to_milliseconds(callTime), false);
});
}
@@ -2996,15 +3133,15 @@
const auto enableFrameRateOverride = [&] {
using Config = scheduler::RefreshRateSelector::Config;
- if (!sysprop::enable_frame_rate_override(false)) {
+ if (!sysprop::enable_frame_rate_override(true)) {
return Config::FrameRateOverride::Disabled;
}
- if (sysprop::frame_rate_override_for_native_rates(true)) {
+ if (sysprop::frame_rate_override_for_native_rates(false)) {
return Config::FrameRateOverride::AppOverrideNativeRefreshRates;
}
- if (!sysprop::frame_rate_override_global(false)) {
+ if (!sysprop::frame_rate_override_global(true)) {
return Config::FrameRateOverride::AppOverride;
}
@@ -3069,6 +3206,8 @@
creationArgs.initialPowerMode =
state.isVirtual() ? std::make_optional(hal::PowerMode::ON) : std::nullopt;
+ creationArgs.requestedRefreshRate = state.requestedRefreshRate;
+
sp<DisplayDevice> display = getFactory().createDisplayDevice(creationArgs);
nativeWindowSurface->preallocateBuffers();
@@ -3185,6 +3324,10 @@
dispatchDisplayHotplugEvent(displayId, true);
}
+ if (display->isVirtual()) {
+ display->adjustRefreshRate(mScheduler->getLeaderRefreshRate());
+ }
+
mDisplays.try_emplace(displayToken, std::move(display));
}
@@ -3336,7 +3479,8 @@
void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) {
// Commit display transactions.
const bool displayTransactionNeeded = transactionFlags & eDisplayTransactionNeeded;
- if (displayTransactionNeeded) {
+ mFrontEndDisplayInfosChanged = displayTransactionNeeded;
+ if (displayTransactionNeeded && !mLayerLifecycleManagerEnabled) {
processDisplayChangesLocked();
mFrontEndDisplayInfos.clear();
for (const auto& [_, display] : mDisplays) {
@@ -3427,7 +3571,7 @@
// this layer is not visible anymore
Region visibleReg;
visibleReg.set(layer->getScreenBounds());
- invalidateLayerStack(sp<Layer>::fromExisting(layer), visibleReg);
+ invalidateLayerStack(layer->getOutputFilter(), visibleReg);
}
});
}
@@ -3515,16 +3659,23 @@
outWindowInfos.reserve(sNumWindowInfos);
sNumWindowInfos = 0;
- mDrawingState.traverseInReverseZOrder([&](Layer* layer) {
- if (!layer->needsInputInfo()) return;
+ if (mLayerLifecycleManagerEnabled) {
+ mLayerSnapshotBuilder.forEachInputSnapshot(
+ [&outWindowInfos](const frontend::LayerSnapshot& snapshot) {
+ outWindowInfos.push_back(snapshot.inputInfo);
+ });
+ } else {
+ mDrawingState.traverseInReverseZOrder([&](Layer* layer) {
+ if (!layer->needsInputInfo()) return;
+ const auto opt =
+ mFrontEndDisplayInfos.get(layer->getLayerStack())
+ .transform([](const frontend::DisplayInfo& info) {
+ return Layer::InputDisplayArgs{&info.transform, info.isSecure};
+ });
- const auto opt = mFrontEndDisplayInfos.get(layer->getLayerStack())
- .transform([](const frontend::DisplayInfo& info) {
- return Layer::InputDisplayArgs{&info.transform, info.isSecure};
- });
-
- outWindowInfos.push_back(layer->fillInputInfo(opt.value_or(Layer::InputDisplayArgs{})));
- });
+ outWindowInfos.push_back(layer->fillInputInfo(opt.value_or(Layer::InputDisplayArgs{})));
+ });
+ }
sNumWindowInfos = outWindowInfos.size();
@@ -3541,17 +3692,9 @@
refreshArgs.outputs.push_back(display->getCompositionDisplay());
}
}
-
- std::vector<LayerSnapshotGuard> layerSnapshotGuards;
- mDrawingState.traverse([&layerSnapshotGuards](Layer* layer) {
- if (layer->getLayerSnapshot()->compositionType ==
- aidl::android::hardware::graphics::composer3::Composition::CURSOR) {
- layer->updateSnapshot(false /* updateGeometry */);
- layerSnapshotGuards.emplace_back(layer);
- }
- });
-
+ auto layers = moveSnapshotsToCompositionArgs(refreshArgs, /*cursorOnly=*/true, 0);
mCompositionEngine->updateCursorAsync(refreshArgs);
+ moveSnapshotsFromCompositionArgs(refreshArgs, layers);
}
void SurfaceFlinger::requestDisplayModes(std::vector<display::DisplayModeRequest> modeRequests) {
@@ -3727,10 +3870,10 @@
}
}
-void SurfaceFlinger::invalidateLayerStack(const sp<const Layer>& layer, const Region& dirty) {
+void SurfaceFlinger::invalidateLayerStack(const ui::LayerFilter& layerFilter, const Region& dirty) {
for (const auto& [token, displayDevice] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
auto display = displayDevice->getCompositionDisplay();
- if (display->includesLayer(layer->getOutputFilter())) {
+ if (display->includesLayer(layerFilter)) {
display->editState().dirtyRegion.orSelf(dirty);
}
}
@@ -3850,6 +3993,7 @@
{
std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
mCreatedLayers.emplace_back(layer, parent, args.addToRoot);
+ mNewLayers.emplace_back(std::make_unique<frontend::RequestedLayerState>(args));
}
setTransactionFlags(eTransactionNeeded);
@@ -3861,14 +4005,18 @@
}
uint32_t SurfaceFlinger::clearTransactionFlags(uint32_t mask) {
- return mTransactionFlags.fetch_and(~mask) & mask;
+ uint32_t transactionFlags = mTransactionFlags.fetch_and(~mask);
+ ATRACE_INT("mTransactionFlags", transactionFlags);
+ return transactionFlags & mask;
}
void SurfaceFlinger::setTransactionFlags(uint32_t mask, TransactionSchedule schedule,
const sp<IBinder>& applyToken, FrameHint frameHint) {
mScheduler->modulateVsync(&VsyncModulator::setTransactionSchedule, schedule, applyToken);
+ uint32_t transactionFlags = mTransactionFlags.fetch_or(mask);
+ ATRACE_INT("mTransactionFlags", transactionFlags);
- if (const bool scheduled = mTransactionFlags.fetch_or(mask) & mask; !scheduled) {
+ if (const bool scheduled = transactionFlags & mask; !scheduled) {
scheduleCommit(frameHint);
} else if (frameHint == FrameHint::kActive) {
// Even if the next frame is already scheduled, we should reset the idle timer
@@ -3916,16 +4064,30 @@
sp<Layer> layer = LayerHandle::getLayer(s.surface);
const auto& transaction = *flushState.transaction;
// check for barrier frames
- if (s.bufferData->hasBarrier &&
- ((layer->getDrawingState().frameNumber) < s.bufferData->barrierFrameNumber)) {
- const bool willApplyBarrierFrame =
- flushState.bufferLayersReadyToPresent.contains(s.surface.get()) &&
- (flushState.bufferLayersReadyToPresent.get(s.surface.get()) >=
- s.bufferData->barrierFrameNumber);
- if (!willApplyBarrierFrame) {
- ATRACE_NAME("NotReadyBarrier");
- ready = TransactionReadiness::NotReadyBarrier;
- return false;
+ if (s.bufferData->hasBarrier) {
+ // The current producerId is already a newer producer than the buffer that has a
+ // barrier. This means the incoming buffer is older and we can release it here. We
+ // don't wait on the barrier since we know that's stale information.
+ if (layer->getDrawingState().producerId > s.bufferData->producerId) {
+ layer->callReleaseBufferCallback(s.bufferData->releaseBufferListener,
+ s.bufferData->buffer, s.bufferData->frameNumber,
+ s.bufferData->acquireFence);
+ // Delete the entire state at this point and not just release the buffer because
+ // everything associated with the Layer in this Transaction is now out of date.
+ ATRACE_NAME("DeleteStaleBuffer");
+ return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL;
+ }
+
+ if (layer->getDrawingState().frameNumber < s.bufferData->barrierFrameNumber) {
+ const bool willApplyBarrierFrame =
+ flushState.bufferLayersReadyToPresent.contains(s.surface.get()) &&
+ ((flushState.bufferLayersReadyToPresent.get(s.surface.get()) >=
+ s.bufferData->barrierFrameNumber));
+ if (!willApplyBarrierFrame) {
+ ATRACE_NAME("NotReadyBarrier");
+ ready = TransactionReadiness::NotReadyBarrier;
+ return TraverseBuffersReturnValues::STOP_TRAVERSAL;
+ }
}
}
@@ -3936,7 +4098,7 @@
if (layer->backpressureEnabled() && hasPendingBuffer && transaction.isAutoTimestamp) {
ATRACE_NAME("hasPendingBuffer");
ready = TransactionReadiness::NotReady;
- return false;
+ return TraverseBuffersReturnValues::STOP_TRAVERSAL;
}
// check fence status
@@ -3963,14 +4125,14 @@
"Buffer processing hung up due to stuck "
"fence. Indicates GPU hang");
}
- return false;
+ return TraverseBuffersReturnValues::STOP_TRAVERSAL;
}
ready = enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer
? TransactionReadiness::ReadyUnsignaledSingle
: TransactionReadiness::ReadyUnsignaled;
}
- return true;
+ return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL;
});
ATRACE_INT("TransactionReadiness", static_cast<int>(ready));
return ready;
@@ -4005,7 +4167,7 @@
transaction.displays, transaction.flags,
transaction.inputWindowCommands,
transaction.desiredPresentTime, transaction.isAutoTimestamp,
- std::move(transaction.uncacheBufferIds), transaction.postTime,
+ transaction.buffer, transaction.postTime,
transaction.permissions, transaction.hasListenerCallbacks,
transaction.listenerCallbacks, transaction.originPid,
transaction.originUid, transaction.id);
@@ -4093,9 +4255,8 @@
const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
- bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
- bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
- uint64_t transactionId) {
+ bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
+ const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId) {
ATRACE_CALL();
uint32_t permissions =
@@ -4129,15 +4290,6 @@
const int originPid = ipc->getCallingPid();
const int originUid = ipc->getCallingUid();
- std::vector<uint64_t> uncacheBufferIds;
- uncacheBufferIds.reserve(uncacheBuffers.size());
- for (const auto& uncacheBuffer : uncacheBuffers) {
- sp<GraphicBuffer> buffer = ClientCache::getInstance().erase(uncacheBuffer);
- if (buffer != nullptr) {
- uncacheBufferIds.push_back(buffer->getId());
- }
- }
-
std::vector<ResolvedComposerState> resolvedStates;
resolvedStates.reserve(states.size());
for (auto& state : states) {
@@ -4155,22 +4307,14 @@
}
}
- TransactionState state{frameTimelineInfo,
- resolvedStates,
- displays,
- flags,
- applyToken,
- inputWindowCommands,
- desiredPresentTime,
- isAutoTimestamp,
- std::move(uncacheBufferIds),
- postTime,
- permissions,
- hasListenerCallbacks,
- listenerCallbacks,
- originPid,
- originUid,
- transactionId};
+ TransactionState state{frameTimelineInfo, resolvedStates,
+ displays, flags,
+ applyToken, inputWindowCommands,
+ desiredPresentTime, isAutoTimestamp,
+ uncacheBuffer, postTime,
+ permissions, hasListenerCallbacks,
+ listenerCallbacks, originPid,
+ originUid, transactionId};
if (mTransactionTracing) {
mTransactionTracing->addQueuedTransaction(state);
@@ -4183,9 +4327,8 @@
}(state.flags);
const auto frameHint = state.isFrameActive() ? FrameHint::kActive : FrameHint::kNone;
- setTransactionFlags(eTransactionFlushNeeded, schedule, state.applyToken, frameHint);
mTransactionHandler.queueTransaction(std::move(state));
-
+ setTransactionFlags(eTransactionFlushNeeded, schedule, applyToken, frameHint);
return NO_ERROR;
}
@@ -4194,15 +4337,17 @@
Vector<DisplayState>& displays, uint32_t flags,
const InputWindowCommands& inputWindowCommands,
const int64_t desiredPresentTime, bool isAutoTimestamp,
- const std::vector<uint64_t>& uncacheBufferIds,
+ const client_cache_t& uncacheBuffer,
const int64_t postTime, uint32_t permissions,
bool hasListenerCallbacks,
const std::vector<ListenerCallbacks>& listenerCallbacks,
int originPid, int originUid, uint64_t transactionId) {
uint32_t transactionFlags = 0;
- for (DisplayState& display : displays) {
- display.sanitize(permissions);
- transactionFlags |= setDisplayStateLocked(display);
+ if (!mLayerLifecycleManagerEnabled) {
+ for (DisplayState& display : displays) {
+ display.sanitize(permissions);
+ transactionFlags |= setDisplayStateLocked(display);
+ }
}
// start and end registration for listeners w/ no surface so they can get their callback. Note
@@ -4214,9 +4359,16 @@
uint32_t clientStateFlags = 0;
for (auto& resolvedState : states) {
- clientStateFlags |=
- setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime,
- isAutoTimestamp, postTime, permissions, transactionId);
+ if (mLegacyFrontEndEnabled) {
+ clientStateFlags |=
+ setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime,
+ isAutoTimestamp, postTime, permissions, transactionId);
+
+ } else /*mLayerLifecycleManagerEnabled*/ {
+ clientStateFlags |= updateLayerCallbacksAndStats(frameTimelineInfo, resolvedState,
+ desiredPresentTime, isAutoTimestamp,
+ postTime, permissions, transactionId);
+ }
if ((flags & eAnimation) && resolvedState.state.surface) {
if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) {
using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType;
@@ -4235,8 +4387,11 @@
ALOGE("Only privileged callers are allowed to send input commands.");
}
- for (uint64_t uncacheBufferId : uncacheBufferIds) {
- mBufferIdsToUncache.push_back(uncacheBufferId);
+ if (uncacheBuffer.isValid()) {
+ sp<GraphicBuffer> buffer = ClientCache::getInstance().erase(uncacheBuffer);
+ if (buffer != nullptr) {
+ mBufferIdsToUncache.push_back(buffer->getId());
+ }
}
// If a synchronous transaction is explicitly requested without any changes, force a transaction
@@ -4249,8 +4404,8 @@
bool needsTraversal = false;
if (transactionFlags) {
- // We are on the main thread, we are about to preform a traversal. Clear the traversal bit
- // so we don't have to wake up again next frame to preform an unnecessary traversal.
+ // We are on the main thread, we are about to perform a traversal. Clear the traversal bit
+ // so we don't have to wake up again next frame to perform an unnecessary traversal.
if (transactionFlags & eTraversalNeeded) {
transactionFlags = transactionFlags & (~eTraversalNeeded);
needsTraversal = true;
@@ -4263,6 +4418,42 @@
return needsTraversal;
}
+bool SurfaceFlinger::applyAndCommitDisplayTransactionStates(
+ std::vector<TransactionState>& transactions) {
+ Mutex::Autolock _l(mStateLock);
+ bool needsTraversal = false;
+ uint32_t transactionFlags = 0;
+ for (auto& transaction : transactions) {
+ for (DisplayState& display : transaction.displays) {
+ display.sanitize(transaction.permissions);
+ transactionFlags |= setDisplayStateLocked(display);
+ }
+ }
+
+ if (transactionFlags) {
+ // We are on the main thread, we are about to perform a traversal. Clear the traversal bit
+ // so we don't have to wake up again next frame to perform an unnecessary traversal.
+ if (transactionFlags & eTraversalNeeded) {
+ transactionFlags = transactionFlags & (~eTraversalNeeded);
+ needsTraversal = true;
+ }
+ if (transactionFlags) {
+ setTransactionFlags(transactionFlags);
+ }
+ }
+
+ mFrontEndDisplayInfosChanged = mTransactionFlags & eDisplayTransactionNeeded;
+ if (mFrontEndDisplayInfosChanged && !mLegacyFrontEndEnabled) {
+ processDisplayChangesLocked();
+ mFrontEndDisplayInfos.clear();
+ for (const auto& [_, display] : mDisplays) {
+ mFrontEndDisplayInfos.try_emplace(display->getLayerStack(), display->getFrontEndInfo());
+ }
+ }
+
+ return needsTraversal;
+}
+
uint32_t SurfaceFlinger::setDisplayStateLocked(const DisplayState& s) {
const ssize_t index = mCurrentState.displays.indexOfKey(s.token);
if (index < 0) return 0;
@@ -4366,8 +4557,10 @@
}
if (layer == nullptr) {
for (auto& [listener, callbackIds] : s.listeners) {
- mTransactionCallbackInvoker.registerUnpresentedCallbackHandle(
- sp<CallbackHandle>::make(listener, callbackIds, s.surface));
+ mTransactionCallbackInvoker.addCallbackHandle(sp<CallbackHandle>::make(listener,
+ callbackIds,
+ s.surface),
+ std::vector<JankData>());
}
return 0;
}
@@ -4497,9 +4690,6 @@
if (what & layer_state_t::eDataspaceChanged) {
if (layer->setDataspace(s.dataspace)) flags |= eTraversalNeeded;
}
- if (what & layer_state_t::eHdrMetadataChanged) {
- if (layer->setHdrMetadata(s.hdrMetadata)) flags |= eTraversalNeeded;
- }
if (what & layer_state_t::eSurfaceDamageRegionChanged) {
if (layer->setSurfaceDamageRegion(s.surfaceDamageRegion)) flags |= eTraversalNeeded;
}
@@ -4573,6 +4763,14 @@
if (what & layer_state_t::eDimmingEnabledChanged) {
if (layer->setDimmingEnabled(s.dimmingEnabled)) flags |= eTraversalNeeded;
}
+ if (what & layer_state_t::eExtendedRangeBrightnessChanged) {
+ if (layer->setExtendedRangeBrightness(s.currentSdrHdrRatio, s.desiredSdrHdrRatio)) {
+ flags |= eTraversalNeeded;
+ }
+ }
+ if (what & layer_state_t::eHdrMetadataChanged) {
+ if (layer->setHdrMetadata(s.hdrMetadata)) flags |= eTraversalNeeded;
+ }
if (what & layer_state_t::eTrustedOverlayChanged) {
if (layer->setTrustedOverlay(s.isTrustedOverlay)) {
flags |= eTraversalNeeded;
@@ -4639,7 +4837,15 @@
s.trustedPresentationListener);
}
- if (layer->setTransactionCompletedListeners(callbackHandles)) flags |= eTraversalNeeded;
+ if (what & layer_state_t::eFlushJankData) {
+ // Do nothing. Processing the transaction completed listeners currently cause the flush.
+ }
+
+ if (layer->setTransactionCompletedListeners(callbackHandles,
+ layer->willPresentCurrentTransaction())) {
+ flags |= eTraversalNeeded;
+ }
+
// Do not put anything that updates layer state or modifies flags after
// setTransactionCompletedListener
@@ -4652,6 +4858,96 @@
return flags;
}
+uint32_t SurfaceFlinger::updateLayerCallbacksAndStats(const FrameTimelineInfo& frameTimelineInfo,
+ ResolvedComposerState& composerState,
+ int64_t desiredPresentTime,
+ bool isAutoTimestamp, int64_t postTime,
+ uint32_t permissions,
+ uint64_t transactionId) {
+ layer_state_t& s = composerState.state;
+ s.sanitize(permissions);
+ const nsecs_t latchTime = systemTime();
+ bool unused;
+
+ std::vector<ListenerCallbacks> filteredListeners;
+ for (auto& listener : s.listeners) {
+ // Starts a registration but separates the callback ids according to callback type. This
+ // allows the callback invoker to send on latch callbacks earlier.
+ // note that startRegistration will not re-register if the listener has
+ // already be registered for a prior surface control
+
+ ListenerCallbacks onCommitCallbacks = listener.filter(CallbackId::Type::ON_COMMIT);
+ if (!onCommitCallbacks.callbackIds.empty()) {
+ filteredListeners.push_back(onCommitCallbacks);
+ }
+
+ ListenerCallbacks onCompleteCallbacks = listener.filter(CallbackId::Type::ON_COMPLETE);
+ if (!onCompleteCallbacks.callbackIds.empty()) {
+ filteredListeners.push_back(onCompleteCallbacks);
+ }
+ }
+
+ const uint64_t what = s.what;
+ uint32_t flags = 0;
+ sp<Layer> layer = nullptr;
+ if (s.surface) {
+ layer = LayerHandle::getLayer(s.surface);
+ } else {
+ // The client may provide us a null handle. Treat it as if the layer was removed.
+ ALOGW("Attempt to set client state with a null layer handle");
+ }
+ if (layer == nullptr) {
+ for (auto& [listener, callbackIds] : s.listeners) {
+ mTransactionCallbackInvoker.addCallbackHandle(sp<CallbackHandle>::make(listener,
+ callbackIds,
+ s.surface),
+ std::vector<JankData>());
+ }
+ return 0;
+ }
+ if (what & layer_state_t::eProducerDisconnect) {
+ layer->onDisconnect();
+ }
+ std::optional<nsecs_t> dequeueBufferTimestamp;
+ if (what & layer_state_t::eMetadataChanged) {
+ dequeueBufferTimestamp = s.metadata.getInt64(gui::METADATA_DEQUEUE_TIME);
+ }
+
+ std::vector<sp<CallbackHandle>> callbackHandles;
+ if ((what & layer_state_t::eHasListenerCallbacksChanged) && (!filteredListeners.empty())) {
+ for (auto& [listener, callbackIds] : filteredListeners) {
+ callbackHandles.emplace_back(
+ sp<CallbackHandle>::make(listener, callbackIds, s.surface));
+ }
+ }
+ if (what & layer_state_t::eSidebandStreamChanged) {
+ if (layer->setSidebandStream(s.sidebandStream)) flags |= eTraversalNeeded;
+ }
+ if (what & layer_state_t::eBufferChanged) {
+ if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime,
+ desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp,
+ frameTimelineInfo)) {
+ layer->latchBuffer(unused, latchTime);
+ flags |= eTraversalNeeded;
+ }
+ mLayersWithQueuedFrames.emplace(layer);
+ } else if (frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) {
+ layer->setFrameTimelineVsyncForBufferlessTransaction(frameTimelineInfo, postTime);
+ }
+
+ if (what & layer_state_t::eTrustedPresentationInfoChanged) {
+ layer->setTrustedPresentationInfo(s.trustedPresentationThresholds,
+ s.trustedPresentationListener);
+ }
+
+ const auto& snapshot = mLayerSnapshotBuilder.getSnapshot(layer->getSequence());
+ bool willPresentCurrentTransaction =
+ snapshot && (snapshot->hasReadyFrame || snapshot->sidebandStreamHasFrame);
+ if (layer->setTransactionCompletedListeners(callbackHandles, willPresentCurrentTransaction))
+ flags |= eTraversalNeeded;
+ return flags;
+}
+
uint32_t SurfaceFlinger::addInputWindowCommands(const InputWindowCommands& inputWindowCommands) {
bool hasChanges = mInputWindowCommands.merge(inputWindowCommands);
return hasChanges ? eTraversalNeeded : 0;
@@ -4720,6 +5016,7 @@
LayerCreationArgs mirrorArgs(args);
mirrorArgs.flags |= ISurfaceComposerClient::eNoColorFill;
mirrorArgs.addToRoot = true;
+ mirrorArgs.layerStackToMirror = layerStack;
result = createEffectLayer(mirrorArgs, &outResult.handle, &rootMirrorLayer);
outResult.layerId = rootMirrorLayer->sequence;
outResult.layerName = String16(rootMirrorLayer->getDebugName());
@@ -4822,7 +5119,12 @@
setTransactionFlags(eTransactionNeeded);
}
-void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t /* layerId */) {
+void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t layerId) {
+ {
+ std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
+ mDestroyedHandles.emplace_back(layerId);
+ }
+
Mutex::Autolock lock(mStateLock);
markLayerPendingRemovalLocked(layer);
mBufferCountTracker.remove(handle);
@@ -4830,6 +5132,8 @@
if (mTransactionTracing) {
mTransactionTracing->onHandleRemoved(handle);
}
+
+ setTransactionFlags(eTransactionFlushNeeded);
}
void SurfaceFlinger::onInitializeDisplays() {
@@ -4930,7 +5234,8 @@
}
getHwComposer().setPowerMode(displayId, mode);
if (isActiveDisplay && mode != hal::PowerMode::DOZE_SUSPEND) {
- setHWCVsyncEnabled(displayId, mHWCVsyncPendingState);
+ setHWCVsyncEnabled(displayId,
+ mScheduler->getVsyncSchedule().getPendingHardwareVsyncState());
mScheduler->onScreenAcquired(mAppConnectionHandle);
mScheduler->resyncToHardwareVsync(true, refreshRate);
}
@@ -4951,7 +5256,7 @@
}
// Make sure HWVsync is disabled before turning off the display
- setHWCVsyncEnabled(displayId, hal::Vsync::DISABLE);
+ setHWCVsyncEnabled(displayId, false);
getHwComposer().setPowerMode(displayId, mode);
mVisibleRegionsDirty = true;
@@ -5160,14 +5465,6 @@
mScheduler->dump(dumper);
- // TODO(b/241286146): Move to Scheduler.
- {
- utils::Dumper::Indent indent(dumper);
- dumper.dump("lastHwcVsyncState"sv, mLastHWCVsyncState);
- dumper.dump("pendingHwcVsyncState"sv, mHWCVsyncPendingState);
- }
- dumper.eol();
-
// TODO(b/241285876): Move to DisplayModeController.
dumper.dump("debugDisplayModeSetByBackdoor"sv, mDebugDisplayModeSetByBackdoor);
dumper.eol();
@@ -6399,11 +6696,17 @@
args.useIdentityTransform, args.captureSecureLayers);
});
- auto traverseLayers = [this, args, layerStack](const LayerVector::Visitor& visitor) {
- traverseLayersInLayerStack(layerStack, args.uid, visitor);
- };
+ GetLayerSnapshotsFunction getLayerSnapshots;
+ if (mLayerLifecycleManagerEnabled) {
+ getLayerSnapshots = getLayerSnapshotsForScreenshots(layerStack, args.uid);
+ } else {
+ auto traverseLayers = [this, args, layerStack](const LayerVector::Visitor& visitor) {
+ traverseLayersInLayerStack(layerStack, args.uid, visitor);
+ };
+ getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
+ }
- auto future = captureScreenCommon(std::move(renderAreaFuture), traverseLayers, reqSize,
+ auto future = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize,
args.pixelFormat, args.allowProtected, args.grayscale,
captureListener);
return fenceStatus(future.get());
@@ -6435,9 +6738,15 @@
false /* captureSecureLayers */);
});
- auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) {
- traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, visitor);
- };
+ GetLayerSnapshotsFunction getLayerSnapshots;
+ if (mLayerLifecycleManagerEnabled) {
+ getLayerSnapshots = getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID);
+ } else {
+ auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) {
+ traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, visitor);
+ };
+ getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
+ }
if (captureListener == nullptr) {
ALOGE("capture screen must provide a capture listener callback");
@@ -6447,7 +6756,7 @@
constexpr bool kAllowProtected = false;
constexpr bool kGrayscale = false;
- auto future = captureScreenCommon(std::move(renderAreaFuture), traverseLayers, size,
+ auto future = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, size,
ui::PixelFormat::RGBA_8888, kAllowProtected, kGrayscale,
captureListener);
return fenceStatus(future.get());
@@ -6465,7 +6774,7 @@
ui::Size reqSize;
sp<Layer> parent;
Rect crop(args.sourceCrop);
- std::unordered_set<sp<Layer>, SpHash<Layer>> excludeLayers;
+ std::unordered_set<uint32_t> excludeLayerIds;
ui::Dataspace dataspace;
// Call this before holding mStateLock to avoid any deadlocking.
@@ -6505,9 +6814,9 @@
reqSize = ui::Size(crop.width() * args.frameScaleX, crop.height() * args.frameScaleY);
for (const auto& handle : args.excludeHandles) {
- sp<Layer> excludeLayer = LayerHandle::getLayer(handle);
- if (excludeLayer != nullptr) {
- excludeLayers.emplace(excludeLayer);
+ uint32_t excludeLayer = LayerHandle::getLayerId(handle);
+ if (excludeLayer != UNASSIGNED_LAYER_ID) {
+ excludeLayerIds.emplace(excludeLayer);
} else {
ALOGW("Invalid layer handle passed as excludeLayer to captureLayers");
return NAME_NOT_FOUND;
@@ -6527,50 +6836,56 @@
return BAD_VALUE;
}
- Rect layerStackSpaceRect(crop.left, crop.top, crop.left + reqSize.width,
- crop.top + reqSize.height);
bool childrenOnly = args.childrenOnly;
RenderAreaFuture renderAreaFuture = ftl::defer([=]() -> std::unique_ptr<RenderArea> {
return std::make_unique<LayerRenderArea>(*this, parent, crop, reqSize, dataspace,
- childrenOnly, layerStackSpaceRect,
- args.captureSecureLayers);
+ childrenOnly, args.captureSecureLayers);
});
-
- auto traverseLayers = [parent, args, excludeLayers](const LayerVector::Visitor& visitor) {
- parent->traverseChildrenInZOrder(LayerVector::StateSet::Drawing, [&](Layer* layer) {
- if (!layer->isVisible()) {
- return;
- } else if (args.childrenOnly && layer == parent.get()) {
- return;
- } else if (args.uid != CaptureArgs::UNSET_UID && args.uid != layer->getOwnerUid()) {
- return;
- }
-
- auto p = sp<Layer>::fromExisting(layer);
- while (p != nullptr) {
- if (excludeLayers.count(p) != 0) {
+ GetLayerSnapshotsFunction getLayerSnapshots;
+ if (mLayerLifecycleManagerEnabled) {
+ FloatRect parentCrop = crop.isEmpty() ? FloatRect(0, 0, reqSize.width, reqSize.height)
+ : crop.toFloatRect();
+ getLayerSnapshots = getLayerSnapshotsForScreenshots(parent->sequence, args.uid,
+ std::move(excludeLayerIds),
+ args.childrenOnly, parentCrop);
+ } else {
+ auto traverseLayers = [parent, args, excludeLayerIds](const LayerVector::Visitor& visitor) {
+ parent->traverseChildrenInZOrder(LayerVector::StateSet::Drawing, [&](Layer* layer) {
+ if (!layer->isVisible()) {
+ return;
+ } else if (args.childrenOnly && layer == parent.get()) {
+ return;
+ } else if (args.uid != CaptureArgs::UNSET_UID && args.uid != layer->getOwnerUid()) {
return;
}
- p = p->getParent();
- }
- visitor(layer);
- });
- };
+ auto p = sp<Layer>::fromExisting(layer);
+ while (p != nullptr) {
+ if (excludeLayerIds.count(p->sequence) != 0) {
+ return;
+ }
+ p = p->getParent();
+ }
+
+ visitor(layer);
+ });
+ };
+ getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
+ }
if (captureListener == nullptr) {
ALOGE("capture screen must provide a capture listener callback");
return BAD_VALUE;
}
- auto future = captureScreenCommon(std::move(renderAreaFuture), traverseLayers, reqSize,
+ auto future = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize,
args.pixelFormat, args.allowProtected, args.grayscale,
captureListener);
return fenceStatus(future.get());
}
ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenCommon(
- RenderAreaFuture renderAreaFuture, TraverseLayersFunction traverseLayers,
+ RenderAreaFuture renderAreaFuture, GetLayerSnapshotsFunction getLayerSnapshots,
ui::Size bufferSize, ui::PixelFormat reqPixelFormat, bool allowProtected, bool grayscale,
const sp<IScreenCaptureListener>& captureListener) {
ATRACE_CALL();
@@ -6589,15 +6904,18 @@
const bool supportsProtected = getRenderEngine().supportsProtectedContent();
bool hasProtectedLayer = false;
if (allowProtected && supportsProtected) {
- auto future = mScheduler->schedule([=]() {
- bool protectedLayerFound = false;
- traverseLayers([&](Layer* layer) {
- protectedLayerFound =
- protectedLayerFound || (layer->isVisible() && layer->isProtected());
- });
- return protectedLayerFound;
- });
- hasProtectedLayer = future.get();
+ hasProtectedLayer = mScheduler
+ ->schedule([=]() {
+ bool protectedLayerFound = false;
+ auto layers = getLayerSnapshots();
+ for (auto& [layer, layerFe] : layers) {
+ protectedLayerFound |=
+ (layerFe->mSnapshot->isVisible &&
+ layerFe->mSnapshot->hasProtectedContent);
+ }
+ return protectedLayerFound;
+ })
+ .get();
}
const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER |
@@ -6622,12 +6940,12 @@
renderengine::impl::ExternalTexture>(buffer, getRenderEngine(),
renderengine::impl::ExternalTexture::Usage::
WRITEABLE);
- return captureScreenCommon(std::move(renderAreaFuture), traverseLayers, texture,
+ return captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, texture,
false /* regionSampling */, grayscale, captureListener);
}
ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenCommon(
- RenderAreaFuture renderAreaFuture, TraverseLayersFunction traverseLayers,
+ RenderAreaFuture renderAreaFuture, GetLayerSnapshotsFunction getLayerSnapshots,
const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
bool grayscale, const sp<IScreenCaptureListener>& captureListener) {
ATRACE_CALL();
@@ -6650,7 +6968,7 @@
ftl::SharedFuture<FenceResult> renderFuture;
renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) {
- renderFuture = renderScreenImpl(renderArea, traverseLayers, buffer,
+ renderFuture = renderScreenImpl(renderArea, getLayerSnapshots, buffer,
canCaptureBlackoutContent, regionSampling,
grayscale, captureResults);
});
@@ -6678,18 +6996,20 @@
}
ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
- std::shared_ptr<const RenderArea> renderArea, TraverseLayersFunction traverseLayers,
+ std::shared_ptr<const RenderArea> renderArea, GetLayerSnapshotsFunction getLayerSnapshots,
const std::shared_ptr<renderengine::ExternalTexture>& buffer,
bool canCaptureBlackoutContent, bool regionSampling, bool grayscale,
ScreenCaptureResults& captureResults) {
ATRACE_CALL();
- size_t layerCount = 0;
- traverseLayers([&](Layer* layer) {
- layerCount++;
- captureResults.capturedSecureLayers =
- captureResults.capturedSecureLayers || (layer->isVisible() && layer->isSecure());
- });
+ auto layers = getLayerSnapshots();
+ for (auto& [layer, layerFE] : layers) {
+ frontend::LayerSnapshot* snapshot = layerFE->mSnapshot.get();
+ captureResults.capturedSecureLayers |= (snapshot->isVisible && snapshot->isSecure);
+ captureResults.capturedHdrLayers |= isHdrLayer(*snapshot);
+ layerFE->mSnapshot->geomLayerTransform =
+ renderArea->getTransform() * layerFE->mSnapshot->geomLayerTransform;
+ }
// We allow the system server to take screenshots of secure layers for
// use in situations like the Screen-rotation animation and place
@@ -6723,31 +7043,6 @@
}
captureResults.capturedDataspace = dataspace;
- const auto transform = renderArea->getTransform();
- const auto display = renderArea->getDisplayDevice();
-
- std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
- layers.reserve(layerCount);
- std::unordered_set<compositionengine::LayerFE*> filterForScreenshot;
- traverseLayers([&](Layer* layer) {
- captureResults.capturedHdrLayers |= isHdrLayer(layer);
- // Layer::prepareClientComposition uses the layer's snapshot to populate the resulting
- // LayerSettings. Calling Layer::updateSnapshot ensures that LayerSettings are
- // generated with the layer's current buffer and geometry.
- layer->updateSnapshot(true /* updateGeometry */);
-
- layers.emplace_back(layer, layer->copyCompositionEngineLayerFE());
-
- sp<LayerFE>& layerFE = layers.back().second;
-
- layerFE->mSnapshot->geomLayerTransform =
- renderArea->getTransform() * layerFE->mSnapshot->geomLayerTransform;
-
- if (layer->needsFilteringForScreenshots(display.get(), transform)) {
- filterForScreenshot.insert(layerFE.get());
- }
- });
-
ui::LayerStack layerStack{ui::DEFAULT_LAYER_STACK};
if (!layers.empty()) {
const sp<LayerFE>& layerFE = layers.back().second;
@@ -6764,9 +7059,9 @@
};
auto present = [this, buffer = std::move(buffer), dataspace, sdrWhitePointNits,
- displayBrightnessNits, filterForScreenshot = std::move(filterForScreenshot),
- grayscale, layerFEs = copyLayerFEs(), layerStack, regionSampling,
- renderArea = std::move(renderArea), renderIntent]() -> FenceResult {
+ displayBrightnessNits, grayscale, layerFEs = copyLayerFEs(), layerStack,
+ regionSampling, renderArea = std::move(renderArea),
+ renderIntent]() -> FenceResult {
std::unique_ptr<compositionengine::CompositionEngine> compositionEngine =
mFactory.createCompositionEngine();
compositionEngine->setRenderEngine(mRenderEngine.get());
@@ -6782,7 +7077,6 @@
.buffer = std::move(buffer),
.sdrWhitePointNits = sdrWhitePointNits,
.displayBrightnessNits = displayBrightnessNits,
- .filterForScreenshot = std::move(filterForScreenshot),
.regionSampling = regionSampling});
const float colorSaturation = grayscale ? 0 : 1;
@@ -7359,24 +7653,18 @@
return true;
}
-bool SurfaceFlinger::commitCreatedLayers(VsyncId vsyncId) {
- std::vector<LayerCreatedState> createdLayers;
- {
- std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
- createdLayers = std::move(mCreatedLayers);
- mCreatedLayers.clear();
- if (createdLayers.size() == 0) {
- return false;
- }
+bool SurfaceFlinger::commitCreatedLayers(VsyncId vsyncId,
+ std::vector<LayerCreatedState>& createdLayers) {
+ if (createdLayers.size() == 0) {
+ return false;
}
Mutex::Autolock _l(mStateLock);
for (const auto& createdLayer : createdLayers) {
handleLayerCreatedLocked(createdLayer, vsyncId);
}
- createdLayers.clear();
mLayersAdded = true;
- return true;
+ return mLayersAdded;
}
void SurfaceFlinger::updateLayerMetadataSnapshot() {
@@ -7404,6 +7692,150 @@
});
}
+void SurfaceFlinger::moveSnapshotsFromCompositionArgs(
+ compositionengine::CompositionRefreshArgs& refreshArgs,
+ std::vector<std::pair<Layer*, LayerFE*>>& layers) {
+ if (mLayerLifecycleManagerEnabled) {
+ std::vector<std::unique_ptr<frontend::LayerSnapshot>>& snapshots =
+ mLayerSnapshotBuilder.getSnapshots();
+ for (auto [_, layerFE] : layers) {
+ auto i = layerFE->mSnapshot->globalZ;
+ snapshots[i] = std::move(layerFE->mSnapshot);
+ }
+ }
+ if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) {
+ for (auto [layer, layerFE] : layers) {
+ layer->updateLayerSnapshot(std::move(layerFE->mSnapshot));
+ }
+ }
+}
+
+std::vector<std::pair<Layer*, LayerFE*>> SurfaceFlinger::moveSnapshotsToCompositionArgs(
+ compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly, int64_t vsyncId) {
+ std::vector<std::pair<Layer*, LayerFE*>> layers;
+ if (mLayerLifecycleManagerEnabled) {
+ mLayerSnapshotBuilder.forEachVisibleSnapshot(
+ [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+ if (cursorOnly &&
+ snapshot->compositionType !=
+ aidl::android::hardware::graphics::composer3::Composition::CURSOR) {
+ return;
+ }
+
+ if (!snapshot->hasSomethingToDraw()) {
+ return;
+ }
+
+ auto it = mLegacyLayers.find(snapshot->sequence);
+ LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(),
+ "Couldnt find layer object for %s",
+ snapshot->getDebugString().c_str());
+ auto& legacyLayer = it->second;
+ sp<LayerFE> layerFE = legacyLayer->getCompositionEngineLayerFE(snapshot->path);
+ layerFE->mSnapshot = std::move(snapshot);
+ refreshArgs.layers.push_back(layerFE);
+ layers.emplace_back(legacyLayer.get(), layerFE.get());
+ });
+ }
+ if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) {
+ mDrawingState.traverseInZOrder([&refreshArgs, cursorOnly, &layers](Layer* layer) {
+ if (const auto& layerFE = layer->getCompositionEngineLayerFE()) {
+ if (cursorOnly &&
+ layer->getLayerSnapshot()->compositionType !=
+ aidl::android::hardware::graphics::composer3::Composition::CURSOR)
+ return;
+ layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame);
+ layerFE->mSnapshot = layer->stealLayerSnapshot();
+ refreshArgs.layers.push_back(layerFE);
+ layers.emplace_back(layer, layerFE.get());
+ }
+ });
+ }
+
+ return layers;
+}
+
+std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()>
+SurfaceFlinger::getLayerSnapshotsForScreenshots(std::optional<ui::LayerStack> layerStack,
+ uint32_t uid) {
+ return [this, layerStack, uid]() {
+ std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
+ for (auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) {
+ if (layerStack && snapshot->outputFilter.layerStack != *layerStack) {
+ continue;
+ }
+ if (uid != CaptureArgs::UNSET_UID && snapshot->inputInfo.ownerUid != uid) {
+ continue;
+ }
+ if (!snapshot->isVisible || !snapshot->hasSomethingToDraw()) {
+ continue;
+ }
+
+ auto it = mLegacyLayers.find(snapshot->sequence);
+ LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s",
+ snapshot->getDebugString().c_str());
+ auto& legacyLayer = it->second;
+ sp<LayerFE> layerFE = getFactory().createLayerFE(legacyLayer->getName());
+ layerFE->mSnapshot = std::make_unique<frontend::LayerSnapshot>(*snapshot);
+ layers.emplace_back(legacyLayer.get(), std::move(layerFE));
+ }
+
+ return layers;
+ };
+}
+
+std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()>
+SurfaceFlinger::getLayerSnapshotsForScreenshots(uint32_t rootLayerId, uint32_t uid,
+ std::unordered_set<uint32_t> excludeLayerIds,
+ bool childrenOnly, const FloatRect& parentCrop) {
+ return [this, excludeLayerIds = std::move(excludeLayerIds), uid, rootLayerId, childrenOnly,
+ parentCrop]() {
+ frontend::LayerSnapshotBuilder::Args
+ args{.root = mLayerHierarchyBuilder.getPartialHierarchy(rootLayerId, childrenOnly),
+ .layerLifecycleManager = mLayerLifecycleManager,
+ .displays = mFrontEndDisplayInfos,
+ .displayChanges = true,
+ .globalShadowSettings = mDrawingState.globalShadowSettings,
+ .supportsBlur = mSupportsBlur,
+ .forceFullDamage = mForceFullDamage,
+ .parentCrop = {parentCrop},
+ .excludeLayerIds = std::move(excludeLayerIds)};
+ mLayerSnapshotBuilder.update(args);
+
+ auto getLayerSnapshotsFn = getLayerSnapshotsForScreenshots({}, uid);
+ std::vector<std::pair<Layer*, sp<LayerFE>>> layers = getLayerSnapshotsFn();
+ args.root = mLayerHierarchyBuilder.getHierarchy();
+ args.parentCrop.reset();
+ args.excludeLayerIds.clear();
+ mLayerSnapshotBuilder.update(args);
+ return layers;
+ };
+}
+
+SurfaceFlinger::LifecycleUpdate SurfaceFlinger::flushLifecycleUpdates() {
+ LifecycleUpdate update;
+ ATRACE_NAME("TransactionHandler:flushTransactions");
+ // Locking:
+ // 1. to prevent onHandleDestroyed from being called while the state lock is held,
+ // we must keep a copy of the transactions (specifically the composer
+ // states) around outside the scope of the lock.
+ // 2. Transactions and created layers do not share a lock. To prevent applying
+ // transactions with layers still in the createdLayer queue, flush the transactions
+ // before committing the created layers.
+ update.transactions = mTransactionHandler.flushTransactions();
+ {
+ // TODO(b/238781169) lockless queue this and keep order.
+ std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
+ update.layerCreatedStates = std::move(mCreatedLayers);
+ mCreatedLayers.clear();
+ update.newLayers = std::move(mNewLayers);
+ mNewLayers.clear();
+ update.destroyedHandles = std::move(mDestroyedHandles);
+ mDestroyedHandles.clear();
+ }
+ return update;
+}
+
// gui::ISurfaceComposer
binder::Status SurfaceComposerAIDL::bootFinished() {
@@ -7417,9 +7849,9 @@
binder::Status SurfaceComposerAIDL::createDisplayEventConnection(
VsyncSource vsyncSource, EventRegistration eventRegistration,
- sp<IDisplayEventConnection>* outConnection) {
+ const sp<IBinder>& layerHandle, sp<IDisplayEventConnection>* outConnection) {
sp<IDisplayEventConnection> conn =
- mFlinger->createDisplayEventConnection(vsyncSource, eventRegistration);
+ mFlinger->createDisplayEventConnection(vsyncSource, eventRegistration, layerHandle);
if (conn == nullptr) {
*outConnection = nullptr;
return binderStatusFromStatusT(BAD_VALUE);
@@ -7441,13 +7873,14 @@
}
binder::Status SurfaceComposerAIDL::createDisplay(const std::string& displayName, bool secure,
+ float requestedRefreshRate,
sp<IBinder>* outDisplay) {
status_t status = checkAccessPermission();
if (status != OK) {
return binderStatusFromStatusT(status);
}
String8 displayName8 = String8::format("%s", displayName.c_str());
- *outDisplay = mFlinger->createDisplay(displayName8, secure);
+ *outDisplay = mFlinger->createDisplay(displayName8, secure, requestedRefreshRate);
return binder::Status::ok();
}
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 9601f2e..4171d39 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -57,6 +57,8 @@
#include <scheduler/PresentLatencyTracker.h>
#include <scheduler/Time.h>
#include <scheduler/TransactionSchedule.h>
+#include <scheduler/interface/CompositionCoverage.h>
+#include <scheduler/interface/ICompositor.h>
#include <ui/FenceResult.h>
#include "Display/DisplayMap.h"
@@ -69,8 +71,12 @@
#include "FlagManager.h"
#include "FrontEnd/DisplayInfo.h"
#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/LayerLifecycleManager.h"
+#include "FrontEnd/LayerSnapshot.h"
+#include "FrontEnd/LayerSnapshotBuilder.h"
#include "FrontEnd/TransactionHandler.h"
#include "LayerVector.h"
+#include "Scheduler/ISchedulerCallback.h"
#include "Scheduler/RefreshRateSelector.h"
#include "Scheduler/RefreshRateStats.h"
#include "Scheduler/Scheduler.h"
@@ -99,6 +105,7 @@
#include <vector>
#include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+#include <aidl/android/hardware/graphics/composer3/RefreshRateChangedDebugData.h>
#include "Client.h"
using namespace android::surfaceflinger;
@@ -122,6 +129,7 @@
class ScreenCapturer;
class WindowInfosListenerInvoker;
+using ::aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
using frontend::TransactionHandler;
using gui::CaptureArgs;
using gui::DisplayCaptureArgs;
@@ -346,6 +354,7 @@
friend class LayerRenderArea;
friend class LayerTracing;
friend class SurfaceComposerAIDL;
+ friend class DisplayRenderArea;
// For unit tests
friend class TestableSurfaceFlinger;
@@ -353,7 +362,7 @@
friend class TunnelModeEnabledReporterTest;
using TransactionSchedule = scheduler::TransactionSchedule;
- using TraverseLayersFunction = std::function<void(const LayerVector::Visitor&)>;
+ using GetLayerSnapshotsFunction = std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()>;
using RenderAreaFuture = ftl::Future<std::unique_ptr<RenderArea>>;
using DumpArgs = Vector<String16>;
using Dumper = std::function<void(const DumpArgs&, bool asProto, std::string&)>;
@@ -445,6 +454,26 @@
FINISHED,
};
+ struct LayerCreatedState {
+ LayerCreatedState(const wp<Layer>& layer, const wp<Layer>& parent, bool addToRoot)
+ : layer(layer), initialParent(parent), addToRoot(addToRoot) {}
+ wp<Layer> layer;
+ // Indicates the initial parent of the created layer, only used for creating layer in
+ // SurfaceFlinger. If nullptr, it may add the created layer into the current root layers.
+ wp<Layer> initialParent;
+ // Indicates whether the layer getting created should be added at root if there's no parent
+ // and has permission ACCESS_SURFACE_FLINGER. If set to false and no parent, the layer will
+ // be added offscreen.
+ bool addToRoot;
+ };
+
+ struct LifecycleUpdate {
+ std::vector<TransactionState> transactions;
+ std::vector<LayerCreatedState> layerCreatedStates;
+ std::vector<std::unique_ptr<frontend::RequestedLayerState>> newLayers;
+ std::vector<uint32_t> destroyedHandles;
+ };
+
template <typename F, std::enable_if_t<!std::is_member_function_pointer_v<F>>* = nullptr>
static Dumper dumper(F&& dump) {
using namespace std::placeholders;
@@ -481,7 +510,8 @@
EXCLUDES(mStateLock);
// Implements ISurfaceComposer
- sp<IBinder> createDisplay(const String8& displayName, bool secure);
+ sp<IBinder> createDisplay(const String8& displayName, bool secure,
+ float requestedRefreshRate = 0);
void destroyDisplay(const sp<IBinder>& displayToken);
std::vector<PhysicalDisplayId> getPhysicalDisplayIds() const EXCLUDES(mStateLock) {
Mutex::Autolock lock(mStateLock);
@@ -494,8 +524,7 @@
uint32_t flags, const sp<IBinder>& applyToken,
const InputWindowCommands& inputWindowCommands,
int64_t desiredPresentTime, bool isAutoTimestamp,
- const std::vector<client_cache_t>& uncacheBuffers,
- bool hasListenerCallbacks,
+ const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
const std::vector<ListenerCallbacks>& listenerCallbacks,
uint64_t transactionId) override;
void bootFinished();
@@ -503,7 +532,8 @@
sp<IDisplayEventConnection> createDisplayEventConnection(
gui::ISurfaceComposer::VsyncSource vsyncSource =
gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp,
- EventRegistrationFlags eventRegistration = {});
+ EventRegistrationFlags eventRegistration = {},
+ const sp<IBinder>& layerHandle = nullptr);
status_t captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&);
status_t captureDisplay(DisplayId, const sp<IScreenCaptureListener>&);
@@ -602,21 +632,12 @@
const hal::VsyncPeriodChangeTimeline&) override;
void onComposerHalSeamlessPossible(hal::HWDisplayId) override;
void onComposerHalVsyncIdle(hal::HWDisplayId) override;
+ void onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) override;
// ICompositor overrides:
-
- // Configures physical displays, processing hotplug and/or mode setting via the Composer HAL.
void configure() override;
-
- // Commits transactions for layers and displays. Returns whether any state has been invalidated,
- // i.e. whether a frame should be composited for each display.
bool commit(TimePoint frameTime, VsyncId, TimePoint expectedVsyncTime) override;
-
- // Composites a frame for each display. CompositionEngine performs GPU and/or HAL composition
- // via RenderEngine and the Composer HAL, respectively.
void composite(TimePoint frameTime, VsyncId) override;
-
- // Samples the composited frame via RegionSamplingThread.
void sample() override;
// ISchedulerCallback overrides:
@@ -692,6 +713,17 @@
void updateLayerGeometry();
void updateLayerMetadataSnapshot();
+ std::vector<std::pair<Layer*, LayerFE*>> moveSnapshotsToCompositionArgs(
+ compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly,
+ int64_t vsyncId);
+ void moveSnapshotsFromCompositionArgs(compositionengine::CompositionRefreshArgs& refreshArgs,
+ std::vector<std::pair<Layer*, LayerFE*>>& layers);
+ bool updateLayerSnapshotsLegacy(VsyncId vsyncId, LifecycleUpdate& update,
+ bool transactionsFlushed, bool& out)
+ REQUIRES(kMainThreadContext);
+ bool updateLayerSnapshots(VsyncId vsyncId, LifecycleUpdate& update, bool transactionsFlushed,
+ bool& out) REQUIRES(kMainThreadContext);
+ LifecycleUpdate flushLifecycleUpdates() REQUIRES(kMainThreadContext);
void updateInputFlinger();
void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext);
@@ -706,19 +738,23 @@
/*
* Transactions
*/
- bool applyTransactionState(
- const FrameTimelineInfo& info, std::vector<ResolvedComposerState>& state,
- Vector<DisplayState>& displays, uint32_t flags,
- const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime,
- bool isAutoTimestamp, const std::vector<uint64_t>& uncacheBufferIds,
- const int64_t postTime, uint32_t permissions, bool hasListenerCallbacks,
- const std::vector<ListenerCallbacks>& listenerCallbacks, int originPid, int originUid,
- uint64_t transactionId) REQUIRES(mStateLock);
+ bool applyTransactionState(const FrameTimelineInfo& info,
+ std::vector<ResolvedComposerState>& state,
+ Vector<DisplayState>& displays, uint32_t flags,
+ const InputWindowCommands& inputWindowCommands,
+ const int64_t desiredPresentTime, bool isAutoTimestamp,
+ const client_cache_t& uncacheBuffer, const int64_t postTime,
+ uint32_t permissions, bool hasListenerCallbacks,
+ const std::vector<ListenerCallbacks>& listenerCallbacks,
+ int originPid, int originUid, uint64_t transactionId)
+ REQUIRES(mStateLock);
// Flush pending transactions that were presented after desiredPresentTime.
// For test only
bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext);
bool applyTransactions(std::vector<TransactionState>&, VsyncId) REQUIRES(kMainThreadContext);
+ bool applyAndCommitDisplayTransactionStates(std::vector<TransactionState>& transactions)
+ REQUIRES(kMainThreadContext);
// Returns true if there is at least one transaction that needs to be flushed
bool transactionFlushNeeded();
@@ -734,7 +770,10 @@
int64_t desiredPresentTime, bool isAutoTimestamp,
int64_t postTime, uint32_t permissions, uint64_t transactionId)
REQUIRES(mStateLock);
-
+ uint32_t updateLayerCallbacksAndStats(const FrameTimelineInfo&, ResolvedComposerState&,
+ int64_t desiredPresentTime, bool isAutoTimestamp,
+ int64_t postTime, uint32_t permissions,
+ uint64_t transactionId) REQUIRES(mStateLock);
uint32_t getTransactionFlags() const;
// Sets the masked bits, and schedules a commit if needed.
@@ -787,16 +826,16 @@
// Boot animation, on/off animations and screen capture
void startBootAnim();
- ftl::SharedFuture<FenceResult> captureScreenCommon(RenderAreaFuture, TraverseLayersFunction,
+ ftl::SharedFuture<FenceResult> captureScreenCommon(RenderAreaFuture, GetLayerSnapshotsFunction,
ui::Size bufferSize, ui::PixelFormat,
bool allowProtected, bool grayscale,
const sp<IScreenCaptureListener>&);
ftl::SharedFuture<FenceResult> captureScreenCommon(
- RenderAreaFuture, TraverseLayersFunction,
+ RenderAreaFuture, GetLayerSnapshotsFunction,
const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
bool grayscale, const sp<IScreenCaptureListener>&);
ftl::SharedFuture<FenceResult> renderScreenImpl(
- std::shared_ptr<const RenderArea>, TraverseLayersFunction,
+ std::shared_ptr<const RenderArea>, GetLayerSnapshotsFunction,
const std::shared_ptr<renderengine::ExternalTexture>&, bool canCaptureBlackoutContent,
bool regionSampling, bool grayscale, ScreenCaptureResults&) EXCLUDES(mStateLock)
REQUIRES(kMainThreadContext);
@@ -892,7 +931,7 @@
// mark a region of a layer stack dirty. this updates the dirty
// region of all screens presenting this layer stack.
- void invalidateLayerStack(const sp<const Layer>& layer, const Region& dirty);
+ void invalidateLayerStack(const ui::LayerFilter& layerFilter, const Region& dirty);
ui::LayerFilter makeLayerFilterForDisplay(DisplayId displayId, ui::LayerStack layerStack)
REQUIRES(mStateLock) {
@@ -958,9 +997,9 @@
*/
nsecs_t getVsyncPeriodFromHWC() const REQUIRES(mStateLock);
- void setHWCVsyncEnabled(PhysicalDisplayId id, hal::Vsync enabled) {
- mLastHWCVsyncState = enabled;
- getHwComposer().setVsyncEnabled(id, enabled);
+ void setHWCVsyncEnabled(PhysicalDisplayId id, bool enabled) {
+ hal::Vsync halState = enabled ? hal::Vsync::ENABLE : hal::Vsync::DISABLE;
+ getHwComposer().setVsyncEnabled(id, halState);
}
using FenceTimePtr = std::shared_ptr<FenceTime>;
@@ -1085,7 +1124,7 @@
void updateInternalDisplayVsyncLocked(const sp<DisplayDevice>& activeDisplay)
REQUIRES(mStateLock, kMainThreadContext);
- bool isHdrLayer(Layer* layer) const;
+ bool isHdrLayer(const frontend::LayerSnapshot& snapshot) const;
ui::Rotation getPhysicalDisplayOrientation(DisplayId, bool isPrimary) const
REQUIRES(mStateLock);
@@ -1133,9 +1172,8 @@
State mDrawingState{LayerVector::StateSet::Drawing};
bool mVisibleRegionsDirty = false;
- // VisibleRegions dirty is already cleared by postComp, but we need to track it to prevent
- // extra work in the HDR layer info listener.
- bool mVisibleRegionsWereDirtyThisFrame = false;
+ bool mHdrLayerInfoChanged = false;
+
// Used to ensure we omit a callback when HDR layer info listener is newly added but the
// scene hasn't changed
bool mAddingHDRLayerInfoListener = false;
@@ -1146,29 +1184,18 @@
// TODO: Also move visibleRegions over to a boolean system.
bool mUpdateInputInfo = false;
bool mSomeChildrenChanged;
- bool mSomeDataspaceChanged = false;
bool mForceTransactionDisplayChange = false;
// Set if LayerMetadata has changed since the last LayerMetadata snapshot.
bool mLayerMetadataSnapshotNeeded = false;
+ // TODO(b/238781169) validate these on composition
// Tracks layers that have pending frames which are candidates for being
// latched.
std::unordered_set<sp<Layer>, SpHash<Layer>> mLayersWithQueuedFrames;
// Tracks layers that need to update a display's dirty region.
std::vector<sp<Layer>> mLayersPendingRefresh;
- // True if in the previous frame at least one layer was composed via the GPU.
- bool mHadClientComposition = false;
- // True if in the previous frame at least one layer was composed via HW Composer.
- // Note that it is possible for a frame to be composed via both client and device
- // composition, for example in the case of overlays.
- bool mHadDeviceComposition = false;
- // True if in the previous frame, the client composition was skipped by reusing the buffer
- // used in a previous composition. This can happed if the client composition requests
- // did not change.
- bool mReusedClientComposition = false;
-
BootStage mBootStage = BootStage::BOOTLOADER;
struct HotplugEvent {
@@ -1204,7 +1231,7 @@
std::atomic_bool mForceFullDamage = false;
bool mLayerCachingEnabled = false;
- bool mPropagateBackpressureClientComposition = false;
+ bool mBackpressureGpuComposition = false;
LayerTracing mLayerTracing{*this};
bool mLayerTracingEnabled = false;
@@ -1268,6 +1295,9 @@
std::atomic<int> mNumTrustedPresentationListeners = 0;
std::unique_ptr<compositionengine::CompositionEngine> mCompositionEngine;
+
+ CompositionCoverageFlags mCompositionCoverage;
+
// mMaxRenderTargetSize is only set once in init() so it doesn't need to be protected by
// any mutex.
size_t mMaxRenderTargetSize{1};
@@ -1296,9 +1326,6 @@
TimePoint mScheduledPresentTime GUARDED_BY(kMainThreadContext);
TimePoint mExpectedPresentTime GUARDED_BY(kMainThreadContext);
- hal::Vsync mHWCVsyncPendingState = hal::Vsync::DISABLE;
- hal::Vsync mLastHWCVsyncState = hal::Vsync::DISABLE;
-
// below flags are set by main thread only
bool mSetActiveModePending = false;
@@ -1332,24 +1359,13 @@
std::unordered_map<DisplayId, sp<HdrLayerInfoReporter>> mHdrLayerInfoListeners
GUARDED_BY(mStateLock);
+
mutable std::mutex mCreatedLayersLock;
- struct LayerCreatedState {
- LayerCreatedState(const wp<Layer>& layer, const wp<Layer> parent, bool addToRoot)
- : layer(layer), initialParent(parent), addToRoot(addToRoot) {}
- wp<Layer> layer;
- // Indicates the initial parent of the created layer, only used for creating layer in
- // SurfaceFlinger. If nullptr, it may add the created layer into the current root layers.
- wp<Layer> initialParent;
- // Indicates whether the layer getting created should be added at root if there's no parent
- // and has permission ACCESS_SURFACE_FLINGER. If set to false and no parent, the layer will
- // be added offscreen.
- bool addToRoot;
- };
// A temporay pool that store the created layers and will be added to current state in main
// thread.
std::vector<LayerCreatedState> mCreatedLayers GUARDED_BY(mCreatedLayersLock);
- bool commitCreatedLayers(VsyncId);
+ bool commitCreatedLayers(VsyncId, std::vector<LayerCreatedState>& createdLayers);
void handleLayerCreatedLocked(const LayerCreatedState&, VsyncId) REQUIRES(mStateLock);
mutable std::mutex mMirrorDisplayLock;
@@ -1371,6 +1387,11 @@
return hasDisplay(
[](const auto& display) { return display.isRefreshRateOverlayEnabled(); });
}
+ std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> getLayerSnapshotsForScreenshots(
+ std::optional<ui::LayerStack> layerStack, uint32_t uid);
+ std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> getLayerSnapshotsForScreenshots(
+ uint32_t rootLayerId, uint32_t uid, std::unordered_set<uint32_t> excludeLayerIds,
+ bool childrenOnly, const FloatRect& parentCrop);
const sp<WindowInfosListenerInvoker> mWindowInfosListenerInvoker;
@@ -1383,6 +1404,18 @@
bool mPowerHintSessionEnabled;
+ bool mLayerLifecycleManagerEnabled = false;
+ bool mLegacyFrontEndEnabled = true;
+
+ frontend::LayerLifecycleManager mLayerLifecycleManager;
+ frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}};
+ frontend::LayerSnapshotBuilder mLayerSnapshotBuilder;
+
+ std::vector<uint32_t> mDestroyedHandles;
+ std::vector<std::unique_ptr<frontend::RequestedLayerState>> mNewLayers;
+ // These classes do not store any client state but help with managing transaction callbacks
+ // and stats.
+ std::unordered_map<uint32_t, sp<Layer>> mLegacyLayers;
struct {
bool late = false;
bool early = false;
@@ -1390,6 +1423,7 @@
TransactionHandler mTransactionHandler;
display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos;
+ bool mFrontEndDisplayInfosChanged = false;
};
class SurfaceComposerAIDL : public gui::BnSurfaceComposer {
@@ -1399,10 +1433,11 @@
binder::Status bootFinished() override;
binder::Status createDisplayEventConnection(
VsyncSource vsyncSource, EventRegistration eventRegistration,
+ const sp<IBinder>& layerHandle,
sp<gui::IDisplayEventConnection>* outConnection) override;
binder::Status createConnection(sp<gui::ISurfaceComposerClient>* outClient) override;
binder::Status createDisplay(const std::string& displayName, bool secure,
- sp<IBinder>* outDisplay) override;
+ float requestedRefreshRate, sp<IBinder>* outDisplay) override;
binder::Status destroyDisplay(const sp<IBinder>& display) override;
binder::Status getPhysicalDisplayIds(std::vector<int64_t>* outDisplayIds) override;
binder::Status getPhysicalDisplayToken(int64_t displayId, sp<IBinder>* outDisplay) override;
@@ -1487,6 +1522,7 @@
binder::Status removeHdrLayerInfoListener(
const sp<IBinder>& displayToken,
const sp<gui::IHdrLayerInfoListener>& listener) override;
+
binder::Status notifyPowerBoost(int boostId) override;
binder::Status setGlobalShadowSettings(const gui::Color& ambientColor,
const gui::Color& spotColor, float lightPosY,
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.cpp b/services/surfaceflinger/TransactionCallbackInvoker.cpp
index e5de759..3587a72 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.cpp
+++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp
@@ -92,11 +92,6 @@
return NO_ERROR;
}
-status_t TransactionCallbackInvoker::registerUnpresentedCallbackHandle(
- const sp<CallbackHandle>& handle) {
- return addCallbackHandle(handle, std::vector<JankData>());
-}
-
status_t TransactionCallbackInvoker::findOrCreateTransactionStats(
const sp<IBinder>& listener, const std::vector<CallbackId>& callbackIds,
TransactionStats** outTransactionStats) {
@@ -137,7 +132,6 @@
sp<Fence> currentFence = future.get().value_or(Fence::NO_FENCE);
if (prevFence == nullptr && currentFence->getStatus() != Fence::Status::Invalid) {
prevFence = std::move(currentFence);
- handle->previousReleaseFence = prevFence;
} else if (prevFence != nullptr) {
// If both fences are signaled or both are unsignaled, we need to merge
// them to get an accurate timestamp.
@@ -147,8 +141,7 @@
snprintf(fenceName, 32, "%.28s", handle->name.c_str());
sp<Fence> mergedFence = Fence::merge(fenceName, prevFence, currentFence);
if (mergedFence->isValid()) {
- handle->previousReleaseFence = std::move(mergedFence);
- prevFence = handle->previousReleaseFence;
+ prevFence = std::move(mergedFence);
}
} else if (currentFence->getStatus() == Fence::Status::Unsignaled) {
// If one fence has signaled and the other hasn't, the unsignaled
@@ -158,10 +151,11 @@
// by this point, they will have both signaled and only the timestamp
// will be slightly off; any dependencies after this point will
// already have been met.
- handle->previousReleaseFence = std::move(currentFence);
+ prevFence = std::move(currentFence);
}
}
}
+ handle->previousReleaseFence = prevFence;
handle->previousReleaseFences.clear();
FrameEventHistoryStats eventStats(handle->frameNumber,
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h
index 61ff9bc..3074795 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.h
+++ b/services/surfaceflinger/TransactionCallbackInvoker.h
@@ -66,9 +66,6 @@
status_t addOnCommitCallbackHandles(const std::deque<sp<CallbackHandle>>& handles,
std::deque<sp<CallbackHandle>>& outRemainingHandles);
- // Adds the Transaction CallbackHandle from a layer that does not need to be relatched and
- // presented this frame.
- status_t registerUnpresentedCallbackHandle(const sp<CallbackHandle>& handle);
void addEmptyTransaction(const ListenerCallbacks& listenerCallbacks);
void addPresentFence(sp<Fence>);
diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h
index 5025c49..aeca56e 100644
--- a/services/surfaceflinger/TransactionState.h
+++ b/services/surfaceflinger/TransactionState.h
@@ -27,6 +27,12 @@
namespace android {
+enum TraverseBuffersReturnValues {
+ CONTINUE_TRAVERSAL,
+ STOP_TRAVERSAL,
+ DELETE_AND_CONTINUE_TRAVERSAL,
+};
+
// Extends the client side composer state by resolving buffer.
class ResolvedComposerState : public ComposerState {
public:
@@ -43,7 +49,7 @@
const Vector<DisplayState>& displayStates, uint32_t transactionFlags,
const sp<IBinder>& applyToken, const InputWindowCommands& inputWindowCommands,
int64_t desiredPresentTime, bool isAutoTimestamp,
- std::vector<uint64_t> uncacheBufferIds, int64_t postTime, uint32_t permissions,
+ const client_cache_t& uncacheBuffer, int64_t postTime, uint32_t permissions,
bool hasListenerCallbacks, std::vector<ListenerCallbacks> listenerCallbacks,
int originPid, int originUid, uint64_t transactionId)
: frameTimelineInfo(frameTimelineInfo),
@@ -54,7 +60,7 @@
inputWindowCommands(inputWindowCommands),
desiredPresentTime(desiredPresentTime),
isAutoTimestamp(isAutoTimestamp),
- uncacheBufferIds(std::move(uncacheBufferIds)),
+ buffer(uncacheBuffer),
postTime(postTime),
permissions(permissions),
hasListenerCallbacks(hasListenerCallbacks),
@@ -75,12 +81,18 @@
}
template <typename Visitor>
- void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) const {
- for (const auto& state : states) {
- if (state.state.hasBufferChanges() && state.state.hasValidBuffer() &&
- state.state.surface) {
- if (!visitor(state.state)) return;
+ void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) {
+ for (auto state = states.begin(); state != states.end();) {
+ if (state->state.hasBufferChanges() && state->state.hasValidBuffer() &&
+ state->state.surface) {
+ int result = visitor(state->state);
+ if (result == STOP_TRAVERSAL) return;
+ if (result == DELETE_AND_CONTINUE_TRAVERSAL) {
+ state = states.erase(state);
+ continue;
+ }
}
+ state++;
}
}
@@ -109,7 +121,7 @@
InputWindowCommands inputWindowCommands;
int64_t desiredPresentTime;
bool isAutoTimestamp;
- std::vector<uint64_t> uncacheBufferIds;
+ client_cache_t buffer;
int64_t postTime;
uint32_t permissions;
bool hasListenerCallbacks;
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h
index 6a6e3db..1a951b3 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h
@@ -41,6 +41,7 @@
namespace android::hardware::graphics::composer::hal {
+using aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::android::HWC2::ComposerCallback;
@@ -99,6 +100,7 @@
void onComposerHalVsyncPeriodTimingChanged(HWDisplayId, const VsyncPeriodChangeTimeline&) {}
void onComposerHalSeamlessPossible(HWDisplayId) {}
void onComposerHalVsyncIdle(HWDisplayId) {}
+ void onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) {}
};
} // namespace android::hardware::graphics::composer::hal
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index cdffbb4..7695459 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -234,7 +234,8 @@
std::shared_ptr<RefreshRateSelector> selectorPtr,
sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback)
: Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) {
- mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller)));
+ mVsyncSchedule = std::unique_ptr<VsyncSchedule>(
+ new VsyncSchedule(std::move(tracker), nullptr, std::move(controller)));
const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
registerDisplay(displayId, std::move(selectorPtr));
@@ -244,9 +245,6 @@
return Scheduler::createConnection(std::move(eventThread));
}
- auto &mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; }
- auto &mutableHWVsyncAvailable() { return mHWVsyncAvailable; }
-
auto &mutableLayerHistory() { return mLayerHistory; }
auto refreshRateSelector() { return leaderSelectorPtr(); }
@@ -739,18 +737,17 @@
return mFlinger->mTransactionHandler.mPendingTransactionQueues;
}
- auto setTransactionState(const FrameTimelineInfo& frameTimelineInfo,
- Vector<ComposerState>& states, const Vector<DisplayState>& displays,
- uint32_t flags, const sp<IBinder>& applyToken,
- const InputWindowCommands& inputWindowCommands,
+ auto setTransactionState(const FrameTimelineInfo &frameTimelineInfo,
+ Vector<ComposerState> &states, const Vector<DisplayState> &displays,
+ uint32_t flags, const sp<IBinder> &applyToken,
+ const InputWindowCommands &inputWindowCommands,
int64_t desiredPresentTime, bool isAutoTimestamp,
- const std::vector<client_cache_t>& uncacheBuffers,
- bool hasListenerCallbacks,
- std::vector<ListenerCallbacks>& listenerCallbacks,
+ const client_cache_t &uncacheBuffer, bool hasListenerCallbacks,
+ std::vector<ListenerCallbacks> &listenerCallbacks,
uint64_t transactionId) {
return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken,
inputWindowCommands, desiredPresentTime,
- isAutoTimestamp, uncacheBuffers, hasListenerCallbacks,
+ isAutoTimestamp, uncacheBuffer, hasListenerCallbacks,
listenerCallbacks, transactionId);
}
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
index acfc1d4..c088e7b 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
@@ -148,7 +148,7 @@
layer->fenceHasSignaled();
layer->onPreComposition(mFdp.ConsumeIntegral<int64_t>());
const std::vector<sp<CallbackHandle>> callbacks;
- layer->setTransactionCompletedListeners(callbacks);
+ layer->setTransactionCompletedListeners(callbacks, mFdp.ConsumeBool());
std::shared_ptr<renderengine::ExternalTexture> texture = std::make_shared<
renderengine::mock::FakeExternalTexture>(mFdp.ConsumeIntegral<uint32_t>(),
@@ -166,7 +166,7 @@
{mFdp.ConsumeIntegral<int32_t>(),
mFdp.ConsumeIntegral<int32_t>()} /*reqSize*/,
mFdp.PickValueInArray(kDataspaces), mFdp.ConsumeBool(),
- getFuzzedRect(), mFdp.ConsumeBool());
+ mFdp.ConsumeBool());
layerArea.render([]() {} /*drawLayers*/);
if (!ownsHandle) {
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index 44805db..61fb29a 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -76,7 +76,7 @@
FuzzedDataProvider mFdp;
- std::optional<scheduler::VsyncSchedule> mVsyncSchedule;
+ std::unique_ptr<scheduler::VsyncSchedule> mVsyncSchedule;
};
PhysicalDisplayId SchedulerFuzzer::getPhysicalDisplayId() {
@@ -90,9 +90,9 @@
}
void SchedulerFuzzer::fuzzEventThread() {
- mVsyncSchedule.emplace(scheduler::VsyncSchedule(std::make_unique<mock::VSyncTracker>(),
- std::make_unique<mock::VSyncDispatch>(),
- nullptr));
+ mVsyncSchedule = std::unique_ptr<scheduler::VsyncSchedule>(
+ new scheduler::VsyncSchedule(std::make_unique<mock::VSyncTracker>(),
+ std::make_unique<mock::VSyncDispatch>(), nullptr));
const auto getVsyncPeriod = [](uid_t /* uid */) { return kSyncPeriod.count(); };
std::unique_ptr<android::impl::EventThread> thread = std::make_unique<
android::impl::EventThread>("fuzzer", *mVsyncSchedule, nullptr, nullptr, getVsyncPeriod,
diff --git a/services/surfaceflinger/main_surfaceflinger.cpp b/services/surfaceflinger/main_surfaceflinger.cpp
index fedd71e..0495678 100644
--- a/services/surfaceflinger/main_surfaceflinger.cpp
+++ b/services/surfaceflinger/main_surfaceflinger.cpp
@@ -139,11 +139,6 @@
set_sched_policy(0, SP_FOREGROUND);
- // Put most SurfaceFlinger threads in the system-background cpuset
- // Keeps us from unnecessarily using big cores
- // Do this after the binder thread pool init
- if (cpusets_enabled()) set_cpuset_policy(0, SP_SYSTEM);
-
// initialize before clients can connect
flinger->init();
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index de47330..6d12aa7 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -57,6 +57,7 @@
"SetFrameRateOverride_test.cpp",
"SetGeometry_test.cpp",
"Stress_test.cpp",
+ "TextureFiltering_test.cpp",
"VirtualDisplay_test.cpp",
"WindowInfosListener_test.cpp",
],
diff --git a/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp b/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp
index 16076ea..c23fb9b 100644
--- a/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp
+++ b/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp
@@ -85,7 +85,8 @@
sp<Fence> fence, CallbackHelper& callback, const ReleaseCallbackId& id,
ReleaseBufferCallbackHelper& releaseCallback) {
Transaction t;
- t.setBuffer(layer, buffer, fence, id.framenumber, releaseCallback.getCallback());
+ t.setBuffer(layer, buffer, fence, id.framenumber, 0 /* producerId */,
+ releaseCallback.getCallback());
t.addTransactionCompletedCallback(callback.function, callback.getContext());
t.apply();
}
@@ -301,7 +302,7 @@
Transaction t;
t.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber,
- releaseCallback->getCallback());
+ 0 /* producerId */, releaseCallback->getCallback());
t.addTransactionCompletedCallback(transactionCallback.function,
transactionCallback.getContext());
t.setDesiredPresentTime(time);
@@ -317,7 +318,7 @@
sp<GraphicBuffer> secondBuffer = getBuffer();
ReleaseCallbackId secondBufferCallbackId(secondBuffer->getId(), generateFrameNumber());
t.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber,
- releaseCallback->getCallback());
+ 0 /* producerId */, releaseCallback->getCallback());
t.addTransactionCompletedCallback(transactionCallback.function,
transactionCallback.getContext());
t.setDesiredPresentTime(time);
@@ -362,7 +363,7 @@
Transaction transaction1;
transaction1.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber,
- releaseCallback->getCallback());
+ 0 /* producerId */, releaseCallback->getCallback());
transaction1.addTransactionCompletedCallback(callback1.function, callback1.getContext());
// Set a different TransactionCompletedListener to mimic a second process
@@ -397,14 +398,14 @@
// Create transaction with a buffer.
Transaction transaction;
transaction.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber,
- releaseCallback->getCallback());
+ 0 /* producerId */, releaseCallback->getCallback());
sp<GraphicBuffer> secondBuffer = getBuffer();
ReleaseCallbackId secondBufferCallbackId(secondBuffer->getId(), generateFrameNumber());
// Call setBuffer on the same transaction with a different buffer.
transaction.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber,
- releaseCallback->getCallback());
+ 0 /* producerId */, releaseCallback->getCallback());
ASSERT_NO_FATAL_FAILURE(waitForReleaseBufferCallback(*releaseCallback, firstBufferCallbackId));
}
@@ -419,7 +420,7 @@
// Create transaction with a buffer.
Transaction transaction1;
transaction1.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber,
- releaseCallback->getCallback());
+ 0 /* producerId */, releaseCallback->getCallback());
sp<GraphicBuffer> secondBuffer = getBuffer();
ReleaseCallbackId secondBufferCallbackId(secondBuffer->getId(), generateFrameNumber());
@@ -427,7 +428,7 @@
// Create a second transaction with a new buffer for the same layer.
Transaction transaction2;
transaction2.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber,
- releaseCallback->getCallback());
+ 0 /* producerId */, releaseCallback->getCallback());
// merge transaction1 into transaction2 so ensure we get a proper buffer release callback.
transaction1.merge(std::move(transaction2));
@@ -450,7 +451,7 @@
Transaction transaction1;
transaction1.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber,
- releaseCallback->getCallback());
+ 0 /* producerId */, releaseCallback->getCallback());
// Sent a second buffer to allow the first buffer to get released.
sp<GraphicBuffer> secondBuffer = getBuffer();
@@ -458,7 +459,7 @@
Transaction transaction2;
transaction2.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber,
- releaseCallback->getCallback());
+ 0 /* producerId */, releaseCallback->getCallback());
// Set a different TransactionCompletedListener to mimic a second process
TransactionCompletedListener::setInstance(secondCompletedListener);
@@ -479,10 +480,11 @@
// Create transaction with a buffer.
Transaction transaction;
transaction.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber,
- releaseCallback->getCallback());
+ 0 /* producerId */, releaseCallback->getCallback());
// Call setBuffer on the same transaction with a null buffer.
- transaction.setBuffer(layer, nullptr, std::nullopt, 0, releaseCallback->getCallback());
+ transaction.setBuffer(layer, nullptr, std::nullopt, 0, 0 /* producerId */,
+ releaseCallback->getCallback());
ASSERT_NO_FATAL_FAILURE(waitForReleaseBufferCallback(*releaseCallback, firstBufferCallbackId));
}
diff --git a/services/surfaceflinger/tests/TextureFiltering_test.cpp b/services/surfaceflinger/tests/TextureFiltering_test.cpp
new file mode 100644
index 0000000..e9b1fbb
--- /dev/null
+++ b/services/surfaceflinger/tests/TextureFiltering_test.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/gui/ISurfaceComposerClient.h>
+#include <gtest/gtest.h>
+#include <gui/DisplayCaptureArgs.h>
+#include <ui/GraphicTypes.h>
+#include <ui/Rect.h>
+
+#include "LayerTransactionTest.h"
+
+namespace android {
+
+bool operator==(const Color& left, const Color& right) {
+ return left.a == right.a && left.r == right.r && left.g == right.g && left.b == right.b;
+}
+
+class TextureFilteringTest : public LayerTransactionTest {
+protected:
+ virtual void SetUp() {
+ LayerTransactionTest::SetUp();
+
+ mParent = createLayer("test-parent", 100, 100,
+ gui::ISurfaceComposerClient::eFXSurfaceContainer);
+ mLayer = createLayer("test-child", 100, 100,
+ gui::ISurfaceComposerClient::eFXSurfaceBufferState, mParent.get());
+ sp<GraphicBuffer> buffer =
+ sp<GraphicBuffer>::make(static_cast<uint32_t>(100), static_cast<uint32_t>(100),
+ PIXEL_FORMAT_RGBA_8888, 1u,
+ BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
+ BufferUsage::COMPOSER_OVERLAY |
+ BufferUsage::GPU_TEXTURE,
+ "test");
+ TransactionUtils::fillGraphicBufferColor(buffer, Rect{0, 0, 50, 100}, Color::RED);
+ TransactionUtils::fillGraphicBufferColor(buffer, Rect{50, 0, 100, 100}, Color::BLUE);
+ Transaction()
+ .setBuffer(mLayer, buffer)
+ .setDataspace(mLayer, ui::Dataspace::V0_SRGB)
+ .setLayer(mLayer, INT32_MAX)
+ .apply();
+ }
+
+ virtual void TearDown() { LayerTransactionTest::TearDown(); }
+
+ void expectFiltered(Rect redRect, Rect blueRect) {
+ // Check that at least some of the pixels in the red rectangle aren't solid red
+ int redPixels = 0;
+ for (int x = redRect.left; x < redRect.right; x++) {
+ for (int y = redRect.top; y < redRect.bottom; y++) {
+ redPixels += mCapture->getPixelColor(static_cast<uint32_t>(x),
+ static_cast<uint32_t>(y)) == Color::RED;
+ }
+ }
+ ASSERT_LT(redPixels, redRect.getWidth() * redRect.getHeight());
+
+ // Check that at least some of the pixels in the blue rectangle aren't solid blue
+ int bluePixels = 0;
+ for (int x = blueRect.left; x < blueRect.right; x++) {
+ for (int y = blueRect.top; y < blueRect.bottom; y++) {
+ bluePixels += mCapture->getPixelColor(static_cast<uint32_t>(x),
+ static_cast<uint32_t>(y)) == Color::BLUE;
+ }
+ }
+ ASSERT_LT(bluePixels, blueRect.getWidth() * blueRect.getHeight());
+ }
+
+ sp<SurfaceControl> mParent;
+ sp<SurfaceControl> mLayer;
+ std::unique_ptr<ScreenCapture> mCapture;
+};
+
+TEST_F(TextureFilteringTest, NoFiltering) {
+ gui::DisplayCaptureArgs captureArgs;
+ captureArgs.displayToken = mDisplay;
+ captureArgs.width = 100;
+ captureArgs.height = 100;
+ captureArgs.sourceCrop = Rect{100, 100};
+ ScreenCapture::captureDisplay(&mCapture, captureArgs);
+
+ mCapture->expectColor(Rect{0, 0, 50, 100}, Color::RED);
+ mCapture->expectColor(Rect{50, 0, 100, 100}, Color::BLUE);
+}
+
+TEST_F(TextureFilteringTest, BufferCropNoFiltering) {
+ Transaction().setBufferCrop(mLayer, Rect{0, 0, 100, 100}).apply();
+
+ gui::DisplayCaptureArgs captureArgs;
+ captureArgs.displayToken = mDisplay;
+ captureArgs.width = 100;
+ captureArgs.height = 100;
+ captureArgs.sourceCrop = Rect{0, 0, 100, 100};
+ ScreenCapture::captureDisplay(&mCapture, captureArgs);
+
+ mCapture->expectColor(Rect{0, 0, 50, 100}, Color::RED);
+ mCapture->expectColor(Rect{50, 0, 100, 100}, Color::BLUE);
+}
+
+// Expect filtering because the buffer is stretched to the layer's bounds.
+TEST_F(TextureFilteringTest, BufferCropIsFiltered) {
+ Transaction().setBufferCrop(mLayer, Rect{25, 25, 75, 75}).apply();
+
+ gui::DisplayCaptureArgs captureArgs;
+ captureArgs.displayToken = mDisplay;
+ captureArgs.width = 100;
+ captureArgs.height = 100;
+ captureArgs.sourceCrop = Rect{0, 0, 100, 100};
+ ScreenCapture::captureDisplay(&mCapture, captureArgs);
+
+ expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100});
+}
+
+// Expect filtering because the output source crop is stretched to the output buffer's size.
+TEST_F(TextureFilteringTest, OutputSourceCropIsFiltered) {
+ gui::DisplayCaptureArgs captureArgs;
+ captureArgs.displayToken = mDisplay;
+ captureArgs.width = 100;
+ captureArgs.height = 100;
+ captureArgs.sourceCrop = Rect{25, 25, 75, 75};
+ ScreenCapture::captureDisplay(&mCapture, captureArgs);
+
+ expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100});
+}
+
+// Expect filtering because the layer crop and output source crop are stretched to the output
+// buffer's size.
+TEST_F(TextureFilteringTest, LayerCropOutputSourceCropIsFiltered) {
+ Transaction().setCrop(mLayer, Rect{25, 25, 75, 75}).apply();
+
+ gui::DisplayCaptureArgs captureArgs;
+ captureArgs.displayToken = mDisplay;
+ captureArgs.width = 100;
+ captureArgs.height = 100;
+ captureArgs.sourceCrop = Rect{25, 25, 75, 75};
+ ScreenCapture::captureDisplay(&mCapture, captureArgs);
+
+ expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100});
+}
+
+// Expect filtering because the layer is scaled up.
+TEST_F(TextureFilteringTest, LayerCaptureWithScalingIsFiltered) {
+ LayerCaptureArgs captureArgs;
+ captureArgs.layerHandle = mLayer->getHandle();
+ captureArgs.frameScaleX = 2;
+ captureArgs.frameScaleY = 2;
+ ScreenCapture::captureLayers(&mCapture, captureArgs);
+
+ expectFiltered({0, 0, 100, 200}, {100, 0, 200, 200});
+}
+
+// Expect no filtering because the output buffer's size matches the source crop.
+TEST_F(TextureFilteringTest, LayerCaptureOutputSourceCropNoFiltering) {
+ LayerCaptureArgs captureArgs;
+ captureArgs.layerHandle = mLayer->getHandle();
+ captureArgs.sourceCrop = Rect{25, 25, 75, 75};
+ ScreenCapture::captureLayers(&mCapture, captureArgs);
+
+ mCapture->expectColor(Rect{0, 0, 25, 50}, Color::RED);
+ mCapture->expectColor(Rect{25, 0, 50, 50}, Color::BLUE);
+}
+
+// Expect no filtering because the output buffer's size matches the source crop (with a cropped
+// layer).
+TEST_F(TextureFilteringTest, LayerCaptureWithCropNoFiltering) {
+ Transaction().setCrop(mLayer, Rect{10, 10, 90, 90}).apply();
+
+ LayerCaptureArgs captureArgs;
+ captureArgs.layerHandle = mLayer->getHandle();
+ captureArgs.sourceCrop = Rect{25, 25, 75, 75};
+ ScreenCapture::captureLayers(&mCapture, captureArgs);
+
+ mCapture->expectColor(Rect{0, 0, 25, 50}, Color::RED);
+ mCapture->expectColor(Rect{25, 0, 50, 50}, Color::BLUE);
+}
+
+// Expect no filtering because the output source crop and output buffer are the same size.
+TEST_F(TextureFilteringTest, OutputSourceCropDisplayFrameMatchNoFiltering) {
+ // Transaction().setCrop(mLayer, Rect{25, 25, 75, 75}).apply();
+
+ gui::DisplayCaptureArgs captureArgs;
+ captureArgs.displayToken = mDisplay;
+ captureArgs.width = 50;
+ captureArgs.height = 50;
+ captureArgs.sourceCrop = Rect{25, 25, 75, 75};
+ ScreenCapture::captureDisplay(&mCapture, captureArgs);
+
+ mCapture->expectColor(Rect{0, 0, 25, 50}, Color::RED);
+ mCapture->expectColor(Rect{25, 0, 50, 50}, Color::BLUE);
+}
+
+// Expect no filtering because the layer crop shouldn't scale the layer.
+TEST_F(TextureFilteringTest, LayerCropDisplayFrameMatchNoFiltering) {
+ Transaction().setCrop(mLayer, Rect{25, 25, 75, 75}).apply();
+
+ gui::DisplayCaptureArgs captureArgs;
+ captureArgs.displayToken = mDisplay;
+ ScreenCapture::captureDisplay(&mCapture, captureArgs);
+
+ mCapture->expectColor(Rect{25, 25, 50, 75}, Color::RED);
+ mCapture->expectColor(Rect{50, 25, 75, 75}, Color::BLUE);
+}
+
+// Expect no filtering because the parent layer crop shouldn't scale the layer.
+TEST_F(TextureFilteringTest, ParentCropNoFiltering) {
+ Transaction().setCrop(mParent, Rect{25, 25, 75, 75}).apply();
+
+ gui::DisplayCaptureArgs captureArgs;
+ captureArgs.displayToken = mDisplay;
+ ScreenCapture::captureDisplay(&mCapture, captureArgs);
+
+ mCapture->expectColor(Rect{25, 25, 50, 75}, Color::RED);
+ mCapture->expectColor(Rect{50, 25, 75, 75}, Color::BLUE);
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index ba77600..0416e93 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -238,6 +238,8 @@
CaptureArgs::UNSET_UID, visitor);
};
+ auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
+
const uint32_t usage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN |
GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE;
mCaptureScreenBuffer =
@@ -246,7 +248,7 @@
HAL_PIXEL_FORMAT_RGBA_8888, 1,
usage);
- auto future = mFlinger.renderScreenImpl(std::move(renderArea), traverseLayers,
+ auto future = mFlinger.renderScreenImpl(std::move(renderArea), getLayerSnapshots,
mCaptureScreenBuffer, forSystem, regionSampling);
ASSERT_TRUE(future.valid());
const auto fenceResult = future.get();
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index b3aba37..f6bcadc 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -125,7 +125,7 @@
ConnectionEventRecorder mConnectionEventCallRecorder{0};
ConnectionEventRecorder mThrottledConnectionEventCallRecorder{0};
- std::optional<scheduler::VsyncSchedule> mVsyncSchedule;
+ std::unique_ptr<scheduler::VsyncSchedule> mVsyncSchedule;
std::unique_ptr<impl::EventThread> mThread;
sp<MockEventThreadConnection> mConnection;
sp<MockEventThreadConnection> mThrottledConnection;
@@ -140,9 +140,9 @@
::testing::UnitTest::GetInstance()->current_test_info();
ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
- mVsyncSchedule.emplace(scheduler::VsyncSchedule(std::make_unique<mock::VSyncTracker>(),
- std::make_unique<mock::VSyncDispatch>(),
- nullptr));
+ mVsyncSchedule = std::unique_ptr<scheduler::VsyncSchedule>(
+ new scheduler::VsyncSchedule(std::make_unique<mock::VSyncTracker>(),
+ std::make_unique<mock::VSyncDispatch>(), nullptr));
mock::VSyncDispatch& mockDispatch =
*static_cast<mock::VSyncDispatch*>(&mVsyncSchedule->getDispatch());
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index f47ac6d..abd7789 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -44,6 +44,17 @@
namespace android::frametimeline {
+static const std::string sLayerNameOne = "layer1";
+static const std::string sLayerNameTwo = "layer2";
+
+constexpr const uid_t sUidOne = 0;
+constexpr pid_t sPidOne = 10;
+constexpr pid_t sPidTwo = 20;
+constexpr int32_t sInputEventId = 5;
+constexpr int32_t sLayerIdOne = 1;
+constexpr int32_t sLayerIdTwo = 2;
+constexpr GameMode sGameMode = GameMode::Unsupported;
+
class FrameTimelineTest : public testing::Test {
public:
FrameTimelineTest() {
@@ -106,6 +117,14 @@
return packets;
}
+ void addEmptySurfaceFrame() {
+ auto surfaceFrame =
+ mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ false, sGameMode);
+ mFrameTimeline->addSurfaceFrame(std::move(surfaceFrame));
+ }
+
void addEmptyDisplayFrame() {
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
// Trigger a flushPresentFence by calling setSfPresent for the next frame
@@ -168,17 +187,6 @@
kStartThreshold};
};
-static const std::string sLayerNameOne = "layer1";
-static const std::string sLayerNameTwo = "layer2";
-
-constexpr const uid_t sUidOne = 0;
-constexpr pid_t sPidOne = 10;
-constexpr pid_t sPidTwo = 20;
-constexpr int32_t sInputEventId = 5;
-constexpr int32_t sLayerIdOne = 1;
-constexpr int32_t sLayerIdTwo = 2;
-constexpr GameMode sGameMode = GameMode::Unsupported;
-
TEST_F(FrameTimelineTest, tokenManagerRemovesStalePredictions) {
int64_t token1 = mTokenManager->generateTokenForPredictions({0, 0, 0});
EXPECT_EQ(getPredictions().size(), 1u);
@@ -1054,6 +1062,9 @@
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
tracingSession->StartBlocking();
+
+ // Add an empty surface frame so that display frame would get traced.
+ addEmptySurfaceFrame();
int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({10, 30, 30});
// Set up the display frame
@@ -1135,6 +1146,9 @@
// Flush the token so that it would expire
flushTokens();
+ // Add an empty surface frame so that display frame would get traced.
+ addEmptySurfaceFrame();
+
// Set up the display frame
mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, Fps::fromPeriodNsecs(11));
mFrameTimeline->setSfPresent(26, presentFence1);
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index afbc57a..da00377 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -52,6 +52,7 @@
using Hwc2::Config;
+using ::aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
using ::testing::_;
using ::testing::DoAll;
using ::testing::ElementsAreArray;
@@ -118,6 +119,34 @@
}
}
+TEST_F(HWComposerTest, onVsync) {
+ constexpr hal::HWDisplayId kHwcDisplayId = 1;
+ expectHotplugConnect(kHwcDisplayId);
+
+ const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+ ASSERT_TRUE(info);
+
+ const auto physicalDisplayId = info->id;
+
+ // Deliberately chosen not to match DisplayData.lastPresentTimestamp's
+ // initial value.
+ constexpr nsecs_t kTimestamp = 1;
+ auto displayIdOpt = mHwc.onVsync(kHwcDisplayId, kTimestamp);
+ ASSERT_TRUE(displayIdOpt);
+ EXPECT_EQ(physicalDisplayId, displayIdOpt);
+
+ // Attempt to send the same time stamp again.
+ displayIdOpt = mHwc.onVsync(kHwcDisplayId, kTimestamp);
+ EXPECT_FALSE(displayIdOpt);
+}
+
+TEST_F(HWComposerTest, onVsyncInvalid) {
+ constexpr hal::HWDisplayId kInvalidHwcDisplayId = 2;
+ constexpr nsecs_t kTimestamp = 1;
+ const auto displayIdOpt = mHwc.onVsync(kInvalidHwcDisplayId, kTimestamp);
+ EXPECT_FALSE(displayIdOpt);
+}
+
struct MockHWC2ComposerCallback final : StrictMock<HWC2::ComposerCallback> {
MOCK_METHOD2(onComposerHalHotplug, void(hal::HWDisplayId, hal::Connection));
MOCK_METHOD1(onComposerHalRefresh, void(hal::HWDisplayId));
@@ -127,6 +156,7 @@
void(hal::HWDisplayId, const hal::VsyncPeriodChangeTimeline&));
MOCK_METHOD1(onComposerHalSeamlessPossible, void(hal::HWDisplayId));
MOCK_METHOD1(onComposerHalVsyncIdle, void(hal::HWDisplayId));
+ MOCK_METHOD(void, onRefreshRateChangedDebug, (const RefreshRateChangedDebugData&), (override));
};
struct HWComposerSetCallbackTest : HWComposerTest {
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
index 783df28..763426a 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
@@ -641,4 +641,69 @@
EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
}
+TEST_F(LayerHierarchyTest, canMirrorDisplay) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
+ createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+ setLayerStack(3, 1);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expected = {3, 1, 11, 111, 12, 121, 122, 1221, 13, 2,
+ 1, 11, 111, 12, 121, 122, 1221, 13, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
+ expected = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
+}
+
+TEST_F(LayerHierarchyTest, mirrorNonExistingDisplay) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
+ createDisplayMirrorLayer(3, ui::LayerStack::fromValue(5));
+ setLayerStack(3, 1);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expected = {3, 1, 11, 111, 12, 121, 122, 1221, 13, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
+ expected = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
+}
+
+TEST_F(LayerHierarchyTest, newRootLayerIsMirrored) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
+ createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+ setLayerStack(3, 1);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ createRootLayer(4);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expected = {3, 1, 11, 111, 12, 121, 122, 1221, 13, 2, 4,
+ 1, 11, 111, 12, 121, 122, 1221, 13, 2, 4};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
+ expected = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
+}
+
+TEST_F(LayerHierarchyTest, removedRootLayerIsNoLongerMirrored) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
+ createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+ setLayerStack(3, 1);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ reparentLayer(1, UNASSIGNED_LAYER_ID);
+ destroyLayerHandle(1);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expected = {3, 2, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
+ expected = {11, 111, 12, 121, 122, 1221, 13};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
+}
+
} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index 1a82232..852cb91 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -60,6 +60,14 @@
return args;
}
+ LayerCreationArgs createDisplayMirrorArgs(uint32_t id, ui::LayerStack layerStack) {
+ LayerCreationArgs args(nullptr, nullptr, "testlayer", 0, {}, std::make_optional(id));
+ args.addToRoot = true;
+ args.parentHandle.clear();
+ args.layerStackToMirror = layerStack;
+ return args;
+ }
+
std::vector<uint32_t> getTraversalPath(const LayerHierarchy& hierarchy) const {
std::vector<uint32_t> layerIds;
hierarchy.traverse([&layerIds = layerIds](const LayerHierarchy& hierarchy,
@@ -90,6 +98,15 @@
mLifecycleManager.addLayers(std::move(layers));
}
+ void createDisplayMirrorLayer(uint32_t id, ui::LayerStack layerStack) {
+ sp<LayerHandle> handle = sp<LayerHandle>::make(id);
+ mHandles[id] = handle;
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(std::make_unique<RequestedLayerState>(
+ createDisplayMirrorArgs(/*id=*/id, layerStack)));
+ mLifecycleManager.addLayers(std::move(layers));
+ }
+
virtual void createLayer(uint32_t id, uint32_t parentId) {
sp<LayerHandle> handle = sp<LayerHandle>::make(id);
mHandles[id] = handle;
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index e124342..aa6a14e 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -306,4 +306,27 @@
EXPECT_EQ(getSnapshot(1)->frameRate.type, scheduler::LayerInfo::FrameRateCompatibility::NoVote);
}
+// Display Mirroring Tests
+// tree with 3 levels of children
+// ROOT (DISPLAY 0)
+// ├── 1
+// │ ├── 11
+// │ │ └── 111
+// │ ├── 12 (has skip screenshot flag)
+// │ │ ├── 121
+// │ │ └── 122
+// │ │ └── 1221
+// │ └── 13
+// └── 2
+// ROOT (DISPLAY 1)
+// └── 3 (mirrors display 0)
+TEST_F(LayerSnapshotTest, displayMirrorRespects) {
+ setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
+ createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+ setLayerStack(3, 1);
+
+ std::vector<uint32_t> expected = {3, 1, 11, 111, 13, 2, 1, 11, 111, 12, 121, 122, 1221, 13, 2};
+ UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+}
+
} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index 06f45f9..f4d052d 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -1165,7 +1165,7 @@
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}};
+ return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}};
}
}();
ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
@@ -1197,7 +1197,7 @@
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}};
+ return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}};
}
}();
ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
@@ -2973,5 +2973,34 @@
EXPECT_EQ(kMode1, selector.getMinRefreshRateByPolicy());
}
+// TODO(b/266481656): Once this bug is fixed, we can remove this test
+TEST_P(RefreshRateSelectorTest, noLowerFrameRateOnMinVote) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "Test layer";
+ layers[0].vote = LayerVoteType::Min;
+ EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, frameRateIsCappedByPolicy) {
+ if (GetParam() != Config::FrameRateOverride::Enabled) {
+ return;
+ }
+
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ constexpr FpsRanges kCappedAt30 = {{60_Hz, 90_Hz}, {30_Hz, 30_Hz}};
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {DisplayModeId(kModeId60), kCappedAt30, kCappedAt30}));
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "Test layer";
+ layers[0].vote = LayerVoteType::Min;
+ EXPECT_FRAME_RATE_MODE(kMode60, 30_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
} // namespace
} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 3ee53c9..4b15385 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -301,9 +301,6 @@
choice = modeChoices.get(kDisplayId1);
ASSERT_TRUE(choice);
EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, globalSignals));
-
- mScheduler->unregisterDisplay(kDisplayId1);
- EXPECT_FALSE(mScheduler->hasRefreshRateSelectors());
}
TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) {
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
index ab732ed..88ddb0f 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
@@ -262,8 +262,8 @@
return display;
}
- static void setInitialPrimaryHWVsyncEnabled(DisplayTransactionTest* test, bool enabled) {
- test->mFlinger.scheduler()->mutablePrimaryHWVsyncEnabled() = enabled;
+ static void setInitialHwVsyncEnabled(DisplayTransactionTest* test, bool enabled) {
+ test->mFlinger.scheduler()->setInitialHwVsyncEnabled(enabled);
}
static void setupRepaintEverythingCallExpectations(DisplayTransactionTest* test) {
@@ -329,9 +329,9 @@
Case::Doze::setupComposerCallExpectations(this);
auto display =
Case::injectDisplayWithInitialPowerMode(this, Case::Transition::INITIAL_POWER_MODE);
- Case::setInitialPrimaryHWVsyncEnabled(this,
- PowerModeInitialVSyncEnabled<
- Case::Transition::INITIAL_POWER_MODE>::value);
+ Case::setInitialHwVsyncEnabled(this,
+ PowerModeInitialVSyncEnabled<
+ Case::Transition::INITIAL_POWER_MODE>::value);
// --------------------------------------------------------------------
// Call Expectations
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 0cbfa63..bd3f3ca 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -16,11 +16,12 @@
#pragma once
-#include <Scheduler/Scheduler.h>
#include <ftl/fake_guard.h>
#include <gmock/gmock.h>
#include <gui/ISurfaceComposer.h>
+#include <scheduler/interface/ICompositor.h>
+
#include "Scheduler/EventThread.h"
#include "Scheduler/LayerHistory.h"
#include "Scheduler/Scheduler.h"
@@ -43,9 +44,9 @@
std::unique_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr,
sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback)
: Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) {
- mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker),
- std::make_unique<mock::VSyncDispatch>(),
- std::move(controller)));
+ mVsyncSchedule = std::unique_ptr<VsyncSchedule>(
+ new VsyncSchedule(std::move(tracker), std::make_unique<mock::VSyncDispatch>(),
+ std::move(controller)));
const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
registerDisplay(displayId, std::move(selectorPtr));
@@ -65,21 +66,12 @@
return Scheduler::createConnection(std::move(eventThread));
}
- /* ------------------------------------------------------------------------
- * Read-write access to private data to set up preconditions and assert
- * post-conditions.
- */
- auto& mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; }
- auto& mutableHWVsyncAvailable() { return mHWVsyncAvailable; }
-
auto refreshRateSelector() { return leaderSelectorPtr(); }
const auto& refreshRateSelectors() const NO_THREAD_SAFETY_ANALYSIS {
return mRefreshRateSelectors;
}
- bool hasRefreshRateSelectors() const { return !refreshRateSelectors().empty(); }
-
void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
ftl::FakeGuard guard(kMainThreadContext);
Scheduler::registerDisplay(displayId, std::move(selectorPtr));
@@ -158,6 +150,12 @@
Scheduler::onNonPrimaryDisplayModeChanged(handle, mode);
}
+ void setInitialHwVsyncEnabled(bool enabled) {
+ std::lock_guard<std::mutex> lock(mVsyncSchedule->mHwVsyncLock);
+ mVsyncSchedule->mHwVsyncState = enabled ? VsyncSchedule::HwVsyncState::Enabled
+ : VsyncSchedule::HwVsyncState::Disabled;
+ }
+
private:
// ICompositor overrides:
void configure() override {}
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 51d2012..e03f6cc 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -407,7 +407,7 @@
}
auto renderScreenImpl(std::shared_ptr<const RenderArea> renderArea,
- SurfaceFlinger::TraverseLayersFunction traverseLayers,
+ SurfaceFlinger::GetLayerSnapshotsFunction traverseLayers,
const std::shared_ptr<renderengine::ExternalTexture>& buffer,
bool forSystem, bool regionSampling) {
ScreenCaptureResults captureResults;
@@ -440,13 +440,12 @@
uint32_t flags, const sp<IBinder>& applyToken,
const InputWindowCommands& inputWindowCommands,
int64_t desiredPresentTime, bool isAutoTimestamp,
- const std::vector<client_cache_t>& uncacheBuffers,
- bool hasListenerCallbacks,
+ const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
std::vector<ListenerCallbacks>& listenerCallbacks,
uint64_t transactionId) {
return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken,
inputWindowCommands, desiredPresentTime,
- isAutoTimestamp, uncacheBuffers, hasListenerCallbacks,
+ isAutoTimestamp, uncacheBuffer, hasListenerCallbacks,
listenerCallbacks, transactionId);
}
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index 859f702..981e9d2 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -102,7 +102,7 @@
int64_t desiredPresentTime = 0;
bool isAutoTimestamp = true;
FrameTimelineInfo frameTimelineInfo;
- std::vector<client_cache_t> uncacheBuffers;
+ client_cache_t uncacheBuffer;
uint64_t id = static_cast<uint64_t>(-1);
static_assert(0xffffffffffffffff == static_cast<uint64_t>(-1));
};
@@ -138,7 +138,7 @@
transaction.displays, transaction.flags,
transaction.applyToken, transaction.inputWindowCommands,
transaction.desiredPresentTime, transaction.isAutoTimestamp,
- transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
+ transaction.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
transaction.id);
// If transaction is synchronous, SF applyTransactionState should time out (5s) wating for
@@ -165,7 +165,7 @@
transaction.displays, transaction.flags,
transaction.applyToken, transaction.inputWindowCommands,
transaction.desiredPresentTime, transaction.isAutoTimestamp,
- transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
+ transaction.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
transaction.id);
nsecs_t returnedTime = systemTime();
@@ -196,7 +196,7 @@
transactionA.displays, transactionA.flags,
transactionA.applyToken, transactionA.inputWindowCommands,
transactionA.desiredPresentTime, transactionA.isAutoTimestamp,
- transactionA.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
+ transactionA.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
transactionA.id);
// This thread should not have been blocked by the above transaction
@@ -211,7 +211,7 @@
transactionB.displays, transactionB.flags,
transactionB.applyToken, transactionB.inputWindowCommands,
transactionB.desiredPresentTime, transactionB.isAutoTimestamp,
- transactionB.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
+ transactionB.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
transactionB.id);
// this thread should have been blocked by the above transaction
@@ -248,7 +248,7 @@
mFlinger.setTransactionState(transactionA.frameTimelineInfo, transactionA.states,
transactionA.displays, transactionA.flags, transactionA.applyToken,
transactionA.inputWindowCommands, transactionA.desiredPresentTime,
- transactionA.isAutoTimestamp, transactionA.uncacheBuffers,
+ transactionA.isAutoTimestamp, transactionA.uncacheBuffer,
mHasListenerCallbacks, mCallbacks, transactionA.id);
auto& transactionQueue = mFlinger.getTransactionQueue();
@@ -268,7 +268,7 @@
mFlinger.setTransactionState(transactionA.frameTimelineInfo, transactionA.states,
transactionA.displays, transactionA.flags, transactionA.applyToken,
transactionA.inputWindowCommands, transactionA.desiredPresentTime,
- transactionA.isAutoTimestamp, transactionA.uncacheBuffers,
+ transactionA.isAutoTimestamp, transactionA.uncacheBuffer,
mHasListenerCallbacks, mCallbacks, transactionA.id);
auto& transactionQueue = mFlinger.getTransactionQueue();
@@ -282,7 +282,7 @@
mFlinger.setTransactionState(empty.frameTimelineInfo, empty.states, empty.displays, empty.flags,
empty.applyToken, empty.inputWindowCommands,
empty.desiredPresentTime, empty.isAutoTimestamp,
- empty.uncacheBuffers, mHasListenerCallbacks, mCallbacks, empty.id);
+ empty.uncacheBuffer, mHasListenerCallbacks, mCallbacks, empty.id);
// flush transaction queue should flush as desiredPresentTime has
// passed
@@ -379,7 +379,8 @@
transaction.applyToken,
transaction.inputWindowCommands,
transaction.desiredPresentTime,
- transaction.isAutoTimestamp, {}, systemTime(), 0,
+ transaction.isAutoTimestamp,
+ transaction.uncacheBuffer, systemTime(), 0,
mHasListenerCallbacks, mCallbacks, getpid(),
static_cast<int>(getuid()), transaction.id);
mFlinger.setTransactionStateInternal(transactionState);
diff --git a/vulkan/vkjson/Android.bp b/vulkan/vkjson/Android.bp
index b6d3a0b..b544245 100644
--- a/vulkan/vkjson/Android.bp
+++ b/vulkan/vkjson/Android.bp
@@ -25,10 +25,8 @@
".",
],
shared_libs: [
- "libvulkan",
- ],
- whole_static_libs: [
"libjsoncpp",
+ "libvulkan",
],
export_shared_lib_headers: [
"libvulkan",