diff options
382 files changed, 13658 insertions, 5582 deletions
diff --git a/TEST_MAPPING b/TEST_MAPPING index f54f13216f..cd8f3cdcc2 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/atrace/atrace.rc b/cmds/atrace/atrace.rc index 2e0c95a5fe..95f5c03a37 100644 --- a/cmds/atrace/atrace.rc +++ b/cmds/atrace/atrace.rc @@ -297,12 +297,23 @@ on late-init write /sys/kernel/debug/tracing/synthetic_events "rss_stat_throttled unsigned int mm_id; unsigned int curr; int member; long size" # allow creating event triggers - chmod 0666 /sys/kernel/debug/tracing/events/kmem/rss_stat/trigger chmod 0666 /sys/kernel/tracing/events/kmem/rss_stat/trigger + chmod 0666 /sys/kernel/debug/tracing/events/kmem/rss_stat/trigger + + # allow enabling rss_stat_throttled + chmod 0666 /sys/kernel/tracing/events/synthetic/rss_stat_throttled/enable + chmod 0666 /sys/kernel/debug/tracing/events/synthetic/rss_stat_throttled/enable on late-init && property:ro.boot.fastboot.boottrace=enabled setprop debug.atrace.tags.enableflags 802922 setprop persist.traced.enable 0 + write /sys/kernel/tracing/events/binder/binder_transaction/enable 1 + write /sys/kernel/tracing/events/binder/binder_transaction_received/enable 1 + write /sys/kernel/tracing/events/binder/binder_transaction_alloc_buf/enable 1 + write /sys/kernel/tracing/events/binder/binder_set_priority/enable 1 + write /sys/kernel/tracing/events/binder/binder_lock/enable 1 + write /sys/kernel/tracing/events/binder/binder_locked/enable 1 + write /sys/kernel/tracing/events/binder/binder_unlock/enable 1 write /sys/kernel/debug/tracing/tracing_on 1 write /sys/kernel/tracing/tracing_on 1 @@ -394,6 +405,103 @@ on post-fs-data && property:persist.mm_events.enabled=true chmod 0666 /sys/kernel/debug/tracing/instances/mm_events/per_cpu/cpu23/trace chmod 0666 /sys/kernel/tracing/instances/mm_events/per_cpu/cpu23/trace +# Handle hyp tracing instance +on late-init && property:ro.boot.hypervisor.vm.supported=1 + +# Hypervisor tracing instance doesn't support changing trace_clock + chmod 0440 /sys/kernel/debug/tracing/hyp/trace_clock + chmod 0440 /sys/kernel/tracing/hyp/trace_clock + + chmod 0660 /sys/kernel/debug/tracing/hyp/buffer_size_kb + chmod 0660 /sys/kernel/tracing/hyp/buffer_size_kb + + chmod 0660 /sys/kernel/debug/tracing/hyp/tracing_on + chmod 0660 /sys/kernel/tracing/hyp/tracing_on + +# Tracing disabled by default + write /sys/kernel/debug/tracing/hyp/tracing_on 0 + write /sys/kernel/tracing/hyp/tracing_on 0 + +# Read and truncate the hyp trace. + chmod 0660 /sys/kernel/debug/tracing/hyp/trace + chmod 0660 /sys/kernel/tracing/hyp/trace + +# Read and truncate the per-CPU kernel trace. +# Cannot use wildcards in .rc files. Update this if there is a phone with +# TODO(b/249050813, ioffe): introduce per-cpu wildcard + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu0/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu0/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu1/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu1/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu2/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu2/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu3/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu3/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu4/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu4/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu5/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu5/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu6/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu6/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu7/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu7/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu8/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu8/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu9/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu9/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu10/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu10/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu11/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu11/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu12/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu12/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu13/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu13/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu14/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu14/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu15/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu15/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu16/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu16/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu17/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu17/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu18/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu18/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu19/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu19/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu20/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu20/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu21/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu21/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu22/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu22/trace + chmod 0660 /sys/kernel/debug/tracing/hyp/per_cpu/cpu23/trace + chmod 0660 /sys/kernel/tracing/hyp/per_cpu/cpu23/trace + + chmod 0440 /sys/kernel/debug/tracing/hyp/events/header_page + chmod 0440 /sys/kernel/tracing/hyp/events/header_page + +# Hyp events start here + +# hyp_enter event + chmod 0660 /sys/kernel/debug/tracing/hyp/events/hyp/hyp_enter/enable + chmod 0660 /sys/kernel/tracing/hyp/events/hyp/hyp_enter/enable +# TODO(b/249050813): should this be handled in kernel? + chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/hyp_enter/format + chmod 0440 /sys/kernel/tracing/hyp/events/hyp/hyp_enter/format + chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/hyp_enter/id + chmod 0440 /sys/kernel/tracing/hyp/events/hyp/hyp_enter/id + +# hyp_exit event + chmod 0660 /sys/kernel/debug/tracing/hyp/events/hyp/hyp_exit/enable + chmod 0660 /sys/kernel/tracing/hyp/events/hyp/hyp_exit/enable +# TODO(b/249050813): should this be handled in kernel? + chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/hyp_exit/format + chmod 0440 /sys/kernel/tracing/hyp/events/hyp/hyp_exit/format + chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/hyp_exit/id + chmod 0440 /sys/kernel/tracing/hyp/events/hyp/hyp_exit/id + + on property:persist.debug.atrace.boottrace=1 start boottrace @@ -405,6 +513,13 @@ service boottrace /system/bin/atrace --async_start -f /data/misc/boottrace/categ on property:sys.boot_completed=1 && property:ro.boot.fastboot.boottrace=enabled setprop debug.atrace.tags.enableflags 0 setprop persist.traced.enable 1 + write /sys/kernel/tracing/events/binder/binder_transaction/enable 0 + write /sys/kernel/tracing/events/binder/binder_transaction_received/enable 0 + write /sys/kernel/tracing/events/binder/binder_transaction_alloc_buf/enable 0 + write /sys/kernel/tracing/events/binder/binder_set_priority/enable 0 + write /sys/kernel/tracing/events/binder/binder_lock/enable 0 + write /sys/kernel/tracing/events/binder/binder_locked/enable 0 + write /sys/kernel/tracing/events/binder/binder_unlock/enable 0 write /sys/kernel/debug/tracing/tracing_on 0 write /sys/kernel/tracing/tracing_on 0 diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp index 42e9e0f1a7..a7bc018ff6 100644 --- a/cmds/dumpstate/DumpstateService.cpp +++ b/cmds/dumpstate/DumpstateService.cpp @@ -58,6 +58,13 @@ static binder::Status exception(uint32_t code, const std::string& msg, 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 @@ binder::Status DumpstateService::cancelBugreport(int32_t calling_uid, 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 997999c805..dd7331932c 100644 --- a/cmds/dumpstate/DumpstateService.h +++ b/cmds/dumpstate/DumpstateService.h @@ -46,6 +46,13 @@ class DumpstateService : public BinderService<DumpstateService>, public BnDumpst 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/OWNERS b/cmds/dumpstate/OWNERS index 5f56531754..ab81ecf4f4 100644 --- a/cmds/dumpstate/OWNERS +++ b/cmds/dumpstate/OWNERS @@ -3,3 +3,4 @@ set noparent gavincorkery@google.com nandana@google.com jsharkey@android.com +smoreland@google.com
\ No newline at end of file diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl index d4323af3cf..0dc8f5ad64 100644 --- a/cmds/dumpstate/binder/android/os/IDumpstate.aidl +++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl @@ -50,7 +50,10 @@ interface IDumpstate { 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 @@ interface IDumpstate { * @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 50c1624dc2..e8891d3236 100644 --- a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl +++ b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl @@ -50,6 +50,9 @@ interface IDumpstateListener { /* 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 @@ interface IDumpstateListener { /** * 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 d77b45800d..23cdd10a25 100644 --- a/cmds/dumpstate/dumpstate.cpp +++ b/cmds/dumpstate/dumpstate.cpp @@ -186,6 +186,7 @@ void add_mountinfo(); #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 @@ static bool skip_not_stat(const char *path) { 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 @@ Dumpstate::RunStatus Dumpstate::DumpstateDefaultAfterCritical() { 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()); @@ -2047,6 +2062,8 @@ static void DumpstateTelephonyOnly(const std::string& calling_package) { SEC_TO_MSEC(10)); RunDumpsys("DUMPSYS", {"telephony.registry"}, CommandOptions::WithTimeout(90).Build(), SEC_TO_MSEC(10)); + RunDumpsys("DUMPSYS", {"telecom"}, CommandOptions::WithTimeout(90).Build(), + SEC_TO_MSEC(10)); if (include_sensitive_info) { // Contains raw IP addresses, omit from reports on user builds. RunDumpsys("DUMPSYS", {"netd"}, CommandOptions::WithTimeout(90).Build(), SEC_TO_MSEC(10)); @@ -2825,6 +2842,7 @@ void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode, 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 +2925,64 @@ void Dumpstate::Initialize() { 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 +3000,7 @@ Dumpstate::RunStatus Dumpstate::Run(int32_t calling_uid, const std::string& call break; } } - return status; } - void Dumpstate::Cancel() { CleanupTmpFiles(); android::os::UnlinkAndLogOnError(log_path_); @@ -3181,7 +3251,7 @@ Dumpstate::RunStatus Dumpstate::RunInternal(int32_t calling_uid, // 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 +3340,15 @@ void Dumpstate::MaybeSnapshotUiTraces() { 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 +3405,11 @@ void Dumpstate::onUiIntensiveBugreportDumpsFinished(int32_t calling_uid) { } 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 9f894b5079..8a31c314d9 100644 --- a/cmds/dumpstate/dumpstate.h +++ b/cmds/dumpstate/dumpstate.h @@ -207,7 +207,9 @@ class Dumpstate { // 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 @@ class Dumpstate { */ 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 @@ class Dumpstate { 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 @@ class Dumpstate { 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 @@ class Dumpstate { 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 12a7cfface..a80da4ec55 100644 --- a/cmds/dumpstate/dumpstate.rc +++ b/cmds/dumpstate/dumpstate.rc @@ -8,7 +8,6 @@ service dumpstate /system/bin/dumpstate -s 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 @@ service dumpstatez /system/bin/dumpstate -S 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 b091c8e297..ccf64fe54e 100644 --- a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp +++ b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp @@ -160,7 +160,7 @@ class DumpstateListener : public BnDumpstateListener { 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 1ffcafa544..aa5219bb5b 100644 --- a/cmds/dumpstate/tests/dumpstate_test.cpp +++ b/cmds/dumpstate/tests/dumpstate_test.cpp @@ -71,7 +71,7 @@ class DumpstateListenerMock : public IDumpstateListener { 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 @@ TEST_F(DumpOptionsTest, ValidateOptionsRemoteMode) { 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 @@ class ZippedBugReportStreamTest : public DumpstateBaseTest { }; // 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/installd/dexopt.cpp b/cmds/installd/dexopt.cpp index 34ea7597b4..ce3d669a63 100644 --- a/cmds/installd/dexopt.cpp +++ b/cmds/installd/dexopt.cpp @@ -442,6 +442,16 @@ static unique_fd open_current_profile(uid_t uid, userid_t user, const std::strin static unique_fd open_reference_profile(uid_t uid, const std::string& package_name, const std::string& location, bool read_write, bool is_secondary_dex) { std::string profile = create_reference_profile_path(package_name, location, is_secondary_dex); + if (read_write && GetBoolProperty("dalvik.vm.useartservice", false)) { + // ART Service doesn't use flock and instead assumes profile files are + // immutable, so ensure we don't open a file for writing when it's + // active. + // TODO(b/251921228): Normally installd isn't called at all in that + // case, but OTA is still an exception that uses the legacy code. + LOG(ERROR) << "Opening ref profile " << profile + << " for writing is unsafe when ART Service is enabled."; + return invalid_unique_fd(); + } return open_profile( uid, profile, @@ -450,14 +460,13 @@ static unique_fd open_reference_profile(uid_t uid, const std::string& package_na } static UniqueFile open_reference_profile_as_unique_file(uid_t uid, const std::string& package_name, - const std::string& location, bool read_write, bool is_secondary_dex) { + const std::string& location, + bool is_secondary_dex) { std::string profile_path = create_reference_profile_path(package_name, location, is_secondary_dex); - unique_fd ufd = open_profile( - uid, - profile_path, - read_write ? (O_CREAT | O_RDWR) : O_RDONLY, - S_IRUSR | S_IWUSR | S_IRGRP); // so that ART can also read it when apps run. + unique_fd ufd = open_profile(uid, profile_path, O_RDONLY, + S_IRUSR | S_IWUSR | + S_IRGRP); // so that ART can also read it when apps run. return UniqueFile(ufd.release(), profile_path, [](const std::string& path) { clear_profile(path); @@ -1104,8 +1113,7 @@ UniqueFile maybe_open_reference_profile(const std::string& pkgname, location = profile_name; } } - return open_reference_profile_as_unique_file(uid, pkgname, location, /*read_write*/false, - is_secondary_dex); + return open_reference_profile_as_unique_file(uid, pkgname, location, is_secondary_dex); } // Opens the vdex files and assigns the input fd to in_vdex_wrapper and the output fd to diff --git a/cmds/installd/otapreopt.cpp b/cmds/installd/otapreopt.cpp index 6a3120c1b7..bf2c0d1023 100644 --- a/cmds/installd/otapreopt.cpp +++ b/cmds/installd/otapreopt.cpp @@ -308,7 +308,7 @@ private: // This is different from the normal installd. We only do the base // directory, the rest will be created on demand when each app is compiled. if (access(GetOtaDirectoryPrefix().c_str(), R_OK) < 0) { - LOG(ERROR) << "Could not access " << GetOtaDirectoryPrefix(); + PLOG(ERROR) << "Could not access " << GetOtaDirectoryPrefix(); return false; } @@ -460,7 +460,7 @@ private: // this tool will wipe the OTA artifact cache and try again (for robustness after // a failed OTA with remaining cache artifacts). if (access(apk_path, F_OK) != 0) { - LOG(WARNING) << "Skipping A/B OTA preopt of non-existing package " << apk_path; + PLOG(WARNING) << "Skipping A/B OTA preopt of non-existing package " << apk_path; return true; } diff --git a/cmds/installd/otapreopt_chroot.cpp b/cmds/installd/otapreopt_chroot.cpp index c62734a925..1b7acabf70 100644 --- a/cmds/installd/otapreopt_chroot.cpp +++ b/cmds/installd/otapreopt_chroot.cpp @@ -45,6 +45,10 @@ using android::base::StringPrintf; namespace android { namespace installd { +// We don't know the filesystem types of the partitions in the update package, +// so just try the possibilities one by one. +static constexpr std::array kTryMountFsTypes = {"ext4", "erofs"}; + static void CloseDescriptor(int fd) { if (fd >= 0) { int result = close(fd); @@ -82,6 +86,27 @@ static void DeactivateApexPackages() { } } +static bool TryMountWithFstypes(const char* block_device, const char* target) { + for (int i = 0; i < kTryMountFsTypes.size(); ++i) { + const char* fstype = kTryMountFsTypes[i]; + int mount_result = mount(block_device, target, fstype, MS_RDONLY, /* data */ nullptr); + if (mount_result == 0) { + return true; + } + if (errno == EINVAL && i < kTryMountFsTypes.size() - 1) { + // Only try the next fstype if mounting failed due to the current one + // being invalid. + LOG(WARNING) << "Failed to mount " << block_device << " on " << target << " with " + << fstype << " - trying " << kTryMountFsTypes[i + 1]; + } else { + PLOG(ERROR) << "Failed to mount " << block_device << " on " << target << " with " + << fstype; + return false; + } + } + __builtin_unreachable(); +} + static void TryExtraMount(const char* name, const char* slot, const char* target) { std::string partition_name = StringPrintf("%s%s", name, slot); @@ -91,12 +116,7 @@ static void TryExtraMount(const char* name, const char* slot, const char* target if (dm.GetState(partition_name) != dm::DmDeviceState::INVALID) { std::string path; if (dm.GetDmDevicePathByName(partition_name, &path)) { - int mount_result = mount(path.c_str(), - target, - "ext4", - MS_RDONLY, - /* data */ nullptr); - if (mount_result == 0) { + if (TryMountWithFstypes(path.c_str(), target)) { return; } } @@ -105,12 +125,7 @@ static void TryExtraMount(const char* name, const char* slot, const char* target // Fall back and attempt a direct mount. std::string block_device = StringPrintf("/dev/block/by-name/%s", partition_name.c_str()); - int mount_result = mount(block_device.c_str(), - target, - "ext4", - MS_RDONLY, - /* data */ nullptr); - UNUSED(mount_result); + (void)TryMountWithFstypes(block_device.c_str(), target); } // Entry for otapreopt_chroot. Expected parameters are: diff --git a/cmds/installd/otapreopt_script.sh b/cmds/installd/otapreopt_script.sh index f950276090..db5c34edc2 100644 --- a/cmds/installd/otapreopt_script.sh +++ b/cmds/installd/otapreopt_script.sh @@ -60,6 +60,11 @@ print -u${STATUS_FD} "global_progress $PROGRESS" i=0 while ((i<MAXIMUM_PACKAGES)) ; do + DONE=$(cmd otadexopt done) + if [ "$DONE" = "OTA complete." ] ; then + break + fi + DEXOPT_PARAMS=$(cmd otadexopt next) /system/bin/otapreopt_chroot $STATUS_FD $TARGET_SLOT_SUFFIX $DEXOPT_PARAMS >&- 2>&- @@ -67,13 +72,8 @@ while ((i<MAXIMUM_PACKAGES)) ; do PROGRESS=$(cmd otadexopt progress) print -u${STATUS_FD} "global_progress $PROGRESS" - DONE=$(cmd otadexopt done) - if [ "$DONE" = "OTA incomplete." ] ; then - sleep 1 - i=$((i+1)) - continue - fi - break + sleep 1 + i=$((i+1)) done DONE=$(cmd otadexopt done) diff --git a/cmds/service/service.cpp b/cmds/service/service.cpp index d5ca725eb9..5e8ef5d7d8 100644 --- a/cmds/service/service.cpp +++ b/cmds/service/service.cpp @@ -75,7 +75,7 @@ int main(int argc, char* const argv[]) ProcessState::initWithDriver("/dev/vndbinder"); #endif #ifndef __ANDROID__ - setDefaultServiceManager(createRpcDelegateServiceManager({.maxOutgoingThreads = 1})); + setDefaultServiceManager(createRpcDelegateServiceManager({.maxOutgoingConnections = 1})); #endif sp<IServiceManager> sm = defaultServiceManager(); fflush(stdout); diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp index 695faf8a78..4da0cd6897 100644 --- a/cmds/servicemanager/ServiceManager.cpp +++ b/cmds/servicemanager/ServiceManager.cpp @@ -39,6 +39,11 @@ using ::android::internal::Stability; 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,18 @@ static bool meetsDeclarationRequirements(const sp<IBinder>& binder, const std::s } #endif // !VENDORSERVICEMANAGER +ServiceManager::Service::~Service() { + if (hasClients) { + // only expected to happen on process death, we don't store the service + // name this late (it's in the map that holds this service), but if it + // is happening, we might want to change 'unlinkToDeath' to explicitly + // clear this bit so that we can abort in other cases, where it would + // mean inconsistent logic in servicemanager (unexpected and tested, but + // the original lazy service impl here had that bug). + 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 +290,8 @@ sp<IBinder> ServiceManager::tryGetService(const std::string& name, bool startIfN 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 +305,13 @@ sp<IBinder> ServiceManager::tryGetService(const std::string& name, bool startIfN } 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 +401,13 @@ Status ServiceManager::addService(const std::string& name, const sp<IBinder>& bi }; 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 +447,17 @@ Status ServiceManager::registerForNotifications( 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,28 +728,28 @@ ssize_t ServiceManager::Service::getNodeStrongRefCount() { 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) { + if (!service.hasClients && !hasKernelReportedClients) { sendClientCallbackNotifications(serviceName, true, "service is guaranteed to be in use"); } @@ -726,21 +758,23 @@ ssize_t ServiceManager::handleServiceClientCallback(const std::string& serviceNa 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, "we now have a record of a client"); - } + // 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) { + // 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, @@ -753,13 +787,10 @@ void ServiceManager::sendClientCallbackNotifications(const std::string& serviceN } Service& service = serviceIt->second; - CHECK(hasClients != service.hasClients) - << "Record shows: " << service.hasClients - << " so we can't tell clients again that we have client: " << hasClients - << " when: " << context; + CHECK_NE(hasClients, service.hasClients) << context; - ALOGI("Notifying %s they %s have clients when %s", serviceName.c_str(), - hasClients ? "do" : "don't", context); + 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()) @@ -803,26 +834,29 @@ Status ServiceManager::tryUnregisterService(const std::string& name, const sp<IB 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 f9d4f8fd90..3aa6731eb3 100644 --- a/cmds/servicemanager/ServiceManager.h +++ b/cmds/servicemanager/ServiceManager.h @@ -80,6 +80,8 @@ private: // 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,7 +93,9 @@ private: void removeRegistrationCallback(const wp<IBinder>& who, ServiceCallbackMap::iterator* it, bool* found); - ssize_t handleServiceClientCallback(const std::string& serviceName, bool isCalledOnInterval); + // 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); diff --git a/cmds/servicemanager/servicemanager.rc b/cmds/servicemanager/servicemanager.rc index 3bd6db5ef6..4f92b3a374 100644 --- a/cmds/servicemanager/servicemanager.rc +++ b/cmds/servicemanager/servicemanager.rc @@ -5,7 +5,7 @@ service servicemanager /system/bin/servicemanager 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 0fd8d8ee2a..cae32e3bc3 100644 --- a/cmds/servicemanager/test_sm.cpp +++ b/cmds/servicemanager/test_sm.cpp @@ -383,6 +383,22 @@ TEST(ServiceNotifications, NoPermissionsRegister) { 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/headers/media_plugin/media/openmax/OMX_AsString.h b/headers/media_plugin/media/openmax/OMX_AsString.h index ce30b417e0..165a868d57 100644 --- a/headers/media_plugin/media/openmax/OMX_AsString.h +++ b/headers/media_plugin/media/openmax/OMX_AsString.h @@ -561,6 +561,7 @@ inline static const char *asString(OMX_INDEXEXTTYPE i, const char *def = "??") { case OMX_IndexConfigPriority: return "ConfigPriority"; case OMX_IndexConfigOperatingRate: return "ConfigOperatingRate"; case OMX_IndexParamConsumerUsageBits: return "ParamConsumerUsageBits"; + case OMX_IndexParamConsumerUsageBits64: return "ParamConsumerUsageBits64"; case OMX_IndexConfigLatency: return "ConfigLatency"; default: return asString((OMX_INDEXTYPE)i, def); } diff --git a/headers/media_plugin/media/openmax/OMX_IndexExt.h b/headers/media_plugin/media/openmax/OMX_IndexExt.h index 0af40dd28e..5ddd719ba1 100644 --- a/headers/media_plugin/media/openmax/OMX_IndexExt.h +++ b/headers/media_plugin/media/openmax/OMX_IndexExt.h @@ -105,6 +105,7 @@ typedef enum OMX_INDEXEXTTYPE { OMX_IndexConfigLowLatency, /**< reference: OMX_CONFIG_BOOLEANTYPE */ OMX_IndexConfigAndroidTunnelPeek, /**< reference: OMX_CONFIG_BOOLEANTYPE */ OMX_IndexConfigAndroidTunnelPeekLegacyMode, /**< reference: OMX_CONFIG_BOOLEANTYPE */ + OMX_IndexParamConsumerUsageBits64, /**< reference: OMX_PARAM_U64TYPE */ OMX_IndexExtOtherEndUnused, /* Time configurations */ diff --git a/include/android/OWNERS b/include/android/OWNERS new file mode 100644 index 0000000000..38f9c5563a --- /dev/null +++ b/include/android/OWNERS @@ -0,0 +1 @@ +per-file input.h, keycodes.h = file:platform/frameworks/base:/INPUT_OWNERS diff --git a/include/android/choreographer.h b/include/android/choreographer.h index cd8e63dec2..f999708f04 100644 --- a/include/android/choreographer.h +++ b/include/android/choreographer.h @@ -219,12 +219,16 @@ void AChoreographer_unregisterRefreshRateCallback(AChoreographer* choreographer, * * Note that this time should \b not be used to advance animation clocks. * Instead, see AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos(). + * + * Available since API level 33. */ int64_t AChoreographerFrameCallbackData_getFrameTimeNanos( const AChoreographerFrameCallbackData* data) __INTRODUCED_IN(33); /** * The number of possible frame timelines. + * + * Available since API level 33. */ size_t AChoreographerFrameCallbackData_getFrameTimelinesLength( const AChoreographerFrameCallbackData* data) __INTRODUCED_IN(33); @@ -233,15 +237,20 @@ size_t AChoreographerFrameCallbackData_getFrameTimelinesLength( * Gets the index of the platform-preferred frame timeline. * The preferred frame timeline is the default * by which the platform scheduled the app, based on the device configuration. + * + * Available since API level 33. */ size_t AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex( const AChoreographerFrameCallbackData* data) __INTRODUCED_IN(33); /** * Gets the token used by the platform to identify the frame timeline at the given \c index. + * q + * Available since API level 33. * * \param index index of a frame timeline, in \f( [0, FrameTimelinesLength) \f). See * AChoreographerFrameCallbackData_getFrameTimelinesLength() + * */ AVsyncId AChoreographerFrameCallbackData_getFrameTimelineVsyncId( const AChoreographerFrameCallbackData* data, size_t index) __INTRODUCED_IN(33); @@ -250,6 +259,8 @@ AVsyncId AChoreographerFrameCallbackData_getFrameTimelineVsyncId( * Gets the time in nanoseconds at which the frame described at the given \c index is expected to * be presented. This time should be used to advance any animation clocks. * + * Available since API level 33. + * * \param index index of a frame timeline, in \f( [0, FrameTimelinesLength) \f). See * AChoreographerFrameCallbackData_getFrameTimelinesLength() */ @@ -260,6 +271,8 @@ int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTime * Gets the time in nanoseconds at which the frame described at the given \c index needs to be * ready by in order to be presented on time. * + * Available since API level 33. + * * \param index index of a frame timeline, in \f( [0, FrameTimelinesLength) \f). See * AChoreographerFrameCallbackData_getFrameTimelinesLength() */ diff --git a/include/android/surface_control.h b/include/android/surface_control.h index f76e73d3cf..e4926a6a8d 100644 --- a/include/android/surface_control.h +++ b/include/android/surface_control.h @@ -521,6 +521,47 @@ void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* transactio __INTRODUCED_IN(29); /** + * Sets the desired extended range brightness for the layer. This only applies for layers whose + * dataspace has RANGE_EXTENDED set on it. + * + * Available since API level 34. + * + * @param surface_control The layer whose extended range brightness is being specified + * @param currentBufferRatio The current hdr/sdr ratio of the current buffer as represented as + * peakHdrBrightnessInNits / targetSdrWhitePointInNits. For example if the + * buffer was rendered with a target SDR whitepoint of 100nits and a max + * display brightness of 200nits, this should be set to 2.0f. + * + * Default value is 1.0f. + * + * Transfer functions that encode their own brightness ranges, such as + * HLG or PQ, should also set this to 1.0f and instead communicate + * extended content brightness information via metadata such as CTA861_3 + * or SMPTE2086. + * + * Must be finite && >= 1.0f + * + * @param desiredRatio The desired hdr/sdr ratio as represented as peakHdrBrightnessInNits / + * targetSdrWhitePointInNits. This can be used to communicate the max desired + * brightness range. This is similar to the "max luminance" value in other + * HDR metadata formats, but represented as a ratio of the target SDR whitepoint + * to the max display brightness. The system may not be able to, or may choose + * not to, deliver the requested range. + * + * If unspecified, the system will attempt to provide the best range it can + * for the given ambient conditions & device state. However, voluntarily + * reducing the requested range can help improve battery life as well as can + * improve quality by ensuring greater bit depth is allocated to the luminance + * range in use. + * + * Must be finite && >= 1.0f + */ +void ASurfaceTransaction_setExtendedRangeBrightness(ASurfaceTransaction* transaction, + ASurfaceControl* surface_control, + float currentBufferRatio, + float desiredRatio) __INTRODUCED_IN(__ANDROID_API_U__); + +/** * Same as ASurfaceTransaction_setFrameRateWithChangeStrategy(transaction, surface_control, * frameRate, compatibility, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS). * @@ -614,6 +655,8 @@ void ASurfaceTransaction_clearFrameRate(ASurfaceTransaction* transaction, * and pushing buffers earlier for server side queuing will be advantageous * in such cases. * + * Available since API level 31. + * * \param transaction The transaction in which to make the change. * \param surface_control The ASurfaceControl on which to control buffer backpressure behavior. * \param enableBackPressure Whether to enable back pressure. @@ -635,6 +678,8 @@ void ASurfaceTransaction_setEnableBackPressure(ASurfaceTransaction* transaction, * AChoreographer_postVsyncCallback(). The \c vsyncId can then be extracted from the * callback payload using AChoreographerFrameCallbackData_getFrameTimelineVsyncId(). * + * Available since API level 33. + * * \param vsyncId The vsync ID received from AChoreographer, setting the frame's presentation target * to the corresponding expected presentation time and deadline from the frame to be rendered. A * stale or invalid value will be ignored. diff --git a/include/input/Input.h b/include/input/Input.h index 7573282fc1..608519b70e 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -1113,6 +1113,7 @@ public: 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/InputEventLabels.h b/include/input/InputEventLabels.h index 4668fce116..9dedd2b2da 100644 --- a/include/input/InputEventLabels.h +++ b/include/input/InputEventLabels.h @@ -30,6 +30,12 @@ struct InputEventLabel { int value; }; +struct EvdevEventLabel { + std::string type; + std::string code; + std::string value; +}; + // NOTE: If you want a new key code, axis code, led code or flag code in keylayout file, // then you must add it to InputEventLabels.cpp. @@ -52,6 +58,8 @@ public: static std::optional<int> getLedByLabel(const char* label); + static EvdevEventLabel getLinuxEvdevLabel(int32_t type, int32_t code, int32_t value); + private: static const std::unordered_map<std::string, int> KEYCODES; diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h index 1c52792cf6..a1be542d7b 100644 --- a/include/input/InputTransport.h +++ b/include/input/InputTransport.h @@ -38,6 +38,7 @@ #include <binder/IBinder.h> #include <binder/Parcelable.h> #include <input/Input.h> +#include <input/InputVerifier.h> #include <sys/stat.h> #include <ui/Transform.h> #include <utils/BitSet.h> @@ -444,6 +445,7 @@ public: private: std::shared_ptr<InputChannel> mChannel; + InputVerifier mInputVerifier; }; /* diff --git a/include/input/InputVerifier.h b/include/input/InputVerifier.h new file mode 100644 index 0000000000..d4589f53b5 --- /dev/null +++ b/include/input/InputVerifier.h @@ -0,0 +1,49 @@ +/* + * 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 <input/Input.h> +#include <map> + +namespace android { + +/* + * Crash if the provided touch stream is inconsistent. + * + * TODO(b/211379801): Add support for hover events: + * - No hover move without enter + * - No touching pointers when hover enter + * - No hovering pointers when touching + * - Only 1 hovering pointer max + */ +class InputVerifier { +public: + InputVerifier(const std::string& name); + + void processMovement(int32_t deviceId, int32_t action, uint32_t pointerCount, + const PointerProperties* pointerProperties, + const PointerCoords* pointerCoords, int32_t flags); + +private: + const std::string mName; + std::map<int32_t /*deviceId*/, std::bitset<MAX_POINTER_ID + 1>> mTouchingPointerIdsByDevice; + void ensureTouchingPointersMatch(int32_t deviceId, uint32_t pointerCount, + const PointerProperties* pointerProperties, + const char* action) const; +}; + +} // namespace android diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h index b5e6f65e48..c67310eaec 100644 --- a/include/input/KeyCharacterMap.h +++ b/include/input/KeyCharacterMap.h @@ -235,7 +235,7 @@ private: KeyedVector<int32_t, Key*> mKeys; KeyboardType mType; std::string mLoadFileName; - bool mLayoutOverlayApplied; + bool mLayoutOverlayApplied = false; std::map<int32_t /* fromAndroidKeyCode */, int32_t /* toAndroidKeyCode */> mKeyRemapping; std::map<int32_t /* fromScanCode */, int32_t /* toAndroidKeyCode */> mKeysByScanCode; diff --git a/include/input/MotionPredictor.h b/include/input/MotionPredictor.h index 3fae4e6b68..de8ddcabeb 100644 --- a/include/input/MotionPredictor.h +++ b/include/input/MotionPredictor.h @@ -19,8 +19,10 @@ #include <cstdint> #include <memory> #include <mutex> +#include <string> #include <unordered_map> +#include <android-base/result.h> #include <android-base/thread_annotations.h> #include <android/sysprop/InputProperties.sysprop.h> #include <input/Input.h> @@ -65,10 +67,17 @@ public: * 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, const char* modelPath = nullptr, + MotionPredictor(nsecs_t predictionTimestampOffsetNanos, std::function<bool()> checkEnableMotionPrediction = isMotionPredictionEnabled); - void record(const MotionEvent& event); - std::vector<std::unique_ptr<MotionEvent>> predict(nsecs_t timestamp); + /** + * Record the actual motion received by the view. This event will be used for calculating the + * predictions. + * + * @return empty result if the event was processed correctly, error if the event is not + * consistent with the previously recorded events. + */ + android::base::Result<void> record(const MotionEvent& event); + std::unique_ptr<MotionEvent> predict(nsecs_t timestamp); bool isPredictionAvailable(int32_t deviceId, int32_t source); private: @@ -76,9 +85,9 @@ private: 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; + + std::unique_ptr<TfLiteMotionPredictorBuffers> mBuffers; + std::optional<MotionEvent> mLastEvent; }; } // namespace android diff --git a/include/input/RingBuffer.h b/include/input/RingBuffer.h new file mode 100644 index 0000000000..5e9972eef8 --- /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/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 { + if (elementIndex > size()) { + abort(); + } + size_type index = mBegin + elementIndex; + if (index >= capacity()) { + index -= capacity(); + } + if (index >= capacity()) { + abort(); + } + 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 index ff0f51c7d9..7de551b417 100644 --- a/include/input/TfLiteMotionPredictor.h +++ b/include/input/TfLiteMotionPredictor.h @@ -22,8 +22,9 @@ #include <memory> #include <optional> #include <span> -#include <string> -#include <vector> + +#include <android-base/mapped_file.h> +#include <input/RingBuffer.h> #include <tensorflow/lite/core/api/error_reporter.h> #include <tensorflow/lite/interpreter.h> @@ -83,11 +84,11 @@ public: private: int64_t mTimestamp = 0; - std::vector<float> mInputR; - std::vector<float> mInputPhi; - std::vector<float> mInputPressure; - std::vector<float> mInputTilt; - std::vector<float> mInputOrientation; + 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; @@ -98,7 +99,9 @@ private: class TfLiteMotionPredictorModel { public: // Creates a model from an encoded Flatbuffer model. - static std::unique_ptr<TfLiteMotionPredictorModel> create(const char* modelPath); + static std::unique_ptr<TfLiteMotionPredictorModel> create(); + + ~TfLiteMotionPredictorModel(); // Returns the length of the model's input buffers. size_t inputLength() const; @@ -121,7 +124,7 @@ public: std::span<const float> outputPressure() const; private: - explicit TfLiteMotionPredictorModel(std::string model); + explicit TfLiteMotionPredictorModel(std::unique_ptr<android::base::MappedFile> model); void allocateTensors(); void attachInputTensors(); @@ -137,7 +140,7 @@ private: const TfLiteTensor* mOutputPhi = nullptr; const TfLiteTensor* mOutputPressure = nullptr; - std::string mFlatBuffer; + 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; diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h index da97c3e855..6d1de649f9 100644 --- a/include/input/VelocityTracker.h +++ b/include/input/VelocityTracker.h @@ -17,6 +17,7 @@ #pragma once #include <input/Input.h> +#include <input/RingBuffer.h> #include <utils/BitSet.h> #include <utils/Timers.h> #include <map> @@ -31,6 +32,8 @@ class VelocityTrackerStrategy; */ class VelocityTracker { public: + static const size_t MAX_DEGREE = 4; + enum class Strategy : int32_t { DEFAULT = -1, MIN = 0, @@ -47,23 +50,6 @@ public: MAX = LEGACY, }; - struct Estimator { - static const size_t MAX_DEGREE = 4; - - // Estimator time base. - nsecs_t time = 0; - - // Polynomial coefficients describing motion. - std::array<float, MAX_DEGREE + 1> coeff{}; - - // Polynomial degree (number of coefficients), or zero if no information is - // available. - uint32_t degree = 0; - - // Confidence (coefficient of determination), between 0 (no fit) and 1 (perfect fit). - float confidence = 0; - }; - /* * Contains all available velocity data from a VelocityTracker. */ @@ -124,11 +110,6 @@ public: // [-maxVelocity, maxVelocity], inclusive. ComputedVelocity getComputedVelocity(int32_t units, float maxVelocity); - // Gets an estimator for the recent movements of the specified pointer id for the given axis. - // Returns false and clears the estimator if there is no information available - // about the pointer. - std::optional<Estimator> getEstimator(int32_t axis, int32_t pointerId) const; - // Gets the active pointer id, or -1 if none. inline int32_t getActivePointerId() const { return mActivePointerId.value_or(-1); } @@ -169,14 +150,36 @@ public: virtual void clearPointer(int32_t pointerId) = 0; virtual void addMovement(nsecs_t eventTime, int32_t pointerId, float position) = 0; - virtual std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const = 0; + virtual std::optional<float> getVelocity(int32_t pointerId) const = 0; }; +/** + * A `VelocityTrackerStrategy` that accumulates added data points and processes the accumulated data + * points when getting velocity. + */ +class AccumulatingVelocityTrackerStrategy : public VelocityTrackerStrategy { +public: + void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override; + void clearPointer(int32_t pointerId) override; + +protected: + struct Movement { + nsecs_t eventTime; + float position; + }; + + // Number of samples to keep. + // If different strategies would like to maintain different history size, we can make this a + // protected const field. + static constexpr uint32_t HISTORY_SIZE = 20; + + std::map<int32_t /*pointerId*/, RingBuffer<Movement>> mMovements; +}; /* * Velocity tracker algorithm based on least-squares linear regression. */ -class LeastSquaresVelocityTrackerStrategy : public VelocityTrackerStrategy { +class LeastSquaresVelocityTrackerStrategy : public AccumulatingVelocityTrackerStrategy { public: enum class Weighting { // No weights applied. All data points are equally reliable. @@ -193,13 +196,11 @@ public: RECENT, }; - // Degree must be no greater than Estimator::MAX_DEGREE. + // Degree must be no greater than VelocityTracker::MAX_DEGREE. LeastSquaresVelocityTrackerStrategy(uint32_t degree, Weighting weighting = Weighting::NONE); ~LeastSquaresVelocityTrackerStrategy() override; - void clearPointer(int32_t pointerId) override; - void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override; - std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override; + std::optional<float> getVelocity(int32_t pointerId) const override; private: // Sample horizon. @@ -207,23 +208,12 @@ private: // changes in direction. static const nsecs_t HORIZON = 100 * 1000000; // 100 ms - // Number of samples to keep. - static const uint32_t HISTORY_SIZE = 20; - - struct Movement { - nsecs_t eventTime; - float position; - }; - float chooseWeight(int32_t pointerId, uint32_t index) const; const uint32_t mDegree; const Weighting mWeighting; - std::map<int32_t /*pointerId*/, size_t /*positionInArray*/> mIndex; - std::map<int32_t /*pointerId*/, std::array<Movement, HISTORY_SIZE>> mMovements; }; - /* * Velocity tracker algorithm that uses an IIR filter. */ @@ -235,7 +225,7 @@ public: void clearPointer(int32_t pointerId) override; void addMovement(nsecs_t eventTime, int32_t pointerId, float positions) override; - std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override; + std::optional<float> getVelocity(int32_t pointerId) const override; private: // Current state estimate for a particular pointer. @@ -252,49 +242,33 @@ private: void initState(State& state, nsecs_t eventTime, float pos) const; void updateState(State& state, nsecs_t eventTime, float pos) const; - void populateEstimator(const State& state, VelocityTracker::Estimator* outEstimator) const; }; /* * Velocity tracker strategy used prior to ICS. */ -class LegacyVelocityTrackerStrategy : public VelocityTrackerStrategy { +class LegacyVelocityTrackerStrategy : public AccumulatingVelocityTrackerStrategy { public: LegacyVelocityTrackerStrategy(); ~LegacyVelocityTrackerStrategy() override; - void clearPointer(int32_t pointerId) override; - void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override; - std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override; + std::optional<float> getVelocity(int32_t pointerId) const override; private: // Oldest sample to consider when calculating the velocity. static const nsecs_t HORIZON = 200 * 1000000; // 100 ms - // Number of samples to keep. - static const uint32_t HISTORY_SIZE = 20; - // The minimum duration between samples when estimating velocity. static const nsecs_t MIN_DURATION = 10 * 1000000; // 10 ms - - struct Movement { - nsecs_t eventTime; - float position; - }; - - std::map<int32_t /*pointerId*/, size_t /*positionInArray*/> mIndex; - std::map<int32_t /*pointerId*/, std::array<Movement, HISTORY_SIZE>> mMovements; }; -class ImpulseVelocityTrackerStrategy : public VelocityTrackerStrategy { +class ImpulseVelocityTrackerStrategy : public AccumulatingVelocityTrackerStrategy { public: ImpulseVelocityTrackerStrategy(bool deltaValues); ~ImpulseVelocityTrackerStrategy() override; - void clearPointer(int32_t pointerId) override; - void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override; - std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override; + std::optional<float> getVelocity(int32_t pointerId) const override; private: // Sample horizon. @@ -302,21 +276,10 @@ private: // changes in direction. static constexpr nsecs_t HORIZON = 100 * 1000000; // 100 ms - // Number of samples to keep. - static constexpr size_t HISTORY_SIZE = 20; - - struct Movement { - nsecs_t eventTime; - float position; - }; - // Whether or not the input movement values for the strategy come in the form of delta values. // If the input values are not deltas, the strategy needs to calculate deltas as part of its // velocity calculation. const bool mDeltaValues; - - std::map<int32_t /*pointerId*/, size_t /*positionInArray*/> mIndex; - std::map<int32_t /*pointerId*/, std::array<Movement, HISTORY_SIZE>> mMovements; }; } // namespace android diff --git a/include/input/VirtualInputDevice.h b/include/input/VirtualInputDevice.h new file mode 100644 index 0000000000..13ffb581b4 --- /dev/null +++ b/include/input/VirtualInputDevice.h @@ -0,0 +1,97 @@ +/* + * 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 <android-base/unique_fd.h> + +namespace android { + +enum class UinputAction { + RELEASE = 0, + PRESS = 1, + MOVE = 2, + CANCEL = 3, +}; + +class VirtualInputDevice { +public: + VirtualInputDevice(android::base::unique_fd fd); + virtual ~VirtualInputDevice(); + +protected: + const android::base::unique_fd mFd; + bool writeInputEvent(uint16_t type, uint16_t code, int32_t value); + bool writeEvKeyEvent(int32_t androidCode, int32_t androidAction, + const std::map<int, int>& evKeyCodeMapping, + const std::map<int, UinputAction>& actionMapping); +}; + +class VirtualKeyboard : public VirtualInputDevice { +public: + static const std::map<int, int> KEY_CODE_MAPPING; + // Expose to share with VirtualDpad. + static const std::map<int, UinputAction> KEY_ACTION_MAPPING; + VirtualKeyboard(android::base::unique_fd fd); + virtual ~VirtualKeyboard() override; + bool writeKeyEvent(int32_t androidKeyCode, int32_t androidAction); +}; + +class VirtualDpad : public VirtualInputDevice { +public: + static const std::map<int, int> DPAD_KEY_CODE_MAPPING; + VirtualDpad(android::base::unique_fd fd); + virtual ~VirtualDpad() override; + bool writeDpadKeyEvent(int32_t androidKeyCode, int32_t androidAction); +}; + +class VirtualMouse : public VirtualInputDevice { +public: + VirtualMouse(android::base::unique_fd fd); + virtual ~VirtualMouse() override; + bool writeButtonEvent(int32_t androidButtonCode, int32_t androidAction); + // TODO(b/259554911): changing float parameters to int32_t. + bool writeRelativeEvent(float relativeX, float relativeY); + bool writeScrollEvent(float xAxisMovement, float yAxisMovement); + +private: + static const std::map<int, UinputAction> BUTTON_ACTION_MAPPING; + static const std::map<int, int> BUTTON_CODE_MAPPING; +}; + +class VirtualTouchscreen : public VirtualInputDevice { +public: + VirtualTouchscreen(android::base::unique_fd fd); + virtual ~VirtualTouchscreen() override; + // TODO(b/259554911): changing float parameters to int32_t. + bool writeTouchEvent(int32_t pointerId, int32_t toolType, int32_t action, float locationX, + float locationY, float pressure, float majorAxisSize); + +private: + static const std::map<int, UinputAction> TOUCH_ACTION_MAPPING; + static const std::map<int, int> TOOL_TYPE_MAPPING; + + /* The set of active touch pointers on this device. + * We only allow pointer id to go up to MAX_POINTERS because the maximum slots of virtual + * touchscreen is set up with MAX_POINTERS. Note that in other cases Android allows pointer id + * to go up to MAX_POINTERS_ID. + */ + std::bitset<MAX_POINTERS> mActivePointers{}; + bool isValidPointerId(int32_t pointerId, UinputAction uinputAction); + bool handleTouchDown(int32_t pointerId); + bool handleTouchUp(int32_t pointerId); +}; +} // namespace android diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h index eaf3b5e791..d50c5f846e 100644 --- a/include/private/performance_hint_private.h +++ b/include/private/performance_hint_private.h @@ -17,6 +17,8 @@ #ifndef ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H #define ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H +#include <stdint.h> + __BEGIN_DECLS /** @@ -27,7 +29,7 @@ void APerformanceHint_setIHintManagerForTesting(void* iManager); /** * Hints for the session used to signal upcoming changes in the mode or workload. */ -enum SessionHint { +enum SessionHint: int32_t { /** * This hint indicates a sudden increase in CPU workload intensity. It means * that this hint session needs extra CPU resources immediately to meet the @@ -61,7 +63,7 @@ enum SessionHint { * @return 0 on success * EPIPE if communication with the system service has failed. */ -int APerformanceHint_sendHint(void* session, int hint); +int APerformanceHint_sendHint(void* session, SessionHint hint); /** * Return the list of thread ids, this API should only be used for testing only. diff --git a/include/private/surface_control_private.h b/include/private/surface_control_private.h index 7e6c51587d..138926e55b 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 @@ void ASurfaceControl_unregisterSurfaceStatsListener(void* context, 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 7e7bba31eb..6a354b4148 100644 --- a/libs/binder/Android.bp +++ b/libs/binder/Android.bp @@ -74,9 +74,6 @@ cc_defaults { name: "libbinder_common_defaults", host_supported: true, - // for vndbinder and binderRpcTest - vendor_available: true, - srcs: [ "Binder.cpp", "BpBinder.cpp", @@ -200,7 +197,6 @@ cc_defaults { cc_library_headers { name: "trusty_mock_headers", - vendor_available: true, host_supported: true, export_include_dirs: [ @@ -215,6 +211,7 @@ cc_library_headers { cc_defaults { name: "trusty_mock_defaults", + host_supported: true, header_libs: [ "trusty_mock_headers", @@ -287,6 +284,14 @@ cc_defaults { cflags: [ "-DBINDER_WITH_KERNEL_IPC", ], + arch: { + // TODO(b/254713216): undefined symbol in BufferedTextOutput::getBuffer + riscv64: { + lto: { + thin: false, + }, + }, + }, } cc_library { @@ -299,6 +304,8 @@ cc_library { version_script: "libbinder.map", + // for vndbinder + vendor_available: true, vndk: { enabled: true, }, @@ -457,7 +464,6 @@ cc_library_shared { cc_library_static { name: "libbinder_tls_static", defaults: ["libbinder_tls_defaults"], - vendor_available: true, visibility: [ ":__subpackages__", ], @@ -508,6 +514,10 @@ aidl_interface { enabled: false, }, }, + visibility: [ + ":__subpackages__", + "//system/tools/aidl:__subpackages__", + ], } // TODO(b/184872979): remove once the Rust API is created. @@ -533,8 +543,10 @@ cc_library { // Do not expand the visibility. visibility: [ ":__subpackages__", - "//packages/modules/Virtualization:__subpackages__", + "//packages/modules/Virtualization/javalib/jni", + "//packages/modules/Virtualization/vm_payload", "//device/google/cuttlefish/shared/minidroid:__subpackages__", + "//system/software_defined_vehicle:__subpackages__", ], } diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp index d03326eb04..53852d88ed 100644 --- a/libs/binder/BpBinder.cpp +++ b/libs/binder/BpBinder.cpp @@ -388,7 +388,8 @@ status_t BpBinder::linkToDeath( { 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/OWNERS b/libs/binder/OWNERS index f954e74eba..bb17683a23 100644 --- a/libs/binder/OWNERS +++ b/libs/binder/OWNERS @@ -1,6 +1,4 @@ # Bug component: 32456 -ctate@google.com -hackbod@google.com maco@google.com smoreland@google.com tkjos@google.com diff --git a/libs/binder/ProcessState.cpp b/libs/binder/ProcessState.cpp index 254dda81a6..5f1f50672a 100644 --- a/libs/binder/ProcessState.cpp +++ b/libs/binder/ProcessState.cpp @@ -100,6 +100,10 @@ static void verifyNotForked(bool forked) { LOG_ALWAYS_FATAL_IF(forked, "libbinder ProcessState can not be used after fork"); } +bool ProcessState::isVndservicemanagerEnabled() { + return access("/vendor/bin/vndservicemanager", R_OK) == 0; +} + sp<ProcessState> ProcessState::init(const char *driver, bool requireDefault) { #ifdef BINDER_IPC_32BIT @@ -123,6 +127,11 @@ sp<ProcessState> ProcessState::init(const char *driver, bool requireDefault) driver = "/dev/binder"; } + if (0 == strcmp(driver, "/dev/vndbinder") && !isVndservicemanagerEnabled()) { + ALOGE("vndservicemanager is not started on this device, you can save resources/threads " + "by not initializing ProcessState with /dev/vndbinder."); + } + // we must install these before instantiating the gProcess object, // otherwise this would race with creating it, and there could be the // possibility of an invalid gProcess object forked by another thread diff --git a/libs/binder/RecordedTransaction.cpp b/libs/binder/RecordedTransaction.cpp index 2e7030437c..51b971651d 100644 --- a/libs/binder/RecordedTransaction.cpp +++ b/libs/binder/RecordedTransaction.cpp @@ -16,6 +16,7 @@ #include <android-base/file.h> #include <android-base/logging.h> +#include <android-base/scopeguard.h> #include <android-base/unique_fd.h> #include <binder/RecordedTransaction.h> #include <sys/mman.h> @@ -176,13 +177,33 @@ std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd RecordedTransaction t; ChunkDescriptor chunk; const long pageSize = sysconf(_SC_PAGE_SIZE); + struct stat fileStat; + if (fstat(fd.get(), &fileStat) != 0) { + LOG(ERROR) << "Unable to get file information"; + return std::nullopt; + } + + off_t fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR); + if (fdCurrentPosition == -1) { + LOG(ERROR) << "Invalid offset in file descriptor."; + return std::nullopt; + } do { + if (fileStat.st_size < (fdCurrentPosition + (off_t)sizeof(ChunkDescriptor))) { + LOG(ERROR) << "Not enough file remains to contain expected chunk descriptor"; + return std::nullopt; + } transaction_checksum_t checksum = 0; if (NO_ERROR != readChunkDescriptor(fd, &chunk, &checksum)) { LOG(ERROR) << "Failed to read chunk descriptor."; return std::nullopt; } - off_t fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR); + + fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR); + if (fdCurrentPosition == -1) { + LOG(ERROR) << "Invalid offset in file descriptor."; + return std::nullopt; + } off_t mmapPageAlignedStart = (fdCurrentPosition / pageSize) * pageSize; off_t mmapPayloadStartOffset = fdCurrentPosition - mmapPageAlignedStart; @@ -194,14 +215,24 @@ std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd size_t chunkPayloadSize = chunk.dataSize + PADDING8(chunk.dataSize) + sizeof(transaction_checksum_t); + if (chunkPayloadSize > (size_t)(fileStat.st_size - fdCurrentPosition)) { + LOG(ERROR) << "Chunk payload exceeds remaining file size."; + return std::nullopt; + } + if (PADDING8(chunkPayloadSize) != 0) { LOG(ERROR) << "Invalid chunk size, not aligned " << chunkPayloadSize; return std::nullopt; } - transaction_checksum_t* payloadMap = reinterpret_cast<transaction_checksum_t*>( - mmap(NULL, chunkPayloadSize + mmapPayloadStartOffset, PROT_READ, MAP_SHARED, - fd.get(), mmapPageAlignedStart)); + size_t memoryMappedSize = chunkPayloadSize + mmapPayloadStartOffset; + void* mappedMemory = + mmap(NULL, memoryMappedSize, PROT_READ, MAP_SHARED, fd.get(), mmapPageAlignedStart); + auto mmap_guard = android::base::make_scope_guard( + [mappedMemory, memoryMappedSize] { munmap(mappedMemory, memoryMappedSize); }); + + transaction_checksum_t* payloadMap = + reinterpret_cast<transaction_checksum_t*>(mappedMemory); payloadMap += mmapPayloadStartOffset / sizeof(transaction_checksum_t); // Skip chunk descriptor and required mmap // page-alignment @@ -218,7 +249,12 @@ std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd LOG(ERROR) << "Checksum failed."; return std::nullopt; } - lseek(fd.get(), chunkPayloadSize, SEEK_CUR); + + fdCurrentPosition = lseek(fd.get(), chunkPayloadSize, SEEK_CUR); + if (fdCurrentPosition == -1) { + LOG(ERROR) << "Invalid offset in file descriptor."; + return std::nullopt; + } switch (chunk.chunkType) { case HEADER_CHUNK: { @@ -255,7 +291,7 @@ std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd break; default: LOG(INFO) << "Unrecognized chunk."; - continue; + break; } } while (chunk.chunkType != END_CHUNK); diff --git a/libs/binder/RpcSession.cpp b/libs/binder/RpcSession.cpp index ce6ef2becf..233a8e4b86 100644 --- a/libs/binder/RpcSession.cpp +++ b/libs/binder/RpcSession.cpp @@ -90,16 +90,16 @@ size_t RpcSession::getMaxIncomingThreads() { return mMaxIncomingThreads; } -void RpcSession::setMaxOutgoingThreads(size_t threads) { +void RpcSession::setMaxOutgoingConnections(size_t connections) { RpcMutexLockGuard _l(mMutex); LOG_ALWAYS_FATAL_IF(mStartedSetup, "Must set max outgoing threads before setting up connections"); - mMaxOutgoingThreads = threads; + mMaxOutgoingConnections = connections; } size_t RpcSession::getMaxOutgoingThreads() { RpcMutexLockGuard _l(mMutex); - return mMaxOutgoingThreads; + return mMaxOutgoingConnections; } bool RpcSession::setProtocolVersionInternal(uint32_t version, bool checkStarted) { @@ -558,11 +558,11 @@ status_t RpcSession::setupClient(const std::function<status_t(const std::vector< return status; } - size_t outgoingThreads = std::min(numThreadsAvailable, mMaxOutgoingThreads); - ALOGI_IF(outgoingThreads != numThreadsAvailable, + size_t outgoingConnections = std::min(numThreadsAvailable, mMaxOutgoingConnections); + ALOGI_IF(outgoingConnections != numThreadsAvailable, "Server hints client to start %zu outgoing threads, but client will only start %zu " "because it is preconfigured to start at most %zu outgoing threads.", - numThreadsAvailable, outgoingThreads, mMaxOutgoingThreads); + numThreadsAvailable, outgoingConnections, mMaxOutgoingConnections); // TODO(b/189955605): we should add additional sessions dynamically // instead of all at once - the other side should be responsible for setting @@ -571,10 +571,10 @@ status_t RpcSession::setupClient(const std::function<status_t(const std::vector< // any requests at all. // we've already setup one client - LOG_RPC_DETAIL("RpcSession::setupClient() instantiating %zu outgoing (server max: %zu) and %zu " - "incoming threads", - outgoingThreads, numThreadsAvailable, mMaxIncomingThreads); - for (size_t i = 0; i + 1 < outgoingThreads; i++) { + LOG_RPC_DETAIL("RpcSession::setupClient() instantiating %zu outgoing connections (server max: " + "%zu) and %zu incoming threads", + outgoingConnections, numThreadsAvailable, mMaxIncomingThreads); + for (size_t i = 0; i + 1 < outgoingConnections; i++) { if (status_t status = connectAndInit(mId, false /*incoming*/); status != OK) return status; } @@ -932,7 +932,8 @@ status_t RpcSession::ExclusiveConnection::find(const sp<RpcSession>& session, Co (session->server() ? "This is a server session, so see RpcSession::setMaxIncomingThreads " "for the corresponding client" - : "This is a client session, so see RpcSession::setMaxOutgoingThreads " + : "This is a client session, so see " + "RpcSession::setMaxOutgoingConnections " "for this client or RpcServer::setMaxThreads for the corresponding " "server")); return WOULD_BLOCK; diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp index 1ea13f9a1c..2b0e5bae0d 100644 --- a/libs/binder/RpcState.cpp +++ b/libs/binder/RpcState.cpp @@ -557,13 +557,12 @@ status_t RpcState::transactAddress(const sp<RpcSession::RpcConnection>& connecti .parcelDataSize = static_cast<uint32_t>(data.dataSize()), }; - constexpr size_t kWaitMaxUs = 1000000; - constexpr size_t kWaitLogUs = 10000; - size_t waitUs = 0; - // Oneway calls have no sync point, so if many are sent before, whether this // is a twoway or oneway transaction, they may have filled up the socket. // So, make sure we drain them before polling + constexpr size_t kWaitMaxUs = 1000000; + constexpr size_t kWaitLogUs = 10000; + size_t waitUs = 0; iovec iovs[]{ {&command, sizeof(RpcWireHeader)}, @@ -591,8 +590,9 @@ status_t RpcState::transactAddress(const sp<RpcSession::RpcConnection>& connecti }, rpcFields->mFds.get()); status != OK) { - // TODO(b/167966510): need to undo onBinderLeaving - we know the - // refcount isn't successfully transferred. + // rpcSend calls shutdownAndWait, so all refcounts should be reset. If we ever tolerate + // errors here, then we may need to undo the binder-sent counts for the transaction as + // well as for the binder objects in the Parcel return status; } @@ -1036,8 +1036,8 @@ processTransactInternalTailCall: return DEAD_OBJECT; } - if (it->second.asyncTodo.size() != 0 && - it->second.asyncTodo.top().asyncNumber == it->second.asyncNumber) { + if (it->second.asyncTodo.size() == 0) return OK; + if (it->second.asyncTodo.top().asyncNumber == it->second.asyncNumber) { LOG_RPC_DETAIL("Found next async transaction %" PRIu64 " on %" PRIu64, it->second.asyncNumber, addr); diff --git a/libs/binder/ServiceManagerHost.cpp b/libs/binder/ServiceManagerHost.cpp index 194254ac69..2b67f030e0 100644 --- a/libs/binder/ServiceManagerHost.cpp +++ b/libs/binder/ServiceManagerHost.cpp @@ -159,8 +159,8 @@ sp<IBinder> getDeviceService(std::vector<std::string>&& serviceDispatcherArgs, LOG_ALWAYS_FATAL_IF(!forwardResult->hostPort().has_value()); auto rpcSession = RpcSession::make(); - if (options.maxOutgoingThreads.has_value()) { - rpcSession->setMaxOutgoingThreads(*options.maxOutgoingThreads); + if (options.maxOutgoingConnections.has_value()) { + rpcSession->setMaxOutgoingConnections(*options.maxOutgoingConnections); } if (status_t status = rpcSession->setupInetClient("127.0.0.1", *forwardResult->hostPort()); diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING index 04cb61f971..1488400007 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/IServiceManager.h b/libs/binder/include/binder/IServiceManager.h index c78f870153..55167a7db0 100644 --- a/libs/binder/include/binder/IServiceManager.h +++ b/libs/binder/include/binder/IServiceManager.h @@ -224,12 +224,12 @@ bool checkPermission(const String16& permission, pid_t pid, uid_t uid, // } // Resources are cleaned up when the object is destroyed. // -// For each returned binder object, at most |maxOutgoingThreads| outgoing threads are instantiated. -// Hence, only |maxOutgoingThreads| calls can be made simultaneously. Additional calls are blocked -// if there are |maxOutgoingThreads| ongoing calls. See RpcSession::setMaxOutgoingThreads. -// If |maxOutgoingThreads| is not set, default is |RpcSession::kDefaultMaxOutgoingThreads|. +// For each returned binder object, at most |maxOutgoingConnections| outgoing connections are +// instantiated, depending on how many the service on the device is configured with. +// Hence, only |maxOutgoingConnections| calls can be made simultaneously. +// See also RpcSession::setMaxOutgoingConnections. struct RpcDelegateServiceManagerOptions { - std::optional<size_t> maxOutgoingThreads; + std::optional<size_t> maxOutgoingConnections; }; sp<IServiceManager> createRpcDelegateServiceManager( const RpcDelegateServiceManagerOptions& options); diff --git a/libs/binder/include/binder/ProcessState.h b/libs/binder/include/binder/ProcessState.h index 471c9949aa..ce578e3f5c 100644 --- a/libs/binder/include/binder/ProcessState.h +++ b/libs/binder/include/binder/ProcessState.h @@ -38,6 +38,8 @@ public: static sp<ProcessState> self(); static sp<ProcessState> selfOrNull(); + static bool isVndservicemanagerEnabled(); + /* initWithDriver() can be used to configure libbinder to use * a different binder driver dev node. It must be called *before* * any call to ProcessState::self(). The default is /dev/vndbinder diff --git a/libs/binder/include/binder/RpcServer.h b/libs/binder/include/binder/RpcServer.h index 25193a3428..1001b64ede 100644 --- a/libs/binder/include/binder/RpcServer.h +++ b/libs/binder/include/binder/RpcServer.h @@ -119,7 +119,10 @@ public: [[nodiscard]] status_t setupExternalServer(base::unique_fd serverFd); /** - * This must be called before adding a client session. + * This must be called before adding a client session. This corresponds + * to the number of incoming connections to RpcSession objects in the + * server, which will correspond to the number of outgoing connections + * in client RpcSession objects. * * If this is not specified, this will be a single-threaded server. * diff --git a/libs/binder/include/binder/RpcSession.h b/libs/binder/include/binder/RpcSession.h index 40faf2c3b1..0750ccfc3f 100644 --- a/libs/binder/include/binder/RpcSession.h +++ b/libs/binder/include/binder/RpcSession.h @@ -54,8 +54,6 @@ constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_ */ class RpcSession final : public virtual RefBase { public: - static constexpr size_t kDefaultMaxOutgoingThreads = 10; - // Create an RpcSession with default configuration (raw sockets). static sp<RpcSession> make(); @@ -67,26 +65,30 @@ public: /** * Set the maximum number of incoming threads allowed to be made (for things like callbacks). * By default, this is 0. This must be called before setting up this connection as a client. - * Server sessions will inherits this value from RpcServer. + * Server sessions will inherits this value from RpcServer. Each thread will serve a + * connection to the remote RpcSession. * * If this is called, 'shutdown' on this session must also be called. * Otherwise, a threadpool will leak. * - * TODO(b/189955605): start these dynamically + * TODO(b/189955605): start these lazily - currently all are started */ void setMaxIncomingThreads(size_t threads); size_t getMaxIncomingThreads(); /** - * Set the maximum number of outgoing threads allowed to be made. - * By default, this is |kDefaultMaxOutgoingThreads|. This must be called before setting up this - * connection as a client. + * Set the maximum number of outgoing connections allowed to be made. + * By default, this is |kDefaultMaxOutgoingConnections|. This must be called before setting up + * this connection as a client. * - * This limits the number of outgoing threads on top of the remote peer setting. This RpcSession - * will only instantiate |min(maxOutgoingThreads, remoteMaxThreads)| outgoing threads, where - * |remoteMaxThreads| can be retrieved from the remote peer via |getRemoteMaxThreads()|. + * For an RpcSession client, if you are connecting to a server which starts N threads, + * then this must be set to >= N. If you set the maximum number of outgoing connections + * to 1, but the server requests 10, then it would be considered an error. If you set a + * maximum number of connections to 10, and the server requests 1, then only 1 will be + * created. This API is used to limit the amount of resources a server can request you + * create. */ - void setMaxOutgoingThreads(size_t threads); + void setMaxOutgoingConnections(size_t connections); size_t getMaxOutgoingThreads(); /** @@ -219,6 +221,8 @@ private: friend RpcState; explicit RpcSession(std::unique_ptr<RpcTransportCtx> ctx); + static constexpr size_t kDefaultMaxOutgoingConnections = 10; + // internal version of setProtocolVersion that // optionally skips the mStartedSetup check [[nodiscard]] bool setProtocolVersionInternal(uint32_t version, bool checkStarted); @@ -368,7 +372,7 @@ private: bool mStartedSetup = false; size_t mMaxIncomingThreads = 0; - size_t mMaxOutgoingThreads = kDefaultMaxOutgoingThreads; + size_t mMaxOutgoingConnections = kDefaultMaxOutgoingConnections; std::optional<uint32_t> mProtocolVersion; FileDescriptorTransportMode mFileDescriptorTransportMode = FileDescriptorTransportMode::NONE; diff --git a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp index 3ebbed6965..a157792156 100644 --- a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp +++ b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp @@ -57,6 +57,15 @@ enum class ARpcSession_FileDescriptorTransportMode { // could not be started. [[nodiscard]] ARpcServer* ARpcServer_newUnixDomainBootstrap(AIBinder* service, int bootstrapFd); +// Starts an RPC server on a given IP address+port and a given IBinder object. +// Returns an opaque handle to the running server instance, or null if the server +// could not be started. +// Does not take ownership of `service`. +// Returns an opaque handle to the running service instance, or null if the server +// could not be started. +[[nodiscard]] ARpcServer* ARpcServer_newInet(AIBinder* service, const char* address, + unsigned int port); + // Sets the list of supported file descriptor transport modes of this RPC server. void ARpcServer_setSupportedFileDescriptorTransportModes( ARpcServer* handle, @@ -98,6 +107,10 @@ AIBinder* ARpcSession_setupUnixDomainClient(ARpcSession* session, const char* na AIBinder* ARpcSession_setupUnixDomainBootstrapClient(ARpcSession* session, int bootstrapFd); +// Connects to an RPC server over an INET socket at a given IP address on a given port. +// Returns the root Binder object of the server. +AIBinder* ARpcSession_setupInet(ARpcSession* session, const char* address, unsigned int port); + // Connects to an RPC server with preconnected file descriptors. // // requestFd should connect to the server and return a valid file descriptor, or @@ -113,11 +126,11 @@ AIBinder* ARpcSession_setupPreconnectedClient(ARpcSession* session, void ARpcSession_setFileDescriptorTransportMode(ARpcSession* session, ARpcSession_FileDescriptorTransportMode mode); -// Sets the maximum number of incoming threads. +// Sets the maximum number of incoming threads, to service connections. void ARpcSession_setMaxIncomingThreads(ARpcSession* session, size_t threads); -// Sets the maximum number of outgoing threads. -void ARpcSession_setMaxOutgoingThreads(ARpcSession* session, size_t threads); +// Sets the maximum number of outgoing connections. +void ARpcSession_setMaxOutgoingConnections(ARpcSession* session, size_t connections); // Decrements the refcount of the underlying RpcSession object. void ARpcSession_free(ARpcSession* session); diff --git a/libs/binder/libbinder_rpc_unstable.cpp b/libs/binder/libbinder_rpc_unstable.cpp index e7943ddf2f..a167f235d5 100644 --- a/libs/binder/libbinder_rpc_unstable.cpp +++ b/libs/binder/libbinder_rpc_unstable.cpp @@ -145,6 +145,17 @@ ARpcServer* ARpcServer_newUnixDomainBootstrap(AIBinder* service, int bootstrapFd return createObjectHandle<ARpcServer>(server); } +ARpcServer* ARpcServer_newInet(AIBinder* service, const char* address, unsigned int port) { + auto server = RpcServer::make(); + if (status_t status = server->setupInetServer(address, port, nullptr); status != OK) { + LOG(ERROR) << "Failed to set up inet RPC server with address " << address << " and port " + << port << " error: " << statusToString(status).c_str(); + return nullptr; + } + server->setRootObject(AIBinder_toPlatformBinder(service)); + return createObjectHandle<ARpcServer>(server); +} + void ARpcServer_setSupportedFileDescriptorTransportModes( ARpcServer* handle, const ARpcSession_FileDescriptorTransportMode modes[], size_t modes_len) { @@ -222,6 +233,16 @@ AIBinder* ARpcSession_setupUnixDomainBootstrapClient(ARpcSession* handle, int bo return AIBinder_fromPlatformBinder(session->getRootObject()); } +AIBinder* ARpcSession_setupInet(ARpcSession* handle, const char* address, unsigned int port) { + auto session = handleToStrongPointer<RpcSession>(handle); + if (status_t status = session->setupInetClient(address, port); status != OK) { + LOG(ERROR) << "Failed to set up inet RPC client with address " << address << " and port " + << port << " error: " << statusToString(status).c_str(); + return nullptr; + } + return AIBinder_fromPlatformBinder(session->getRootObject()); +} + AIBinder* ARpcSession_setupPreconnectedClient(ARpcSession* handle, int (*requestFd)(void* param), void* param) { auto session = handleToStrongPointer<RpcSession>(handle); @@ -244,8 +265,8 @@ void ARpcSession_setMaxIncomingThreads(ARpcSession* handle, size_t threads) { session->setMaxIncomingThreads(threads); } -void ARpcSession_setMaxOutgoingThreads(ARpcSession* handle, size_t threads) { +void ARpcSession_setMaxOutgoingConnections(ARpcSession* handle, size_t connections) { auto session = handleToStrongPointer<RpcSession>(handle); - session->setMaxOutgoingThreads(threads); + session->setMaxOutgoingConnections(connections); } } diff --git a/libs/binder/libbinder_rpc_unstable.map.txt b/libs/binder/libbinder_rpc_unstable.map.txt index 1bc2416533..63679c28d0 100644 --- a/libs/binder/libbinder_rpc_unstable.map.txt +++ b/libs/binder/libbinder_rpc_unstable.map.txt @@ -2,6 +2,7 @@ LIBBINDER_RPC_UNSTABLE_SHIM { # platform-only global: ARpcServer_free; ARpcServer_join; + ARpcServer_newInet; ARpcServer_newInitUnixDomain; ARpcServer_newVsock; ARpcServer_shutdown; diff --git a/libs/binder/ndk/include_ndk/android/binder_parcel.h b/libs/binder/ndk/include_ndk/android/binder_parcel.h index f68612c3ba..d833b837d0 100644 --- a/libs/binder/ndk/include_ndk/android/binder_parcel.h +++ b/libs/binder/ndk/include_ndk/android/binder_parcel.h @@ -26,11 +26,11 @@ #pragma once +#include <android/binder_status.h> #include <stdbool.h> #include <stddef.h> #include <sys/cdefs.h> - -#include <android/binder_status.h> +#include <uchar.h> struct AIBinder; typedef struct AIBinder AIBinder; diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h index ad4188f499..43159d8ba2 100644 --- a/libs/binder/ndk/include_platform/android/binder_manager.h +++ b/libs/binder/ndk/include_platform/android/binder_manager.h @@ -22,6 +22,16 @@ __BEGIN_DECLS +enum AServiceManager_AddServiceFlag : uint32_t { + /** + * This allows processes with AID_ISOLATED to get the binder of the service added. + * + * Services with methods that perform file IO, web socket creation or ways to egress data must + * not be added with this flag for privacy concerns. + */ + ADD_SERVICE_ALLOW_ISOLATED = 1, +}; + /** * This registers the service with the default service manager under this instance name. This does * not take ownership of binder. @@ -38,6 +48,23 @@ __attribute__((warn_unused_result)) binder_exception_t AServiceManager_addServic 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 flag an AServiceManager_AddServiceFlag enum to denote how the service should be added. + * + * \return EX_NONE on success. + */ +__attribute__((warn_unused_result)) binder_exception_t AServiceManager_addServiceWithFlag( + AIBinder* binder, const char* instance, const AServiceManager_AddServiceFlag flag) + __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 54e46287a9..1078fb2b16 100644 --- a/libs/binder/ndk/libbinder_ndk.map.txt +++ b/libs/binder/ndk/libbinder_ndk.map.txt @@ -158,6 +158,7 @@ LIBBINDER_NDK34 { # introduced=UpsideDownCake AServiceManager_getUpdatableApexName; # systemapi AServiceManager_registerForServiceNotifications; # systemapi llndk AServiceManager_NotificationRegistration_delete; # systemapi llndk + AServiceManager_addServiceWithFlag; # systemapi llndk }; LIBBINDER_NDK_PLATFORM { diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp index e107c83d14..84da459454 100644 --- a/libs/binder/ndk/service_manager.cpp +++ b/libs/binder/ndk/service_manager.cpp @@ -41,6 +41,20 @@ binder_exception_t AServiceManager_addService(AIBinder* binder, const char* inst status_t exception = sm->addService(String16(instance), binder->getBinder()); return PruneException(exception); } + +binder_exception_t AServiceManager_addServiceWithFlag(AIBinder* binder, const char* instance, + const AServiceManager_AddServiceFlag flag) { + if (binder == nullptr || instance == nullptr) { + return EX_ILLEGAL_ARGUMENT; + } + + sp<IServiceManager> sm = defaultServiceManager(); + + bool allowIsolated = flag & AServiceManager_AddServiceFlag::ADD_SERVICE_ALLOW_ISOLATED; + 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/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp index 5b2532ab4e..882f1d66b7 100644 --- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp +++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp @@ -52,7 +52,7 @@ constexpr char kLazyBinderNdkUnitTestService[] = "LazyBinderNdkUnitTest"; constexpr char kForcePersistNdkUnitTestService[] = "ForcePersistNdkUnitTestService"; constexpr char kActiveServicesNdkUnitTestService[] = "ActiveServicesNdkUnitTestService"; -constexpr unsigned int kShutdownWaitTime = 10; +constexpr unsigned int kShutdownWaitTime = 11; constexpr uint64_t kContextTestValue = 0xb4e42fb4d9a1d715; class MyTestFoo : public IFoo { diff --git a/libs/binder/rust/Android.bp b/libs/binder/rust/Android.bp index afd414a7cb..d36ebac109 100644 --- a/libs/binder/rust/Android.bp +++ b/libs/binder/rust/Android.bp @@ -21,6 +21,7 @@ rust_library { ], host_supported: true, vendor_available: true, + product_available: true, target: { darwin: { enabled: false, @@ -72,6 +73,7 @@ rust_library { ], host_supported: true, vendor_available: true, + product_available: true, target: { darwin: { enabled: false, @@ -129,6 +131,7 @@ rust_bindgen { ], host_supported: true, vendor_available: true, + product_available: true, // Currently necessary for host builds // TODO(b/31559095): bionic on host should define this diff --git a/libs/binder/rust/rpcbinder/Android.bp b/libs/binder/rust/rpcbinder/Android.bp index afb73e920f..0067a20484 100644 --- a/libs/binder/rust/rpcbinder/Android.bp +++ b/libs/binder/rust/rpcbinder/Android.bp @@ -23,7 +23,13 @@ rust_library { "liblibc", "liblog_rust", ], + visibility: [ + "//device/google/cuttlefish/shared/minidroid/sample", + "//packages/modules/Virtualization:__subpackages__", + "//system/software_defined_vehicle:__subpackages__", + ], apex_available: [ + "//apex_available:platform", "com.android.compos", "com.android.uwb", "com.android.virt", @@ -51,6 +57,7 @@ rust_library { "libutils", ], apex_available: [ + "//apex_available:platform", "com.android.compos", "com.android.uwb", "com.android.virt", @@ -84,6 +91,7 @@ rust_bindgen { "libutils", ], apex_available: [ + "//apex_available:platform", "com.android.compos", "com.android.uwb", "com.android.virt", diff --git a/libs/binder/rust/rpcbinder/src/server.rs b/libs/binder/rust/rpcbinder/src/server.rs index 761b306a1e..c87876ac15 100644 --- a/libs/binder/rust/rpcbinder/src/server.rs +++ b/libs/binder/rust/rpcbinder/src/server.rs @@ -102,6 +102,29 @@ impl RpcServer { } } + /// Creates a binder RPC server, serving the supplied binder service implementation on the given + /// IP address and port. + pub fn new_inet(mut service: SpIBinder, address: &str, port: u32) -> Result<RpcServer, Error> { + let address = match CString::new(address) { + Ok(s) => s, + Err(e) => { + log::error!("Cannot convert {} to CString. Error: {:?}", address, e); + return Err(Error::from(ErrorKind::InvalidInput)); + } + }; + let service = service.as_native_mut(); + + // SAFETY: Service ownership is transferring to the server and won't be valid afterward. + // Plus the binder objects are threadsafe. + unsafe { + Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newInet( + service, + address.as_ptr(), + port, + )) + } + } + unsafe fn checked_from_ptr(ptr: *mut ARpcServer) -> Result<RpcServer, Error> { if ptr.is_null() { return Err(Error::new(ErrorKind::Other, "Failed to start server")); diff --git a/libs/binder/rust/rpcbinder/src/session.rs b/libs/binder/rust/rpcbinder/src/session.rs index 62fedb1ffa..28c5390665 100644 --- a/libs/binder/rust/rpcbinder/src/session.rs +++ b/libs/binder/rust/rpcbinder/src/session.rs @@ -75,11 +75,14 @@ impl RpcSessionRef { }; } - /// Sets the maximum number of outgoing threads. - pub fn set_max_outgoing_threads(&self, threads: usize) { + /// Sets the maximum number of outgoing connections. + pub fn set_max_outgoing_connections(&self, connections: usize) { // SAFETY - Only passes the 'self' pointer as an opaque handle. unsafe { - binder_rpc_unstable_bindgen::ARpcSession_setMaxOutgoingThreads(self.as_ptr(), threads) + binder_rpc_unstable_bindgen::ARpcSession_setMaxOutgoingConnections( + self.as_ptr(), + connections, + ) }; } @@ -144,6 +147,32 @@ impl RpcSessionRef { Self::get_interface(service) } + /// Connects to an RPC Binder server over inet socket at the given address and port. + pub fn setup_inet_client<T: FromIBinder + ?Sized>( + &self, + address: &str, + port: u32, + ) -> Result<Strong<T>, StatusCode> { + let address = match CString::new(address) { + Ok(s) => s, + Err(e) => { + log::error!("Cannot convert {} to CString. Error: {:?}", address, e); + return Err(StatusCode::BAD_VALUE); + } + }; + + // SAFETY: AIBinder returned by ARpcSession_setupInet has correct reference + // count, and the ownership can safely be taken by new_spibinder. + let service = unsafe { + new_spibinder(binder_rpc_unstable_bindgen::ARpcSession_setupInet( + self.as_ptr(), + address.as_ptr(), + port, + )) + }; + Self::get_interface(service) + } + /// Connects to an RPC Binder server, using the given callback to get (and /// take ownership of) file descriptors already connected to it. pub fn setup_preconnected_client<T: FromIBinder + ?Sized>( diff --git a/libs/binder/rust/src/native.rs b/libs/binder/rust/src/native.rs index 6f686fbd93..5557168055 100644 --- a/libs/binder/rust/src/native.rs +++ b/libs/binder/rust/src/native.rs @@ -209,8 +209,8 @@ impl<T: Remotable> Binder<T> { } /// Mark this binder object with local stability, which is vendor if we are - /// building for the VNDK and system otherwise. - #[cfg(any(vendor_ndk, android_vndk))] + /// building for android_vendor and system otherwise. + #[cfg(android_vendor)] fn mark_local_stability(&mut self) { unsafe { // Safety: Self always contains a valid `AIBinder` pointer, so @@ -220,8 +220,8 @@ impl<T: Remotable> Binder<T> { } /// Mark this binder object with local stability, which is vendor if we are - /// building for the VNDK and system otherwise. - #[cfg(not(any(vendor_ndk, android_vndk)))] + /// building for android_vendor and system otherwise. + #[cfg(not(android_vendor))] fn mark_local_stability(&mut self) { unsafe { // Safety: Self always contains a valid `AIBinder` pointer, so diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp index 7006f87314..0f0d64aea8 100644 --- a/libs/binder/tests/Android.bp +++ b/libs/binder/tests/Android.bp @@ -138,7 +138,6 @@ cc_test { aidl_interface { name: "binderRpcTestIface", - vendor_available: true, host_supported: true, unstable: true, srcs: [ @@ -159,7 +158,6 @@ aidl_interface { cc_library_static { name: "libbinder_tls_test_utils", - vendor_available: true, host_supported: true, target: { darwin: { @@ -213,7 +211,6 @@ cc_defaults { defaults: [ "binderRpcTest_common_defaults", ], - vendor_available: true, gtest: false, auto_gen_config: false, srcs: [ @@ -224,18 +221,10 @@ cc_defaults { cc_defaults { name: "binderRpcTest_defaults", - vendor_available: true, target: { android: { test_suites: ["vts"], }, - - vendor: { - shared_libs: [ - "libbinder_trusty", - "libtrusty", - ], - }, }, defaults: [ "binderRpcTest_common_defaults", @@ -370,6 +359,31 @@ cc_binary { ], } +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 +396,7 @@ cc_test { required: [ "libbinder_on_trusty_mock", "binderRpcTestService_on_trusty_mock", + "binderRpcTest_on_trusty_mock", ], } diff --git a/libs/binder/tests/binderHostDeviceTest.cpp b/libs/binder/tests/binderHostDeviceTest.cpp index 464da60dde..77a5fa8d65 100644 --- a/libs/binder/tests/binderHostDeviceTest.cpp +++ b/libs/binder/tests/binderHostDeviceTest.cpp @@ -66,7 +66,7 @@ MATCHER_P(StatusEq, expected, (negation ? "not " : "") + statusToString(expected void initHostRpcServiceManagerOnce() { static std::once_flag gSmOnce; std::call_once(gSmOnce, [] { - setDefaultServiceManager(createRpcDelegateServiceManager({.maxOutgoingThreads = 1})); + setDefaultServiceManager(createRpcDelegateServiceManager({.maxOutgoingConnections = 1})); }); } diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp index 955c650205..8974ad745d 100644 --- a/libs/binder/tests/binderLibTest.cpp +++ b/libs/binder/tests/binderLibTest.cpp @@ -507,7 +507,13 @@ TEST_F(BinderLibTest, Freeze) { } EXPECT_EQ(-EAGAIN, IPCThreadState::self()->freeze(pid, true, 0)); - EXPECT_EQ(-EAGAIN, IPCThreadState::self()->freeze(pid, true, 0)); + + // b/268232063 - succeeds ~0.08% of the time + { + auto ret = IPCThreadState::self()->freeze(pid, true, 0); + EXPECT_TRUE(ret == -EAGAIN || ret == OK); + } + EXPECT_EQ(NO_ERROR, IPCThreadState::self()->freeze(pid, true, 1000)); EXPECT_EQ(FAILED_TRANSACTION, m_server->transact(BINDER_LIB_TEST_NOP_TRANSACTION, data, &reply)); @@ -1370,7 +1376,7 @@ TEST_F(BinderLibTest, ThreadPoolAvailableThreads) { })); } - data.writeInt32(100); + data.writeInt32(500); // Give a chance for all threads to be used EXPECT_THAT(server->transact(BINDER_LIB_TEST_UNLOCK_AFTER_MS, data, &reply), NO_ERROR); diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp index 84c93ddc30..b520f4ffc9 100644 --- a/libs/binder/tests/binderRpcTest.cpp +++ b/libs/binder/tests/binderRpcTest.cpp @@ -237,9 +237,13 @@ std::string BinderRpc::PrintParamInfo(const testing::TestParamInfo<ParamType>& i std::to_string(clientVersion) + "_serverV" + std::to_string(serverVersion); if (singleThreaded) { ret += "_single_threaded"; + } else { + ret += "_multi_threaded"; } if (noKernel) { ret += "_no_kernel"; + } else { + ret += "_with_kernel"; } return ret; } @@ -350,7 +354,7 @@ std::unique_ptr<ProcessSession> BinderRpc::createRpcTestSocketServerProcessEtc( for (const auto& session : sessions) { CHECK(session->setProtocolVersion(clientVersion)); session->setMaxIncomingThreads(options.numIncomingConnections); - session->setMaxOutgoingThreads(options.numOutgoingConnections); + session->setMaxOutgoingConnections(options.numOutgoingConnections); session->setFileDescriptorTransportMode(options.clientFileDescriptorTransportMode); switch (socketType) { @@ -435,8 +439,7 @@ TEST_P(BinderRpc, ThreadPoolGreaterThanEqualRequested) { for (auto& t : ts) t.join(); } -static void testThreadPoolOverSaturated(sp<IBinderRpcTest> iface, size_t numCalls, - size_t sleepMs = 500) { +static void testThreadPoolOverSaturated(sp<IBinderRpcTest> iface, size_t numCalls, size_t sleepMs) { size_t epochMsBefore = epochMillis(); std::vector<std::thread> ts; @@ -462,7 +465,7 @@ TEST_P(BinderRpc, ThreadPoolOverSaturated) { constexpr size_t kNumThreads = 10; constexpr size_t kNumCalls = kNumThreads + 3; auto proc = createRpcTestSocketServerProcess({.numThreads = kNumThreads}); - testThreadPoolOverSaturated(proc.rootIface, kNumCalls); + testThreadPoolOverSaturated(proc.rootIface, kNumCalls, 250 /*ms*/); } TEST_P(BinderRpc, ThreadPoolLimitOutgoing) { @@ -475,7 +478,7 @@ TEST_P(BinderRpc, ThreadPoolLimitOutgoing) { constexpr size_t kNumCalls = kNumOutgoingConnections + 3; auto proc = createRpcTestSocketServerProcess( {.numThreads = kNumThreads, .numOutgoingConnections = kNumOutgoingConnections}); - testThreadPoolOverSaturated(proc.rootIface, kNumCalls); + testThreadPoolOverSaturated(proc.rootIface, kNumCalls, 250 /*ms*/); } TEST_P(BinderRpc, ThreadingStressTest) { @@ -483,9 +486,9 @@ TEST_P(BinderRpc, ThreadingStressTest) { GTEST_SKIP() << "This test requires multiple threads"; } - constexpr size_t kNumClientThreads = 10; - constexpr size_t kNumServerThreads = 10; - constexpr size_t kNumCalls = 100; + constexpr size_t kNumClientThreads = 5; + constexpr size_t kNumServerThreads = 5; + constexpr size_t kNumCalls = 50; auto proc = createRpcTestSocketServerProcess({.numThreads = kNumServerThreads}); @@ -544,6 +547,8 @@ TEST_P(BinderRpc, OnewayCallQueueingWithFds) { GTEST_SKIP() << "This test requires multiple threads"; } + constexpr size_t kNumServerThreads = 3; + // This test forces a oneway transaction to be queued by issuing two // `blockingSendFdOneway` calls, then drains the queue by issuing two // `blockingRecvFd` calls. @@ -552,7 +557,7 @@ TEST_P(BinderRpc, OnewayCallQueueingWithFds) { // https://developer.android.com/reference/android/os/IBinder#FLAG_ONEWAY auto proc = createRpcTestSocketServerProcess({ - .numThreads = 3, + .numThreads = kNumServerThreads, .clientFileDescriptorTransportMode = RpcSession::FileDescriptorTransportMode::UNIX, .serverSupportedFileDescriptorTransportModes = {RpcSession::FileDescriptorTransportMode::UNIX}, @@ -573,6 +578,8 @@ TEST_P(BinderRpc, OnewayCallQueueingWithFds) { EXPECT_OK(proc.rootIface->blockingRecvFd(&fdB)); CHECK(android::base::ReadFdToString(fdB.get(), &result)); EXPECT_EQ(result, "b"); + + saturateThreadPool(kNumServerThreads, proc.rootIface); } TEST_P(BinderRpc, OnewayCallQueueing) { diff --git a/libs/binder/tests/binderRpcTestService.cpp b/libs/binder/tests/binderRpcTestService.cpp index 714f0636f9..a27bd2f2e6 100644 --- a/libs/binder/tests/binderRpcTestService.cpp +++ b/libs/binder/tests/binderRpcTestService.cpp @@ -85,7 +85,9 @@ public: } }; -int main(int argc, const char* argv[]) { +int main(int argc, char* argv[]) { + android::base::InitLogging(argv, android::base::StderrLogger, android::base::DefaultAborter); + LOG_ALWAYS_FATAL_IF(argc != 3, "Invalid number of arguments: %d", argc); base::unique_fd writeEnd(atoi(argv[1])); base::unique_fd readEnd(atoi(argv[2])); diff --git a/libs/binder/tests/binderRpcTestTrusty.cpp b/libs/binder/tests/binderRpcTestTrusty.cpp new file mode 100644 index 0000000000..63b56a3a64 --- /dev/null +++ b/libs/binder/tests/binderRpcTestTrusty.cpp @@ -0,0 +1,109 @@ +/* + * 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"; + } else { + ret += "_multi_threaded"; + } + if (noKernel) { + ret += "_no_kernel"; + } else { + ret += "_with_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->setMaxOutgoingConnections(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 2249e5ca0a..11a22b0fb6 100644 --- a/libs/binder/tests/binderRpcUniversalTests.cpp +++ b/libs/binder/tests/binderRpcUniversalTests.cpp @@ -386,11 +386,11 @@ TEST_P(BinderRpc, SameBinderEqualityWeak) { 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 @@ TEST_P(BinderRpc, SingleSession) { 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 @@ TEST_P(BinderRpc, ManySessions) { 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 @@ TEST_P(BinderRpc, Callbacks) { 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/tests/unit_fuzzers/Android.bp b/libs/binder/tests/unit_fuzzers/Android.bp index 8ea948cc15..a88158299e 100644 --- a/libs/binder/tests/unit_fuzzers/Android.bp +++ b/libs/binder/tests/unit_fuzzers/Android.bp @@ -104,3 +104,42 @@ cc_fuzz { defaults: ["binder_fuzz_defaults"], srcs: ["MemoryDealerFuzz.cpp"], } + +cc_fuzz { + name: "binder_recordedTransactionFileFuzz", + defaults: ["binder_fuzz_defaults"], + srcs: ["RecordedTransactionFileFuzz.cpp"], + corpus: [ + "recorded_transaction_corpus/*", + ], +} + +cc_fuzz { + name: "binder_recordedTransactionFuzz", + defaults: ["binder_fuzz_defaults"], + srcs: ["RecordedTransactionFuzz.cpp"], + target: { + android: { + shared_libs: [ + "libcutils", + "libutils", + "libbase", + "libbinder", + ], + static_libs: ["libbinder_random_parcel"], + }, + host: { + static_libs: [ + "libcutils", + "liblog", + "libutils", + "libbase", + "libbinder", + "libbinder_random_parcel", + ], + }, + darwin: { + enabled: false, + }, + }, +} diff --git a/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp b/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp new file mode 100644 index 0000000000..73790fae49 --- /dev/null +++ b/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp @@ -0,0 +1,45 @@ +/* + * 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-base/macros.h> +#include <binder/RecordedTransaction.h> +#include <filesystem> + +#include "fuzzer/FuzzedDataProvider.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + std::FILE* intermediateFile = std::tmpfile(); + fwrite(data, sizeof(uint8_t), size, intermediateFile); + rewind(intermediateFile); + int fileNumber = fileno(intermediateFile); + + android::base::unique_fd fd(fileNumber); + + auto transaction = android::binder::debug::RecordedTransaction::fromFile(fd); + + std::fclose(intermediateFile); + + if (transaction.has_value()) { + intermediateFile = std::tmpfile(); + + android::base::unique_fd fdForWriting(fileno(intermediateFile)); + auto writeStatus ATTRIBUTE_UNUSED = transaction.value().dumpToFile(fdForWriting); + + std::fclose(intermediateFile); + } + + return 0; +} diff --git a/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp b/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp new file mode 100644 index 0000000000..943fb9f285 --- /dev/null +++ b/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp @@ -0,0 +1,64 @@ +/* + * 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-base/macros.h> +#include <binder/RecordedTransaction.h> +#include <fuzzbinder/random_parcel.h> +#include <filesystem> +#include <string> + +#include "fuzzer/FuzzedDataProvider.h" + +using android::fillRandomParcel; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FuzzedDataProvider provider = FuzzedDataProvider(data, size); + + android::String16 interfaceName = + android::String16(provider.ConsumeRandomLengthString().c_str()); + + uint32_t code = provider.ConsumeIntegral<uint32_t>(); + uint32_t flags = provider.ConsumeIntegral<uint32_t>(); + time_t sec = provider.ConsumeIntegral<time_t>(); + long nsec = provider.ConsumeIntegral<long>(); + timespec timestamp = {.tv_sec = sec, .tv_nsec = nsec}; + android::status_t transactionStatus = provider.ConsumeIntegral<android::status_t>(); + + std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>( + provider.ConsumeIntegralInRange<size_t>(0, provider.remaining_bytes())); + + // same options so that FDs and binders could be shared in both Parcels + android::RandomParcelOptions options; + + android::Parcel p0, p1; + fillRandomParcel(&p0, FuzzedDataProvider(bytes.data(), bytes.size()), &options); + fillRandomParcel(&p1, std::move(provider), &options); + + auto transaction = + android::binder::debug::RecordedTransaction::fromDetails(interfaceName, code, flags, + timestamp, p0, p1, + transactionStatus); + + if (transaction.has_value()) { + std::FILE* intermediateFile = std::tmpfile(); + android::base::unique_fd fdForWriting(fileno(intermediateFile)); + auto writeStatus ATTRIBUTE_UNUSED = transaction.value().dumpToFile(fdForWriting); + + std::fclose(intermediateFile); + } + + return 0; +} diff --git a/libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/power_recording b/libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/power_recording Binary files differnew file mode 100644 index 0000000000..79442078c2 --- /dev/null +++ b/libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/power_recording diff --git a/libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/recorded_binder_transaction b/libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/recorded_binder_transaction Binary files differnew file mode 100644 index 0000000000..658addbed5 --- /dev/null +++ b/libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/recorded_binder_transaction diff --git a/libs/binder/trusty/binderRpcTest/manifest.json b/libs/binder/trusty/binderRpcTest/manifest.json new file mode 100644 index 0000000000..d8b080f0d4 --- /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 0000000000..ae3949246d --- /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 0000000000..d0a1fbca49 --- /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/trusty-gtest.h b/libs/binder/trusty/include_mock/trusty-gtest.h new file mode 100644 index 0000000000..046b403553 --- /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 43ab84a424..db044c2c04 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 2f5a7f479b..13001219d4 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/binderdebug/include/binderdebug/BinderDebug.h b/libs/binderdebug/include/binderdebug/BinderDebug.h index dfd5a7cc3e..6ce8edfc7c 100644 --- a/libs/binderdebug/include/binderdebug/BinderDebug.h +++ b/libs/binderdebug/include/binderdebug/BinderDebug.h @@ -15,6 +15,8 @@ */ #pragma once +#include <utils/Errors.h> + #include <map> #include <vector> diff --git a/libs/fakeservicemanager/Android.bp b/libs/fakeservicemanager/Android.bp index 29924ff500..96dcce11ea 100644 --- a/libs/fakeservicemanager/Android.bp +++ b/libs/fakeservicemanager/Android.bp @@ -11,7 +11,7 @@ cc_defaults { name: "fakeservicemanager_defaults", host_supported: true, srcs: [ - "ServiceManager.cpp", + "FakeServiceManager.cpp", ], shared_libs: [ @@ -28,7 +28,7 @@ cc_defaults { cc_library { name: "libfakeservicemanager", defaults: ["fakeservicemanager_defaults"], - export_include_dirs: ["include/fakeservicemanager"], + export_include_dirs: ["include"], } cc_test_host { @@ -38,5 +38,5 @@ cc_test_host { "test_sm.cpp", ], static_libs: ["libgmock"], - local_include_dirs: ["include/fakeservicemanager"], + local_include_dirs: ["include"], } diff --git a/libs/fakeservicemanager/ServiceManager.cpp b/libs/fakeservicemanager/FakeServiceManager.cpp index 1109ad8594..3272bbc1aa 100644 --- a/libs/fakeservicemanager/ServiceManager.cpp +++ b/libs/fakeservicemanager/FakeServiceManager.cpp @@ -14,18 +14,18 @@ * limitations under the License. */ -#include "ServiceManager.h" +#include "fakeservicemanager/FakeServiceManager.h" namespace android { -ServiceManager::ServiceManager() {} +FakeServiceManager::FakeServiceManager() {} -sp<IBinder> ServiceManager::getService( const String16& name) const { +sp<IBinder> FakeServiceManager::getService( const String16& name) const { // Servicemanager is single-threaded and cannot block. This method exists for legacy reasons. return checkService(name); } -sp<IBinder> ServiceManager::checkService( const String16& name) const { +sp<IBinder> FakeServiceManager::checkService( const String16& name) const { auto it = mNameToService.find(name); if (it == mNameToService.end()) { return nullptr; @@ -33,7 +33,7 @@ sp<IBinder> ServiceManager::checkService( const String16& name) const { return it->second; } -status_t ServiceManager::addService(const String16& name, const sp<IBinder>& service, +status_t FakeServiceManager::addService(const String16& name, const sp<IBinder>& service, bool /*allowIsolated*/, int /*dumpsysFlags*/) { if (service == nullptr) { @@ -43,7 +43,7 @@ status_t ServiceManager::addService(const String16& name, const sp<IBinder>& ser return NO_ERROR; } -Vector<String16> ServiceManager::listServices(int /*dumpsysFlags*/) { +Vector<String16> FakeServiceManager::listServices(int /*dumpsysFlags*/) { Vector<String16> services; for (auto const& [name, service] : mNameToService) { (void) service; @@ -52,19 +52,19 @@ Vector<String16> ServiceManager::listServices(int /*dumpsysFlags*/) { return services; } -IBinder* ServiceManager::onAsBinder() { +IBinder* FakeServiceManager::onAsBinder() { return nullptr; } -sp<IBinder> ServiceManager::waitForService(const String16& name) { +sp<IBinder> FakeServiceManager::waitForService(const String16& name) { return checkService(name); } -bool ServiceManager::isDeclared(const String16& name) { +bool FakeServiceManager::isDeclared(const String16& name) { return mNameToService.find(name) != mNameToService.end(); } -Vector<String16> ServiceManager::getDeclaredInstances(const String16& name) { +Vector<String16> FakeServiceManager::getDeclaredInstances(const String16& name) { Vector<String16> out; const String16 prefix = name + String16("/"); for (const auto& [registeredName, service] : mNameToService) { @@ -76,38 +76,38 @@ Vector<String16> ServiceManager::getDeclaredInstances(const String16& name) { return out; } -std::optional<String16> ServiceManager::updatableViaApex(const String16& name) { +std::optional<String16> FakeServiceManager::updatableViaApex(const String16& name) { (void)name; return std::nullopt; } -Vector<String16> ServiceManager::getUpdatableNames(const String16& apexName) { +Vector<String16> FakeServiceManager::getUpdatableNames(const String16& apexName) { (void)apexName; return {}; } -std::optional<IServiceManager::ConnectionInfo> ServiceManager::getConnectionInfo( +std::optional<IServiceManager::ConnectionInfo> FakeServiceManager::getConnectionInfo( const String16& name) { (void)name; return std::nullopt; } -status_t ServiceManager::registerForNotifications(const String16&, +status_t FakeServiceManager::registerForNotifications(const String16&, const sp<LocalRegistrationCallback>&) { return INVALID_OPERATION; } -status_t ServiceManager::unregisterForNotifications(const String16&, +status_t FakeServiceManager::unregisterForNotifications(const String16&, const sp<LocalRegistrationCallback>&) { return INVALID_OPERATION; } -std::vector<IServiceManager::ServiceDebugInfo> ServiceManager::getServiceDebugInfo() { +std::vector<IServiceManager::ServiceDebugInfo> FakeServiceManager::getServiceDebugInfo() { std::vector<IServiceManager::ServiceDebugInfo> ret; return ret; } -void ServiceManager::clear() { +void FakeServiceManager::clear() { mNameToService.clear(); } } // namespace android diff --git a/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h b/libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h index ba6bb7d95b..97add24ac8 100644 --- a/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h +++ b/libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h @@ -28,9 +28,9 @@ namespace android { * A local host simple implementation of IServiceManager, that does not * communicate over binder. */ -class ServiceManager : public IServiceManager { +class FakeServiceManager : public IServiceManager { public: - ServiceManager(); + FakeServiceManager(); sp<IBinder> getService( const String16& name) const override; diff --git a/libs/fakeservicemanager/test_sm.cpp b/libs/fakeservicemanager/test_sm.cpp index 8682c1c795..6fc21c65d1 100644 --- a/libs/fakeservicemanager/test_sm.cpp +++ b/libs/fakeservicemanager/test_sm.cpp @@ -21,14 +21,14 @@ #include <binder/ProcessState.h> #include <binder/IServiceManager.h> -#include "ServiceManager.h" +#include "fakeservicemanager/FakeServiceManager.h" using android::sp; using android::BBinder; using android::IBinder; using android::OK; using android::status_t; -using android::ServiceManager; +using android::FakeServiceManager; using android::String16; using android::IServiceManager; using testing::ElementsAre; @@ -45,19 +45,19 @@ static sp<IBinder> getBinder() { } TEST(AddService, HappyHappy) { - auto sm = new ServiceManager(); + auto sm = new FakeServiceManager(); EXPECT_EQ(sm->addService(String16("foo"), getBinder(), false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK); } TEST(AddService, SadNullBinder) { - auto sm = new ServiceManager(); + auto sm = new FakeServiceManager(); EXPECT_EQ(sm->addService(String16("foo"), nullptr, false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), android::UNEXPECTED_NULL); } TEST(AddService, HappyOverExistingService) { - auto sm = new ServiceManager(); + auto sm = new FakeServiceManager(); EXPECT_EQ(sm->addService(String16("foo"), getBinder(), false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK); EXPECT_EQ(sm->addService(String16("foo"), getBinder(), false /*allowIsolated*/, @@ -65,7 +65,7 @@ TEST(AddService, HappyOverExistingService) { } TEST(AddService, HappyClearAddedService) { - auto sm = new ServiceManager(); + auto sm = new FakeServiceManager(); EXPECT_EQ(sm->addService(String16("foo"), getBinder(), false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK); EXPECT_NE(sm->getService(String16("foo")), nullptr); @@ -74,7 +74,7 @@ TEST(AddService, HappyClearAddedService) { } TEST(GetService, HappyHappy) { - auto sm = new ServiceManager(); + auto sm = new FakeServiceManager(); sp<IBinder> service = getBinder(); EXPECT_EQ(sm->addService(String16("foo"), service, false /*allowIsolated*/, @@ -84,13 +84,13 @@ TEST(GetService, HappyHappy) { } TEST(GetService, NonExistant) { - auto sm = new ServiceManager(); + auto sm = new FakeServiceManager(); EXPECT_EQ(sm->getService(String16("foo")), nullptr); } TEST(ListServices, AllServices) { - auto sm = new ServiceManager(); + auto sm = new FakeServiceManager(); EXPECT_EQ(sm->addService(String16("sd"), getBinder(), false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK); @@ -109,13 +109,13 @@ TEST(ListServices, AllServices) { } TEST(WaitForService, NonExistant) { - auto sm = new ServiceManager(); + auto sm = new FakeServiceManager(); EXPECT_EQ(sm->waitForService(String16("foo")), nullptr); } TEST(WaitForService, HappyHappy) { - auto sm = new ServiceManager(); + auto sm = new FakeServiceManager(); sp<IBinder> service = getBinder(); EXPECT_EQ(sm->addService(String16("foo"), service, false /*allowIsolated*/, @@ -125,13 +125,13 @@ TEST(WaitForService, HappyHappy) { } TEST(IsDeclared, NonExistant) { - auto sm = new ServiceManager(); + auto sm = new FakeServiceManager(); EXPECT_FALSE(sm->isDeclared(String16("foo"))); } TEST(IsDeclared, HappyHappy) { - auto sm = new ServiceManager(); + auto sm = new FakeServiceManager(); sp<IBinder> service = getBinder(); EXPECT_EQ(sm->addService(String16("foo"), service, false /*allowIsolated*/, diff --git a/libs/graphicsenv/OWNERS b/libs/graphicsenv/OWNERS index 347c4e0db1..1db8cbe52f 100644 --- a/libs/graphicsenv/OWNERS +++ b/libs/graphicsenv/OWNERS @@ -1,10 +1,4 @@ -abdolrashidi@google.com -cclao@google.com chrisforbes@google.com cnorthrop@google.com ianelliott@google.com -lfy@google.com lpy@google.com -romanl@google.com -vantablack@google.com -yuxinhu@google.com diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index 6c9c28a48f..21900a073a 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -254,6 +254,10 @@ cc_library_shared { 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 948611218b..821dd37a85 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 @@ namespace android { 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; @@ -157,11 +163,11 @@ BLASTBufferQueue::BLASTBufferQueue(const std::string& name, bool updateDestinati 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); @@ -212,7 +218,7 @@ void BLASTBufferQueue::update(const sp<SurfaceControl>& surface, uint32_t width, 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)); @@ -282,7 +288,7 @@ void BLASTBufferQueue::transactionCommittedCallback(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("transactionCommittedCallback"); if (!mSurfaceControlsWithPendingCallback.empty()) { @@ -330,7 +336,7 @@ static void transactionCallbackThunk(void* context, nsecs_t latchTime, 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"); @@ -411,9 +417,8 @@ void BLASTBufferQueue::flushShadowQueue() { 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 */); } @@ -428,17 +433,15 @@ void BLASTBufferQueue::releaseBufferCallbackLocked( // 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()) { @@ -488,13 +491,32 @@ void BLASTBufferQueue::releaseBuffer(const ReleaseCallbackId& callbackId, mSyncedFrameNumbers.erase(callbackId.framenumber); } +static ui::Size getBufferSize(const BufferItem& item) { + uint32_t bufWidth = item.mGraphicBuffer->getWidth(); + uint32_t bufHeight = item.mGraphicBuffer->getHeight(); + + // Take the buffer's orientation into account + if (item.mTransform & ui::Transform::ROT_90) { + std::swap(bufWidth, bufHeight); + } + return ui::Size(bufWidth, bufHeight); +} + 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) { @@ -557,7 +579,12 @@ status_t BLASTBufferQueue::acquireNextBufferLocked( // Ensure BLASTBufferQueue stays alive until we receive the transaction complete callback. incStrong((void*)transactionCallbackThunk); - mSize = mRequestedSize; + // Only update mSize for destination bounds if the incoming buffer matches the requested size. + // Otherwise, it could cause stretching since the destination bounds will update before the + // buffer with the new size is acquired. + if (mRequestedSize == getBufferSize(bufferItem)) { + mSize = mRequestedSize; + } Rect crop = computeCrop(bufferItem); mLastBufferInfo.update(true /* hasBuffer */, bufferItem.mGraphicBuffer->getWidth(), bufferItem.mGraphicBuffer->getHeight(), bufferItem.mTransform, @@ -567,7 +594,8 @@ status_t BLASTBufferQueue::acquireNextBufferLocked( 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); @@ -612,7 +640,7 @@ status_t BLASTBufferQueue::acquireNextBufferLocked( } { - std::unique_lock _lock{mTimestampMutex}; + std::lock_guard _lock{mTimestampMutex}; auto dequeueTime = mDequeueTimestamps.find(buffer->getId()); if (dequeueTime != mDequeueTimestamps.end()) { Parcel p; @@ -667,11 +695,11 @@ void BLASTBufferQueue::acquireAndReleaseBuffer() { 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)); @@ -701,6 +729,15 @@ void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) { // 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); + } } } @@ -716,6 +753,11 @@ void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) { 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) { @@ -728,7 +770,6 @@ void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) { incStrong((void*)transactionCommittedCallbackThunk); mSyncTransaction->addTransactionCommittedCallback(transactionCommittedCallbackThunk, static_cast<void*>(this)); - mSyncedFrameNumbers.emplace(item.mFrameNumber); if (mAcquireSingleBuffer) { prevCallback = mTransactionReadyCallback; prevTransaction = mSyncTransaction; @@ -750,25 +791,24 @@ void BLASTBufferQueue::onFrameReplaced(const BufferItem& item) { } 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) { @@ -815,14 +855,7 @@ bool BLASTBufferQueue::rejectBuffer(const BufferItem& item) { return false; } - uint32_t bufWidth = item.mGraphicBuffer->getWidth(); - uint32_t bufHeight = item.mGraphicBuffer->getHeight(); - - // Take the buffer's orientation into account - if (item.mTransform & ui::Transform::ROT_90) { - std::swap(bufWidth, bufHeight); - } - ui::Size bufferSize(bufWidth, bufHeight); + ui::Size bufferSize = getBufferSize(item); if (mRequestedSize != mSize && mRequestedSize == bufferSize) { return false; } @@ -834,8 +867,8 @@ bool BLASTBufferQueue::rejectBuffer(const BufferItem& item) { 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, @@ -856,7 +889,7 @@ public: 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; } @@ -869,7 +902,7 @@ public: 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; } @@ -879,7 +912,7 @@ public: void destroy() override { Surface::destroy(); - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; mDestroyed = true; mBbq = nullptr; } @@ -889,7 +922,7 @@ public: // 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(); @@ -899,20 +932,20 @@ status_t BLASTBufferQueue::setFrameTimelineInfo(uint64_t frameNumber, 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(); @@ -1137,6 +1170,7 @@ PixelFormat BLASTBufferQueue::convertBufferFormat(PixelFormat& format) { } uint32_t BLASTBufferQueue::getLastTransformHint() const { + std::lock_guard _lock{mMutex}; if (mSurfaceControl != nullptr) { return mSurfaceControl->getTransformHint(); } else { @@ -1145,18 +1179,18 @@ uint32_t BLASTBufferQueue::getLastTransformHint() const { } 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 6b25b262c3..46fb068dee 100644 --- a/libs/gui/Choreographer.cpp +++ b/libs/gui/Choreographer.cpp @@ -15,8 +15,10 @@ */ // #define LOG_NDEBUG 0 +#define ATRACE_TAG ATRACE_TAG_GRAPHICS #include <gui/Choreographer.h> +#include <gui/TraceUtils.h> #include <jni.h> #undef LOG_TAG @@ -101,8 +103,9 @@ Choreographer* Choreographer::getForThread() { 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); @@ -296,6 +299,8 @@ void Choreographer::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId, uint32_t mLastVsyncEventData = vsyncEventData; for (const auto& cb : callbacks) { if (cb.vsyncCallback != nullptr) { + ATRACE_FORMAT("AChoreographer_vsyncCallback %" PRId64, + vsyncEventData.preferredVsyncId()); const ChoreographerFrameCallbackDataImpl frameCallbackData = createFrameCallbackData(timestamp); registerStartTime(); @@ -305,8 +310,10 @@ void Choreographer::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId, uint32_t cb.data); mInCallback = false; } else if (cb.callback64 != nullptr) { + ATRACE_FORMAT("AChoreographer_frameCallback64"); cb.callback64(timestamp, cb.data); } else if (cb.callback != nullptr) { + ATRACE_FORMAT("AChoreographer_frameCallback"); cb.callback(timestamp, cb.data); } } diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp index 501e69ade5..8a883770d8 100644 --- a/libs/gui/DisplayEventDispatcher.cpp +++ b/libs/gui/DisplayEventDispatcher.cpp @@ -37,9 +37,10 @@ static constexpr nsecs_t WAITING_FOR_VSYNC_TIMEOUT = ms2ns(300); 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 c52fb6b7c3..6849a95d1e 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 @@ namespace android { // --------------------------------------------------------------------------- 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 @@ DisplayEventReceiver::DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource vs 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 @@ DisplayEventReceiver::DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource vs mDataChannel.reset(); mEventConnection.clear(); } + } else { + ALOGE("DisplayEventConnection creation failed: status=%s", status.toString8().c_str()); } } } diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp index 985c54922d..ffe79a3a03 100644 --- a/libs/gui/ITransactionCompletedListener.cpp +++ b/libs/gui/ITransactionCompletedListener.cpp @@ -39,6 +39,12 @@ enum class Tag : uint32_t { } // 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 @@ ListenerCallbacks ListenerCallbacks::filter(CallbackId::Type type) const { 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 @@ status_t CallbackId::readFromParcel(const Parcel* input) { 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 8372363185..b391337d1d 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -74,7 +74,7 @@ layer_state_t::layer_state_t() surfaceDamageRegion(), api(-1), colorTransform(mat4()), - bgColorAlpha(0), + bgColor(0), bgColorDataspace(ui::Dataspace::UNKNOWN), colorSpaceAgnostic(false), shadowRadius(0.0f), @@ -140,7 +140,10 @@ status_t layer_state_t::write(Parcel& output) const SAFE_PARCEL(output.writeFloat, cornerRadius); SAFE_PARCEL(output.writeUint32, backgroundBlurRadius); SAFE_PARCEL(output.writeParcelable, metadata); - SAFE_PARCEL(output.writeFloat, bgColorAlpha); + SAFE_PARCEL(output.writeFloat, bgColor.r); + SAFE_PARCEL(output.writeFloat, bgColor.g); + SAFE_PARCEL(output.writeFloat, bgColor.b); + SAFE_PARCEL(output.writeFloat, bgColor.a); SAFE_PARCEL(output.writeUint32, static_cast<uint32_t>(bgColorDataspace)); SAFE_PARCEL(output.writeBool, colorSpaceAgnostic); SAFE_PARCEL(output.writeVectorSize, listeners); @@ -189,6 +192,7 @@ status_t layer_state_t::write(Parcel& output) const SAFE_PARCEL(output.writeParcelable, trustedPresentationListener); SAFE_PARCEL(output.writeFloat, currentSdrHdrRatio); SAFE_PARCEL(output.writeFloat, desiredSdrHdrRatio); + SAFE_PARCEL(output.writeInt32, static_cast<int32_t>(cachingHint)) return NO_ERROR; } @@ -258,7 +262,14 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(input.readUint32, &backgroundBlurRadius); SAFE_PARCEL(input.readParcelable, &metadata); - SAFE_PARCEL(input.readFloat, &bgColorAlpha); + SAFE_PARCEL(input.readFloat, &tmpFloat); + bgColor.r = tmpFloat; + SAFE_PARCEL(input.readFloat, &tmpFloat); + bgColor.g = tmpFloat; + SAFE_PARCEL(input.readFloat, &tmpFloat); + bgColor.b = tmpFloat; + SAFE_PARCEL(input.readFloat, &tmpFloat); + bgColor.a = tmpFloat; SAFE_PARCEL(input.readUint32, &tmpUint32); bgColorDataspace = static_cast<ui::Dataspace>(tmpUint32); SAFE_PARCEL(input.readBool, &colorSpaceAgnostic); @@ -328,6 +339,10 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(input.readFloat, &tmpFloat); desiredSdrHdrRatio = tmpFloat; + int32_t tmpInt32; + SAFE_PARCEL(input.readInt32, &tmpInt32); + cachingHint = static_cast<gui::CachingHint>(tmpInt32); + return NO_ERROR; } @@ -580,6 +595,10 @@ void layer_state_t::merge(const layer_state_t& other) { desiredSdrHdrRatio = other.desiredSdrHdrRatio; currentSdrHdrRatio = other.currentSdrHdrRatio; } + if (other.what & eCachingHintChanged) { + what |= eCachingHintChanged; + cachingHint = other.cachingHint; + } if (other.what & eHdrMetadataChanged) { what |= eHdrMetadataChanged; hdrMetadata = other.hdrMetadata; @@ -609,8 +628,7 @@ void layer_state_t::merge(const layer_state_t& other) { } if (other.what & eBackgroundColorChanged) { what |= eBackgroundColorChanged; - color.rgb = other.color.rgb; - bgColorAlpha = other.bgColorAlpha; + bgColor = other.bgColor; bgColorDataspace = other.bgColorDataspace; } if (other.what & eMetadataChanged) { @@ -684,6 +702,9 @@ void layer_state_t::merge(const layer_state_t& other) { 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, @@ -728,6 +749,7 @@ uint64_t layer_state_t::diff(const layer_state_t& other) const { CHECK_DIFF(diff, eDataspaceChanged, other, dataspace); CHECK_DIFF2(diff, eExtendedRangeBrightnessChanged, other, currentSdrHdrRatio, desiredSdrHdrRatio); + CHECK_DIFF(diff, eCachingHintChanged, other, cachingHint); CHECK_DIFF(diff, eHdrMetadataChanged, other, hdrMetadata); if (other.what & eSurfaceDamageRegionChanged && (!surfaceDamageRegion.hasSameRects(other.surfaceDamageRegion))) { @@ -739,7 +761,7 @@ uint64_t layer_state_t::diff(const layer_state_t& other) const { CHECK_DIFF(diff, eColorTransformChanged, other, colorTransform); if (other.what & eHasListenerCallbacksChanged) diff |= eHasListenerCallbacksChanged; if (other.what & eInputInfoChanged) diff |= eInputInfoChanged; - CHECK_DIFF3(diff, eBackgroundColorChanged, other, color.rgb, bgColorAlpha, bgColorDataspace); + CHECK_DIFF2(diff, eBackgroundColorChanged, other, bgColor, bgColorDataspace); if (other.what & eMetadataChanged) diff |= eMetadataChanged; CHECK_DIFF(diff, eShadowRadiusChanged, other, shadowRadius); CHECK_DIFF3(diff, eRenderBorderChanged, other, borderEnabled, borderWidth, borderColor); @@ -981,6 +1003,7 @@ status_t BufferData::writeToParcel(Parcel* output) const { SAFE_PARCEL(output->writeUint64, cachedBuffer.id); SAFE_PARCEL(output->writeBool, hasBarrier); SAFE_PARCEL(output->writeUint64, barrierFrameNumber); + SAFE_PARCEL(output->writeUint32, producerId); return NO_ERROR; } @@ -1019,6 +1042,7 @@ status_t BufferData::readFromParcel(const Parcel* input) { 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 9092f5fe67..2f5830d362 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 @@ std::atomic<uint32_t> idCounter = 0; 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 @@ CallbackId TransactionCompletedListener::addCallbackFunction( 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 @@ CallbackId TransactionCompletedListener::addCallbackFunction( 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::removeSurfaceStatsListener(void* context, voi } 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); } } @@ -929,8 +956,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::merge(Tr // 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); } } @@ -1180,6 +1206,19 @@ sp<IBinder> SurfaceComposerClient::Transaction::getDefaultApplyToken() { 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, @@ -1253,8 +1292,7 @@ void SurfaceComposerClient::Transaction::registerSurfaceControlForCallback( 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( @@ -1325,7 +1363,8 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFlags (mask & layer_state_t::eLayerSecure) || (mask & layer_state_t::eLayerSkipScreenshot) || (mask & layer_state_t::eEnableBackpressure) || (mask & layer_state_t::eIgnoreDestinationFrame) || - (mask & layer_state_t::eLayerIsDisplayDecoration)) { + (mask & layer_state_t::eLayerIsDisplayDecoration) || + (mask & layer_state_t::eLayerIsRefreshRateIndicator)) { s->what |= layer_state_t::eFlagsChanged; } s->flags &= ~mask; @@ -1522,8 +1561,8 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBackg } s->what |= layer_state_t::eBackgroundColorChanged; - s->color.rgb = color; - s->bgColorAlpha = alpha; + s->bgColor.rgb = color; + s->bgColor.a = alpha; s->bgColorDataspace = dataspace; registerSurfaceControlForCallback(sc); @@ -1594,7 +1633,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffe 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; @@ -1613,6 +1652,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffe 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; @@ -1690,6 +1730,20 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setExten return *this; } +SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setCachingHint( + const sp<SurfaceControl>& sc, gui::CachingHint cachingHint) { + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + s->what |= layer_state_t::eCachingHintChanged; + s->cachingHint = cachingHint; + + registerSurfaceControlForCallback(sc); + return *this; +} + SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setHdrMetadata( const sp<SurfaceControl>& sc, const HdrMetadata& hdrMetadata) { layer_state_t* s = getLayerState(sc); @@ -2618,9 +2672,17 @@ status_t SurfaceComposerClient::getHdrConversionCapabilities( } status_t SurfaceComposerClient::setHdrConversionStrategy( - gui::HdrConversionStrategy hdrConversionStrategy) { - binder::Status status = ComposerServiceAIDL::getComposerService()->setHdrConversionStrategy( - hdrConversionStrategy); + gui::HdrConversionStrategy hdrConversionStrategy, ui::Hdr* outPreferredHdrOutputType) { + int hdrType; + binder::Status status = ComposerServiceAIDL::getComposerService() + ->setHdrConversionStrategy(hdrConversionStrategy, &hdrType); + *outPreferredHdrOutputType = static_cast<ui::Hdr>(hdrType); + return statusTFromBinderStatus(status); +} + +status_t SurfaceComposerClient::getHdrOutputConversionSupport(bool* isSupported) { + binder::Status status = + ComposerServiceAIDL::getComposerService()->getHdrOutputConversionSupport(isSupported); return statusTFromBinderStatus(status); } @@ -2988,6 +3050,7 @@ void ReleaseCallbackThread::threadMain() { while (true) { { std::unique_lock<std::mutex> lock(mMutex); + base::ScopedLockAssertion assumeLocked(mMutex); callbackInfos = std::move(mCallbackInfos); mCallbackInfos = {}; } @@ -3000,6 +3063,7 @@ void ReleaseCallbackThread::threadMain() { { 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 7aee882422..c5f9c38ca3 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 @@ const std::string& SurfaceControl::getName() const { 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/VsyncEventData.cpp b/libs/gui/VsyncEventData.cpp index 23f0921e99..76c60c23c4 100644 --- a/libs/gui/VsyncEventData.cpp +++ b/libs/gui/VsyncEventData.cpp @@ -23,6 +23,9 @@ namespace android::gui { +static_assert(VsyncEventData::kFrameTimelinesLength == 7, + "Must update value in DisplayEventReceiver.java#FRAME_TIMELINES_LENGTH (and here)"); + int64_t VsyncEventData::preferredVsyncId() const { return frameTimelines[preferredFrameTimelineIndex].vsyncId; } diff --git a/libs/gui/aidl/android/gui/CachingHint.aidl b/libs/gui/aidl/android/gui/CachingHint.aidl new file mode 100644 index 0000000000..b35c79547f --- /dev/null +++ b/libs/gui/aidl/android/gui/CachingHint.aidl @@ -0,0 +1,30 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +/* + * Hint for configuring caching behavior for a layer + * @hide + */ +@Backing(type="int") +enum CachingHint { + // Caching is disabled. A layer may explicitly disable caching for + // improving image quality for some scenes. + Disabled = 0, + // Caching is enabled. A layer is cacheable by default. + Enabled = 1 +} diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 597749acc4..aa58e2e580 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -68,9 +68,15 @@ interface ISurfaceComposer { /** * 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. @@ -185,10 +191,12 @@ interface ISurfaceComposer { /** * Sets the HDR conversion strategy of the device. + * Returns the preferred HDR output type of the device, in case when HdrConversionStrategy has + * autoAllowedHdrTypes set. Returns Hdr::INVALID in other cases. * * Requires the ACCESS_SURFACE_FLINGER permission. */ - void setHdrConversionStrategy(in HdrConversionStrategy hdrConversionStrategy); + int setHdrConversionStrategy(in HdrConversionStrategy hdrConversionStrategy); /** * Gets whether HDR output conversion operations are supported on the device. diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h index 14a0e39813..8c003d8ad4 100644 --- a/libs/gui/fuzzer/libgui_fuzzer_utils.h +++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h @@ -64,7 +64,7 @@ public: 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, float, sp<IBinder>*), @@ -93,8 +93,8 @@ public: MOCK_METHOD(binder::Status, getBootDisplayModeSupport, (bool*), (override)); MOCK_METHOD(binder::Status, getHdrConversionCapabilities, (std::vector<gui::HdrConversionCapability>*), (override)); - MOCK_METHOD(binder::Status, setHdrConversionStrategy, (const gui::HdrConversionStrategy&), - (override)); + MOCK_METHOD(binder::Status, setHdrConversionStrategy, + (const gui::HdrConversionStrategy&, int32_t*), (override)); MOCK_METHOD(binder::Status, getHdrOutputConversionSupport, (bool*), (override)); MOCK_METHOD(binder::Status, setAutoLowLatencyMode, (const sp<IBinder>&, bool), (override)); MOCK_METHOD(binder::Status, setGameContentType, (const sp<IBinder>&, bool), (override)); diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h index 515363a4cd..69e9f8a4fb 100644 --- a/libs/gui/include/gui/BLASTBufferQueue.h +++ b/libs/gui/include/gui/BLASTBufferQueue.h @@ -44,25 +44,25 @@ public: 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); void resizeFrameEventHistory(size_t newSize); 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); @@ -96,7 +96,7 @@ public: 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(); @@ -155,7 +155,7 @@ private: // 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; @@ -167,6 +167,11 @@ private: 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 @@ -257,7 +262,7 @@ private: // 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 @@ -281,8 +286,8 @@ private: // 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 89a7058dd6..1df9b11432 100644 --- a/libs/gui/include/gui/Choreographer.h +++ b/libs/gui/include/gui/Choreographer.h @@ -73,7 +73,8 @@ public: }; 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/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h index bf3a07b6b5..140efa6d97 100644 --- a/libs/gui/include/gui/DisplayEventDispatcher.h +++ b/libs/gui/include/gui/DisplayEventDispatcher.h @@ -26,7 +26,8 @@ public: 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 0f4907fcb9..7fd6c35c5e 100644 --- a/libs/gui/include/gui/DisplayEventReceiver.h +++ b/libs/gui/include/gui/DisplayEventReceiver.h @@ -119,7 +119,8 @@ public: */ 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 ae56f9fdb5..1e67225a4e 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -16,6 +16,7 @@ #pragma once +#include <android/gui/CachingHint.h> #include <android/gui/DisplayBrightness.h> #include <android/gui/FrameTimelineInfo.h> #include <android/gui/IDisplayEventConnection.h> diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h index d593f56c3a..39bcb4a56c 100644 --- a/libs/gui/include/gui/ITransactionCompletedListener.h +++ b/libs/gui/include/gui/ITransactionCompletedListener.h @@ -40,10 +40,15 @@ class ListenerCallbacks; 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 b8bee72cec..6e3be5cef8 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -111,6 +111,7 @@ public: 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 @@ -159,6 +160,7 @@ struct layer_state_t { // This is needed to maintain compatibility for SurfaceView scaling behavior. // See SurfaceView scaling behavior for more details. eIgnoreDestinationFrame = 0x400, + eLayerIsRefreshRateIndicator = 0x800, // REFRESH_RATE_INDICATOR }; enum { @@ -170,8 +172,8 @@ struct layer_state_t { eTransparentRegionChanged = 0x00000020, eFlagsChanged = 0x00000040, eLayerStackChanged = 0x00000080, - /* unused = 0x00000100, */ - /* unused = 0x00000200, */ + eFlushJankData = 0x00000100, + eCachingHintChanged = 0x00000200, eDimmingEnabledChanged = 0x00000400, eShadowRadiusChanged = 0x00000800, eRenderBorderChanged = 0x00001000, @@ -210,7 +212,8 @@ struct layer_state_t { eStretchChanged = 0x2000'00000000, eTrustedOverlayChanged = 0x4000'00000000, eDropInputModeChanged = 0x8000'00000000, - eExtendedRangeBrightnessChanged = 0x10000'00000000 + eExtendedRangeBrightnessChanged = 0x10000'00000000, + }; layer_state_t(); @@ -224,9 +227,9 @@ struct layer_state_t { bool hasBufferChanges() const; // Layer hierarchy updates. - static constexpr uint64_t HIERARCHY_CHANGES = layer_state_t::eBackgroundColorChanged | - layer_state_t::eLayerChanged | layer_state_t::eRelativeLayerChanged | - layer_state_t::eReparent; + static constexpr uint64_t HIERARCHY_CHANGES = layer_state_t::eLayerChanged | + layer_state_t::eRelativeLayerChanged | layer_state_t::eReparent | + layer_state_t::eLayerStackChanged; // Geometry updates. static constexpr uint64_t GEOMETRY_CHANGES = layer_state_t::eBufferCropChanged | @@ -262,9 +265,8 @@ struct layer_state_t { static constexpr uint64_t AFFECTS_CHILDREN = layer_state_t::GEOMETRY_CHANGES | layer_state_t::HIERARCHY_CHANGES | layer_state_t::eAlphaChanged | layer_state_t::eColorTransformChanged | layer_state_t::eCornerRadiusChanged | - layer_state_t::eFlagsChanged | layer_state_t::eLayerStackChanged | - layer_state_t::eTrustedOverlayChanged | layer_state_t::eFrameRateChanged | - layer_state_t::eFixedTransformHintChanged; + layer_state_t::eFlagsChanged | layer_state_t::eTrustedOverlayChanged | + layer_state_t::eFrameRateChanged | layer_state_t::eFixedTransformHintChanged; // Changes affecting data sent to input. static constexpr uint64_t INPUT_CHANGES = layer_state_t::GEOMETRY_CHANGES | @@ -331,7 +333,7 @@ struct layer_state_t { // The following refer to the alpha, and dataspace, respectively of // the background color layer - float bgColorAlpha; + half4 bgColor; ui::Dataspace bgColorDataspace; // A color space agnostic layer means the color of this layer can be @@ -390,6 +392,8 @@ struct layer_state_t { float currentSdrHdrRatio = 1.f; float desiredSdrHdrRatio = 1.f; + gui::CachingHint cachingHint = gui::CachingHint::Enabled; + TrustedPresentationThresholds trustedPresentationThresholds; TrustedPresentationListener trustedPresentationListener; }; diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 45f4dbe2be..d431b4381a 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -192,8 +192,12 @@ public: // Gets the HDR conversion capabilities of the device static status_t getHdrConversionCapabilities(std::vector<gui::HdrConversionCapability>*); - // Sets the HDR conversion strategy for the device - static status_t setHdrConversionStrategy(gui::HdrConversionStrategy hdrConversionStrategy); + // Sets the HDR conversion strategy for the device. in case when HdrConversionStrategy has + // autoAllowedHdrTypes set. Returns Hdr::INVALID in other cases. + static status_t setHdrConversionStrategy(gui::HdrConversionStrategy hdrConversionStrategy, + ui::Hdr* outPreferredHdrOutputType); + // Returns whether HDR conversion is supported by the device. + static status_t getHdrOutputConversionSupport(bool* isSupported); // Sets the frame rate of a particular app (uid). This is currently called // by GameManager. @@ -536,7 +540,7 @@ public: 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); /** @@ -562,6 +566,7 @@ public: Transaction& setDataspace(const sp<SurfaceControl>& sc, ui::Dataspace dataspace); Transaction& setExtendedRangeBrightness(const sp<SurfaceControl>& sc, float currentBufferRatio, float desiredRatio); + Transaction& setCachingHint(const sp<SurfaceControl>& sc, gui::CachingHint cachingHint); Transaction& setHdrMetadata(const sp<SurfaceControl>& sc, const HdrMetadata& hdrMetadata); Transaction& setSurfaceDamageRegion(const sp<SurfaceControl>& sc, const Region& surfaceDamageRegion); @@ -742,6 +747,8 @@ public: 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; @@ -876,10 +883,14 @@ public: 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); @@ -921,7 +932,7 @@ public: 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 1d4fc7f06d..344b957ba7 100644 --- a/libs/gui/include/gui/SurfaceControl.h +++ b/libs/gui/include/gui/SurfaceControl.h @@ -36,6 +36,7 @@ namespace android { // --------------------------------------------------------------------------- +class Choreographer; class IGraphicBufferProducer; class Surface; class SurfaceComposerClient; @@ -80,6 +81,9 @@ public: 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 @@ private: 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 9b2bf7ff31..fccc408473 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -725,6 +725,7 @@ public: binder::Status createDisplayEventConnection( VsyncSource /*vsyncSource*/, EventRegistration /*eventRegistration*/, + const sp<IBinder>& /*layerHandle*/, sp<gui::IDisplayEventConnection>* outConnection) override { *outConnection = nullptr; return binder::Status::ok(); @@ -826,7 +827,8 @@ public: } binder::Status setHdrConversionStrategy( - const gui::HdrConversionStrategy& /*hdrConversionStrategy*/) override { + const gui::HdrConversionStrategy& /*hdrConversionStrategy*/, + int32_t* /*outPreferredHdrOutputType*/) override { return binder::Status::ok(); } diff --git a/libs/gui/view/Surface.cpp b/libs/gui/view/Surface.cpp index 1bfe4624f1..198908d334 100644 --- a/libs/gui/view/Surface.cpp +++ b/libs/gui/view/Surface.cpp @@ -16,17 +16,59 @@ #define LOG_TAG "Surface" -#include <gui/view/Surface.h> - +#include <android/binder_libbinder.h> +#include <android/binder_parcel.h> +#include <android/native_window.h> #include <binder/Parcel.h> - -#include <utils/Log.h> - #include <gui/IGraphicBufferProducer.h> +#include <gui/Surface.h> +#include <gui/view/Surface.h> +#include <system/window.h> +#include <utils/Log.h> namespace android { namespace view { +// Since this is a parcelable utility and we want to keep the wire format stable, only build this +// when building the system libgui to detect any issues loading the wrong libgui from +// libnativewindow + +#if (!defined(__ANDROID_APEX__) && !defined(__ANDROID_VNDK__)) + +extern "C" status_t android_view_Surface_writeToParcel(ANativeWindow* _Nonnull window, + Parcel* _Nonnull parcel) { + int value; + int err = (*window->query)(window, NATIVE_WINDOW_CONCRETE_TYPE, &value); + if (err != OK || value != NATIVE_WINDOW_SURFACE) { + ALOGE("Error: ANativeWindow is not backed by Surface"); + return STATUS_BAD_VALUE; + } + // Use a android::view::Surface to parcelize the window + android::view::Surface shimSurface; + shimSurface.graphicBufferProducer = android::Surface::getIGraphicBufferProducer(window); + shimSurface.surfaceControlHandle = android::Surface::getSurfaceControlHandle(window); + return shimSurface.writeToParcel(parcel); +} + +extern "C" status_t android_view_Surface_readFromParcel( + const Parcel* _Nonnull parcel, ANativeWindow* _Nullable* _Nonnull outWindow) { + // Use a android::view::Surface to unparcel the window + android::view::Surface shimSurface; + status_t ret = shimSurface.readFromParcel(parcel); + if (ret != OK) { + ALOGE("%s: Error: Failed to create android::view::Surface from AParcel", __FUNCTION__); + return STATUS_BAD_VALUE; + } + auto surface = sp<android::Surface>::make(shimSurface.graphicBufferProducer, false, + shimSurface.surfaceControlHandle); + ANativeWindow* anw = surface.get(); + ANativeWindow_acquire(anw); + *outWindow = anw; + return STATUS_OK; +} + +#endif + status_t Surface::writeToParcel(Parcel* parcel) const { return writeToParcel(parcel, false); } diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 83392ec793..f38dd98428 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -47,6 +47,7 @@ cc_library { "Input.cpp", "InputDevice.cpp", "InputEventLabels.cpp", + "InputVerifier.cpp", "Keyboard.cpp", "KeyCharacterMap.cpp", "KeyLayoutMap.cpp", @@ -57,6 +58,7 @@ cc_library { "TouchVideoFrame.cpp", "VelocityControl.cpp", "VelocityTracker.cpp", + "VirtualInputDevice.cpp", "VirtualKeyMap.cpp", ], @@ -67,17 +69,25 @@ cc_library { ], export_header_lib_headers: ["jni_headers"], + generated_headers: [ + "toolbox_input_labels", + ], + shared_libs: [ "libbase", "libcutils", "liblog", "libPlatformProperties", "libvintf", - "libtflite", + ], + + ldflags: [ + "-Wl,--exclude-libs=libtflite_static.a", ], static_libs: [ "libui-types", + "libtflite_static", ], export_static_lib_headers: [ @@ -120,6 +130,8 @@ cc_library { enabled: false, }, include_dirs: [ + "bionic/libc/kernel/android/uapi/", + "bionic/libc/kernel/uapi", "frameworks/native/libs/arect/include", ], }, diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp index d97c1bb629..4a19227694 100644 --- a/libs/input/InputEventLabels.cpp +++ b/libs/input/InputEventLabels.cpp @@ -16,6 +16,9 @@ #include <input/InputEventLabels.h> +#include <linux/input-event-codes.h> +#include <linux/input.h> + #define DEFINE_KEYCODE(key) { #key, AKEYCODE_##key } #define DEFINE_AXIS(axis) { #axis, AMOTION_EVENT_AXIS_##axis } #define DEFINE_LED(led) { #led, ALED_##led } @@ -480,4 +483,85 @@ std::optional<int> InputEventLookup::getLedByLabel(const char* label) { return lookupValueByLabel(LEDS, label); } +namespace { + +struct label { + const char* name; + int value; +}; + +#define LABEL(constant) \ + { #constant, constant } +#define LABEL_END \ + { nullptr, -1 } + +static struct label ev_key_value_labels[] = { + {"UP", 0}, + {"DOWN", 1}, + {"REPEAT", 2}, + LABEL_END, +}; + +#include "input.h-labels.h" + +#undef LABEL +#undef LABEL_END + +std::string getLabel(const label* labels, int value) { + if (labels == nullptr) return std::to_string(value); + while (labels->name != nullptr && value != labels->value) { + labels++; + } + return labels->name != nullptr ? labels->name : std::to_string(value); +} + +const label* getCodeLabelsForType(int32_t type) { + switch (type) { + case EV_SYN: + return syn_labels; + case EV_KEY: + return key_labels; + case EV_REL: + return rel_labels; + case EV_ABS: + return abs_labels; + case EV_SW: + return sw_labels; + case EV_MSC: + return msc_labels; + case EV_LED: + return led_labels; + case EV_REP: + return rep_labels; + case EV_SND: + return snd_labels; + case EV_FF: + return ff_labels; + case EV_FF_STATUS: + return ff_status_labels; + default: + return nullptr; + } +} + +const label* getValueLabelsForTypeAndCode(int32_t type, int32_t code) { + if (type == EV_KEY) { + return ev_key_value_labels; + } + if (type == EV_MSC && code == ABS_MT_TOOL_TYPE) { + return mt_tool_labels; + } + return nullptr; +} + +} // namespace + +EvdevEventLabel InputEventLookup::getLinuxEvdevLabel(int32_t type, int32_t code, int32_t value) { + return { + .type = getLabel(ev_labels, type), + .code = getLabel(getCodeLabelsForType(type), code), + .value = getLabel(getValueLabelsForTypeAndCode(type, code), value), + }; +} + } // namespace android diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp index 9f0a314041..311b2441a4 100644 --- a/libs/input/InputTransport.cpp +++ b/libs/input/InputTransport.cpp @@ -5,20 +5,6 @@ // #define LOG_TAG "InputTransport" -//#define LOG_NDEBUG 0 - -// Log debug messages about channel messages (send message, receive message) -#define DEBUG_CHANNEL_MESSAGES 0 - -// Log debug messages whenever InputChannel objects are created/destroyed -static constexpr bool DEBUG_CHANNEL_LIFECYCLE = false; - -// Log debug messages about transport actions -static constexpr bool DEBUG_TRANSPORT_ACTIONS = false; - -// Log debug messages about touch event resampling -#define DEBUG_RESAMPLING 0 - #include <errno.h> #include <fcntl.h> #include <inttypes.h> @@ -27,6 +13,7 @@ static constexpr bool DEBUG_TRANSPORT_ACTIONS = false; #include <sys/types.h> #include <unistd.h> +#include <android-base/properties.h> #include <android-base/stringprintf.h> #include <binder/Parcel.h> #include <cutils/properties.h> @@ -36,6 +23,63 @@ static constexpr bool DEBUG_TRANSPORT_ACTIONS = false; #include <input/InputTransport.h> +namespace { + +/** + * Log debug messages about channel messages (send message, receive message). + * Enable this via "adb shell setprop log.tag.InputTransportMessages DEBUG" + * (requires restart) + */ +const bool DEBUG_CHANNEL_MESSAGES = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Messages", ANDROID_LOG_INFO); + +/** + * Log debug messages whenever InputChannel objects are created/destroyed. + * Enable this via "adb shell setprop log.tag.InputTransportLifecycle DEBUG" + * (requires restart) + */ +const bool DEBUG_CHANNEL_LIFECYCLE = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Lifecycle", ANDROID_LOG_INFO); + +/** + * Log debug messages relating to the consumer end of the transport channel. + * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart) + */ + +const bool DEBUG_TRANSPORT_CONSUMER = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO); + +const bool IS_DEBUGGABLE_BUILD = +#if defined(__ANDROID__) + android::base::GetBoolProperty("ro.debuggable", false); +#else + true; +#endif + +/** + * Log debug messages relating to the producer end of the transport channel. + * Enable this via "adb shell setprop log.tag.InputTransportPublisher DEBUG". + * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately + * on debuggable builds (e.g. userdebug). + */ +bool debugTransportPublisher() { + if (!IS_DEBUGGABLE_BUILD) { + static const bool DEBUG_TRANSPORT_PUBLISHER = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Publisher", ANDROID_LOG_INFO); + return DEBUG_TRANSPORT_PUBLISHER; + } + return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Publisher", ANDROID_LOG_INFO); +} + +/** + * Log debug messages about touch event resampling. + * Enable this via "adb shell setprop log.tag.InputTransportResampling DEBUG" (requires restart) + */ +const bool DEBUG_RESAMPLING = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO); + +} // namespace + using android::base::StringPrintf; namespace android { @@ -76,6 +120,14 @@ static const nsecs_t RESAMPLE_MAX_PREDICTION = 8 * NANOS_PER_MS; */ static const char* PROPERTY_RESAMPLING_ENABLED = "ro.input.resampling"; +/** + * Crash if the events that are getting sent to the InputPublisher are inconsistent. + * Enable this via "adb shell setprop log.tag.InputTransportVerifyEvents DEBUG" + */ +static bool verifyEvents() { + return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VerifyEvents", ANDROID_LOG_INFO); +} + template<typename T> inline static T min(const T& a, const T& b) { return a < b ? a : b; @@ -132,7 +184,7 @@ bool InputMessage::isValid(size_t actualSize) const { return valid; } } - ALOGE("Invalid message type: %" PRIu32, header.type); + ALOGE("Invalid message type: %s", ftl::enum_string(header.type).c_str()); return false; } @@ -322,15 +374,13 @@ std::unique_ptr<InputChannel> InputChannel::create(const std::string& name, InputChannel::InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token) : mName(std::move(name)), mFd(std::move(fd)), mToken(std::move(token)) { - if (DEBUG_CHANNEL_LIFECYCLE) { - ALOGD("Input channel constructed: name='%s', fd=%d", getName().c_str(), getFd().get()); - } + ALOGD_IF(DEBUG_CHANNEL_LIFECYCLE, "Input channel constructed: name='%s', fd=%d", + getName().c_str(), getFd().get()); } InputChannel::~InputChannel() { - if (DEBUG_CHANNEL_LIFECYCLE) { - ALOGD("Input channel destroyed: name='%s', fd=%d", getName().c_str(), getFd().get()); - } + ALOGD_IF(DEBUG_CHANNEL_LIFECYCLE, "Input channel destroyed: name='%s', fd=%d", + getName().c_str(), getFd().get()); } status_t InputChannel::openInputChannelPair(const std::string& name, @@ -375,10 +425,8 @@ status_t InputChannel::sendMessage(const InputMessage* msg) { if (nWrite < 0) { int error = errno; -#if DEBUG_CHANNEL_MESSAGES - ALOGD("channel '%s' ~ error sending message of type %d, %s", mName.c_str(), - msg->header.type, strerror(error)); -#endif + ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ error sending message of type %s, %s", + mName.c_str(), ftl::enum_string(msg->header.type).c_str(), strerror(error)); if (error == EAGAIN || error == EWOULDBLOCK) { return WOULD_BLOCK; } @@ -389,16 +437,14 @@ status_t InputChannel::sendMessage(const InputMessage* msg) { } if (size_t(nWrite) != msgLength) { -#if DEBUG_CHANNEL_MESSAGES - ALOGD("channel '%s' ~ error sending message type %d, send was incomplete", - mName.c_str(), msg->header.type); -#endif + ALOGD_IF(DEBUG_CHANNEL_MESSAGES, + "channel '%s' ~ error sending message type %s, send was incomplete", mName.c_str(), + ftl::enum_string(msg->header.type).c_str()); return DEAD_OBJECT; } -#if DEBUG_CHANNEL_MESSAGES - ALOGD("channel '%s' ~ sent message of type %d", mName.c_str(), msg->header.type); -#endif + ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ sent message of type %s", mName.c_str(), + ftl::enum_string(msg->header.type).c_str()); return OK; } @@ -410,9 +456,8 @@ status_t InputChannel::receiveMessage(InputMessage* msg) { if (nRead < 0) { int error = errno; -#if DEBUG_CHANNEL_MESSAGES - ALOGD("channel '%s' ~ receive message failed, errno=%d", mName.c_str(), errno); -#endif + ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ receive message failed, errno=%d", + mName.c_str(), errno); if (error == EAGAIN || error == EWOULDBLOCK) { return WOULD_BLOCK; } @@ -423,9 +468,8 @@ status_t InputChannel::receiveMessage(InputMessage* msg) { } if (nRead == 0) { // check for EOF -#if DEBUG_CHANNEL_MESSAGES - ALOGD("channel '%s' ~ receive message failed because peer was closed", mName.c_str()); -#endif + ALOGD_IF(DEBUG_CHANNEL_MESSAGES, + "channel '%s' ~ receive message failed because peer was closed", mName.c_str()); return DEAD_OBJECT; } @@ -434,9 +478,8 @@ status_t InputChannel::receiveMessage(InputMessage* msg) { return BAD_VALUE; } -#if DEBUG_CHANNEL_MESSAGES - ALOGD("channel '%s' ~ received message of type %d", mName.c_str(), msg->header.type); -#endif + ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ received message of type %s", mName.c_str(), + ftl::enum_string(msg->header.type).c_str()); return OK; } @@ -492,7 +535,8 @@ base::unique_fd InputChannel::dupFd() const { // --- InputPublisher --- -InputPublisher::InputPublisher(const std::shared_ptr<InputChannel>& channel) : mChannel(channel) {} +InputPublisher::InputPublisher(const std::shared_ptr<InputChannel>& channel) + : mChannel(channel), mInputVerifier(channel->getName()) {} InputPublisher::~InputPublisher() { } @@ -504,17 +548,19 @@ status_t InputPublisher::publishKeyEvent(uint32_t seq, int32_t eventId, int32_t int32_t metaState, int32_t repeatCount, nsecs_t downTime, nsecs_t eventTime) { if (ATRACE_ENABLED()) { - std::string message = StringPrintf("publishKeyEvent(inputChannel=%s, keyCode=%" PRId32 ")", - mChannel->getName().c_str(), keyCode); + std::string message = + StringPrintf("publishKeyEvent(inputChannel=%s, action=%s, keyCode=%s)", + mChannel->getName().c_str(), KeyEvent::actionToString(action), + KeyEvent::getLabel(keyCode)); ATRACE_NAME(message.c_str()); } - if (DEBUG_TRANSPORT_ACTIONS) { - ALOGD("channel '%s' publisher ~ publishKeyEvent: seq=%u, deviceId=%d, source=0x%x, " - "action=0x%x, flags=0x%x, keyCode=%d, scanCode=%d, metaState=0x%x, repeatCount=%d," - "downTime=%" PRId64 ", eventTime=%" PRId64, - mChannel->getName().c_str(), seq, deviceId, source, action, flags, keyCode, scanCode, - metaState, repeatCount, downTime, eventTime); - } + ALOGD_IF(debugTransportPublisher(), + "channel '%s' publisher ~ %s: seq=%u, id=%d, deviceId=%d, source=%s, " + "action=%s, flags=0x%x, keyCode=%s, scanCode=%d, metaState=0x%x, repeatCount=%d," + "downTime=%" PRId64 ", eventTime=%" PRId64, + mChannel->getName().c_str(), __func__, seq, eventId, deviceId, + inputEventSourceToString(source).c_str(), KeyEvent::actionToString(action), flags, + KeyEvent::getLabel(keyCode), scanCode, metaState, repeatCount, downTime, eventTime); if (!seq) { ALOGE("Attempted to publish a key event with sequence number 0."); @@ -550,24 +596,29 @@ status_t InputPublisher::publishMotionEvent( uint32_t pointerCount, const PointerProperties* pointerProperties, const PointerCoords* pointerCoords) { if (ATRACE_ENABLED()) { - std::string message = StringPrintf( - "publishMotionEvent(inputChannel=%s, action=%" PRId32 ")", - mChannel->getName().c_str(), action); + std::string message = StringPrintf("publishMotionEvent(inputChannel=%s, action=%s)", + mChannel->getName().c_str(), + MotionEvent::actionToString(action).c_str()); ATRACE_NAME(message.c_str()); } - if (DEBUG_TRANSPORT_ACTIONS) { + if (verifyEvents()) { + mInputVerifier.processMovement(deviceId, action, pointerCount, pointerProperties, + pointerCoords, flags); + } + if (debugTransportPublisher()) { std::string transformString; transform.dump(transformString, "transform", " "); - ALOGD("channel '%s' publisher ~ publishMotionEvent: seq=%u, deviceId=%d, source=0x%x, " + ALOGD("channel '%s' publisher ~ %s: seq=%u, id=%d, deviceId=%d, source=%s, " "displayId=%" PRId32 ", " - "action=0x%x, actionButton=0x%08x, flags=0x%x, edgeFlags=0x%x, " + "action=%s, actionButton=0x%08x, flags=0x%x, edgeFlags=0x%x, " "metaState=0x%x, buttonState=0x%x, classification=%s," "xPrecision=%f, yPrecision=%f, downTime=%" PRId64 ", eventTime=%" PRId64 ", " "pointerCount=%" PRIu32 " \n%s", - mChannel->getName().c_str(), seq, deviceId, source, displayId, action, actionButton, - flags, edgeFlags, metaState, buttonState, - motionClassificationToString(classification), xPrecision, yPrecision, downTime, - eventTime, pointerCount, transformString.c_str()); + mChannel->getName().c_str(), __func__, seq, eventId, deviceId, + inputEventSourceToString(source).c_str(), displayId, + MotionEvent::actionToString(action).c_str(), actionButton, flags, edgeFlags, + metaState, buttonState, motionClassificationToString(classification), xPrecision, + yPrecision, downTime, eventTime, pointerCount, transformString.c_str()); } if (!seq) { @@ -629,6 +680,8 @@ status_t InputPublisher::publishFocusEvent(uint32_t seq, int32_t eventId, bool h mChannel->getName().c_str(), toString(hasFocus)); ATRACE_NAME(message.c_str()); } + ALOGD_IF(debugTransportPublisher(), "channel '%s' publisher ~ %s: seq=%u, id=%d, hasFocus=%s", + mChannel->getName().c_str(), __func__, seq, eventId, toString(hasFocus)); InputMessage msg; msg.header.type = InputMessage::Type::FOCUS; @@ -646,6 +699,9 @@ status_t InputPublisher::publishCaptureEvent(uint32_t seq, int32_t eventId, mChannel->getName().c_str(), toString(pointerCaptureEnabled)); ATRACE_NAME(message.c_str()); } + ALOGD_IF(debugTransportPublisher(), + "channel '%s' publisher ~ %s: seq=%u, id=%d, pointerCaptureEnabled=%s", + mChannel->getName().c_str(), __func__, seq, eventId, toString(pointerCaptureEnabled)); InputMessage msg; msg.header.type = InputMessage::Type::CAPTURE; @@ -663,6 +719,9 @@ status_t InputPublisher::publishDragEvent(uint32_t seq, int32_t eventId, float x mChannel->getName().c_str(), x, y, toString(isExiting)); ATRACE_NAME(message.c_str()); } + ALOGD_IF(debugTransportPublisher(), + "channel '%s' publisher ~ %s: seq=%u, id=%d, x=%f, y=%f, isExiting=%s", + mChannel->getName().c_str(), __func__, seq, eventId, x, y, toString(isExiting)); InputMessage msg; msg.header.type = InputMessage::Type::DRAG; @@ -681,6 +740,9 @@ status_t InputPublisher::publishTouchModeEvent(uint32_t seq, int32_t eventId, bo mChannel->getName().c_str(), toString(isInTouchMode)); ATRACE_NAME(message.c_str()); } + ALOGD_IF(debugTransportPublisher(), + "channel '%s' publisher ~ %s: seq=%u, id=%d, isInTouchMode=%s", + mChannel->getName().c_str(), __func__, seq, eventId, toString(isInTouchMode)); InputMessage msg; msg.header.type = InputMessage::Type::TOUCH_MODE; @@ -691,16 +753,18 @@ status_t InputPublisher::publishTouchModeEvent(uint32_t seq, int32_t eventId, bo } android::base::Result<InputPublisher::ConsumerResponse> InputPublisher::receiveConsumerResponse() { - if (DEBUG_TRANSPORT_ACTIONS) { - ALOGD("channel '%s' publisher ~ %s", mChannel->getName().c_str(), __func__); - } - InputMessage msg; status_t result = mChannel->receiveMessage(&msg); if (result) { + ALOGD_IF(debugTransportPublisher(), "channel '%s' publisher ~ %s: %s", + mChannel->getName().c_str(), __func__, strerror(result)); return android::base::Error(result); } if (msg.header.type == InputMessage::Type::FINISHED) { + ALOGD_IF(debugTransportPublisher(), + "channel '%s' publisher ~ %s: finished: seq=%u, handled=%s", + mChannel->getName().c_str(), __func__, msg.header.seq, + toString(msg.body.finished.handled)); return Finished{ .seq = msg.header.seq, .handled = msg.body.finished.handled, @@ -709,6 +773,8 @@ android::base::Result<InputPublisher::ConsumerResponse> InputPublisher::receiveC } if (msg.header.type == InputMessage::Type::TIMELINE) { + ALOGD_IF(debugTransportPublisher(), "channel '%s' publisher ~ %s: timeline: id=%d", + mChannel->getName().c_str(), __func__, msg.body.timeline.eventId); return Timeline{ .inputEventId = msg.body.timeline.eventId, .graphicsTimeline = msg.body.timeline.graphicsTimeline, @@ -738,10 +804,9 @@ bool InputConsumer::isTouchResamplingEnabled() { status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches, nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) { - if (DEBUG_TRANSPORT_ACTIONS) { - ALOGD("channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%" PRId64, - mChannel->getName().c_str(), toString(consumeBatches), frameTime); - } + ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, + "channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%" PRId64, + mChannel->getName().c_str(), toString(consumeBatches), frameTime); *outSeq = 0; *outEvent = nullptr; @@ -767,10 +832,9 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum if (consumeBatches || result != WOULD_BLOCK) { result = consumeBatch(factory, frameTime, outSeq, outEvent); if (*outEvent) { - if (DEBUG_TRANSPORT_ACTIONS) { - ALOGD("channel '%s' consumer ~ consumed batch event, seq=%u", - mChannel->getName().c_str(), *outSeq); - } + ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, + "channel '%s' consumer ~ consumed batch event, seq=%u", + mChannel->getName().c_str(), *outSeq); break; } } @@ -786,11 +850,10 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum initializeKeyEvent(keyEvent, &mMsg); *outSeq = mMsg.header.seq; *outEvent = keyEvent; - if (DEBUG_TRANSPORT_ACTIONS) { - ALOGD("channel '%s' consumer ~ consumed key event, seq=%u", - mChannel->getName().c_str(), *outSeq); - } - break; + ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, + "channel '%s' consumer ~ consumed key event, seq=%u", + mChannel->getName().c_str(), *outSeq); + break; } case InputMessage::Type::MOTION: { @@ -799,11 +862,10 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum Batch& batch = mBatches[batchIndex]; if (canAddSample(batch, &mMsg)) { batch.samples.push_back(mMsg); - if (DEBUG_TRANSPORT_ACTIONS) { - ALOGD("channel '%s' consumer ~ appended to batch event", - mChannel->getName().c_str()); - } - break; + ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, + "channel '%s' consumer ~ appended to batch event", + mChannel->getName().c_str()); + break; } else if (isPointerEvent(mMsg.body.motion.source) && mMsg.body.motion.action == AMOTION_EVENT_ACTION_CANCEL) { // No need to process events that we are going to cancel anyways @@ -824,12 +886,11 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum if (result) { return result; } - if (DEBUG_TRANSPORT_ACTIONS) { - ALOGD("channel '%s' consumer ~ consumed batch event and " - "deferred current event, seq=%u", - mChannel->getName().c_str(), *outSeq); - } - break; + ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, + "channel '%s' consumer ~ consumed batch event and " + "deferred current event, seq=%u", + mChannel->getName().c_str(), *outSeq); + break; } } @@ -839,10 +900,9 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum Batch batch; batch.samples.push_back(mMsg); mBatches.push_back(batch); - if (DEBUG_TRANSPORT_ACTIONS) { - ALOGD("channel '%s' consumer ~ started batch event", - mChannel->getName().c_str()); - } + ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, + "channel '%s' consumer ~ started batch event", + mChannel->getName().c_str()); break; } @@ -854,10 +914,9 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum *outSeq = mMsg.header.seq; *outEvent = motionEvent; - if (DEBUG_TRANSPORT_ACTIONS) { - ALOGD("channel '%s' consumer ~ consumed motion event, seq=%u", - mChannel->getName().c_str(), *outSeq); - } + ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, + "channel '%s' consumer ~ consumed motion event, seq=%u", + mChannel->getName().c_str(), *outSeq); break; } @@ -1074,11 +1133,9 @@ void InputConsumer::rewriteMessage(TouchState& state, InputMessage& msg) { state.recentCoordinatesAreIdentical(id)) { PointerCoords& msgCoords = msg.body.motion.pointers[i].coords; const PointerCoords& resampleCoords = state.lastResample.getPointerById(id); -#if DEBUG_RESAMPLING - ALOGD("[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id, - resampleCoords.getX(), resampleCoords.getY(), - msgCoords.getX(), msgCoords.getY()); -#endif + ALOGD_IF(DEBUG_RESAMPLING, "[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id, + resampleCoords.getX(), resampleCoords.getY(), msgCoords.getX(), + msgCoords.getY()); msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX()); msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY()); msgCoords.isResampled = true; @@ -1099,17 +1156,13 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, ssize_t index = findTouchState(event->getDeviceId(), event->getSource()); if (index < 0) { -#if DEBUG_RESAMPLING - ALOGD("Not resampled, no touch state for device."); -#endif + ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, no touch state for device."); return; } TouchState& touchState = mTouchStates[index]; if (touchState.historySize < 1) { -#if DEBUG_RESAMPLING - ALOGD("Not resampled, no history for device."); -#endif + ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, no history for device."); return; } @@ -1119,9 +1172,7 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, for (size_t i = 0; i < pointerCount; i++) { uint32_t id = event->getPointerId(i); if (!current->idBits.hasBit(id)) { -#if DEBUG_RESAMPLING - ALOGD("Not resampled, missing id %d", id); -#endif + ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, missing id %d", id); return; } } @@ -1137,9 +1188,8 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, other = &future; nsecs_t delta = future.eventTime - current->eventTime; if (delta < RESAMPLE_MIN_DELTA) { -#if DEBUG_RESAMPLING - ALOGD("Not resampled, delta time is too small: %" PRId64 " ns.", delta); -#endif + ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, delta time is too small: %" PRId64 " ns.", + delta); return; } alpha = float(sampleTime - current->eventTime) / delta; @@ -1149,30 +1199,25 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, other = touchState.getHistory(1); nsecs_t delta = current->eventTime - other->eventTime; if (delta < RESAMPLE_MIN_DELTA) { -#if DEBUG_RESAMPLING - ALOGD("Not resampled, delta time is too small: %" PRId64 " ns.", delta); -#endif + ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, delta time is too small: %" PRId64 " ns.", + delta); return; } else if (delta > RESAMPLE_MAX_DELTA) { -#if DEBUG_RESAMPLING - ALOGD("Not resampled, delta time is too large: %" PRId64 " ns.", delta); -#endif + ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, delta time is too large: %" PRId64 " ns.", + delta); return; } nsecs_t maxPredict = current->eventTime + min(delta / 2, RESAMPLE_MAX_PREDICTION); if (sampleTime > maxPredict) { -#if DEBUG_RESAMPLING - ALOGD("Sample time is too far in the future, adjusting prediction " - "from %" PRId64 " to %" PRId64 " ns.", - sampleTime - current->eventTime, maxPredict - current->eventTime); -#endif + ALOGD_IF(DEBUG_RESAMPLING, + "Sample time is too far in the future, adjusting prediction " + "from %" PRId64 " to %" PRId64 " ns.", + sampleTime - current->eventTime, maxPredict - current->eventTime); sampleTime = maxPredict; } alpha = float(current->eventTime - sampleTime) / delta; } else { -#if DEBUG_RESAMPLING - ALOGD("Not resampled, insufficient data."); -#endif + ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, insufficient data."); return; } @@ -1207,28 +1252,22 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, PointerCoords& resampledCoords = touchState.lastResample.pointers[i]; const PointerCoords& currentCoords = current->getPointerById(id); resampledCoords.copyFrom(currentCoords); - if (other->idBits.hasBit(id) - && shouldResampleTool(event->getToolType(i))) { + if (other->idBits.hasBit(id) && shouldResampleTool(event->getToolType(i))) { const PointerCoords& otherCoords = other->getPointerById(id); resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X, - lerp(currentCoords.getX(), otherCoords.getX(), alpha)); + lerp(currentCoords.getX(), otherCoords.getX(), alpha)); resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, - lerp(currentCoords.getY(), otherCoords.getY(), alpha)); + lerp(currentCoords.getY(), otherCoords.getY(), alpha)); resampledCoords.isResampled = true; -#if DEBUG_RESAMPLING - ALOGD("[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), " - "other (%0.3f, %0.3f), alpha %0.3f", - id, resampledCoords.getX(), resampledCoords.getY(), - currentCoords.getX(), currentCoords.getY(), - otherCoords.getX(), otherCoords.getY(), - alpha); -#endif + ALOGD_IF(DEBUG_RESAMPLING, + "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), " + "other (%0.3f, %0.3f), alpha %0.3f", + id, resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(), + currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha); } else { -#if DEBUG_RESAMPLING - ALOGD("[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)", - id, resampledCoords.getX(), resampledCoords.getY(), - currentCoords.getX(), currentCoords.getY()); -#endif + ALOGD_IF(DEBUG_RESAMPLING, "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)", id, + resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(), + currentCoords.getY()); } } @@ -1241,10 +1280,9 @@ bool InputConsumer::shouldResampleTool(int32_t toolType) { } status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) { - if (DEBUG_TRANSPORT_ACTIONS) { - ALOGD("channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s", - mChannel->getName().c_str(), seq, toString(handled)); - } + ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, + "channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s", + mChannel->getName().c_str(), seq, toString(handled)); if (!seq) { ALOGE("Attempted to send a finished signal with sequence number 0."); @@ -1291,13 +1329,12 @@ status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) { status_t InputConsumer::sendTimeline(int32_t inputEventId, std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline) { - if (DEBUG_TRANSPORT_ACTIONS) { - ALOGD("channel '%s' consumer ~ sendTimeline: inputEventId=%" PRId32 - ", gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64, - mChannel->getName().c_str(), inputEventId, - graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME], - graphicsTimeline[GraphicsTimeline::PRESENT_TIME]); - } + ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, + "channel '%s' consumer ~ sendTimeline: inputEventId=%" PRId32 + ", gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64, + mChannel->getName().c_str(), inputEventId, + graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME], + graphicsTimeline[GraphicsTimeline::PRESENT_TIME]); InputMessage msg; msg.header.type = InputMessage::Type::TIMELINE; diff --git a/libs/input/InputVerifier.cpp b/libs/input/InputVerifier.cpp new file mode 100644 index 0000000000..eb758045cc --- /dev/null +++ b/libs/input/InputVerifier.cpp @@ -0,0 +1,128 @@ +/* + * 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 "InputVerifier" + +#include <android-base/logging.h> +#include <input/InputVerifier.h> + +namespace android { + +/** + * Log all of the movements that are sent to this verifier. Helps to identify the streams that lead + * to inconsistent events. + * Enable this via "adb shell setprop log.tag.InputVerifierLogEvents DEBUG" + */ +static bool logEvents() { + return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "LogEvents", ANDROID_LOG_INFO); +} + +// --- InputVerifier --- + +InputVerifier::InputVerifier(const std::string& name) : mName(name){}; + +void InputVerifier::processMovement(int32_t deviceId, int32_t action, uint32_t pointerCount, + const PointerProperties* pointerProperties, + const PointerCoords* pointerCoords, int32_t flags) { + if (logEvents()) { + LOG(ERROR) << "Processing " << MotionEvent::actionToString(action) << " for device " + << deviceId << " (" << pointerCount << " pointer" + << (pointerCount == 1 ? "" : "s") << ") on " << mName; + } + + switch (MotionEvent::getActionMasked(action)) { + case AMOTION_EVENT_ACTION_DOWN: { + auto [it, inserted] = mTouchingPointerIdsByDevice.insert({deviceId, {}}); + if (!inserted) { + LOG(FATAL) << "Got ACTION_DOWN, but already have touching pointers " << it->second + << " for device " << deviceId << " on " << mName; + } + it->second.set(pointerProperties[0].id); + break; + } + case AMOTION_EVENT_ACTION_POINTER_DOWN: { + auto it = mTouchingPointerIdsByDevice.find(deviceId); + if (it == mTouchingPointerIdsByDevice.end()) { + LOG(FATAL) << "Got POINTER_DOWN, but no touching pointers for device " << deviceId + << " on " << mName; + } + it->second.set(pointerProperties[MotionEvent::getActionIndex(action)].id); + break; + } + case AMOTION_EVENT_ACTION_MOVE: { + ensureTouchingPointersMatch(deviceId, pointerCount, pointerProperties, "MOVE"); + break; + } + case AMOTION_EVENT_ACTION_POINTER_UP: { + auto it = mTouchingPointerIdsByDevice.find(deviceId); + if (it == mTouchingPointerIdsByDevice.end()) { + LOG(FATAL) << "Got POINTER_UP, but no touching pointers for device " << deviceId + << " on " << mName; + } + it->second.reset(pointerProperties[MotionEvent::getActionIndex(action)].id); + break; + } + case AMOTION_EVENT_ACTION_UP: { + auto it = mTouchingPointerIdsByDevice.find(deviceId); + if (it == mTouchingPointerIdsByDevice.end()) { + LOG(FATAL) << "Got ACTION_UP, but no record for deviceId " << deviceId << " on " + << mName; + } + const auto& [_, touchingPointerIds] = *it; + if (touchingPointerIds.count() != 1) { + LOG(FATAL) << "Got ACTION_UP, but we have pointers: " << touchingPointerIds + << " for deviceId " << deviceId << " on " << mName; + } + const int32_t pointerId = pointerProperties[0].id; + if (!touchingPointerIds.test(pointerId)) { + LOG(FATAL) << "Got ACTION_UP, but pointerId " << pointerId + << " is not touching. Touching pointers: " << touchingPointerIds + << " for deviceId " << deviceId << " on " << mName; + } + mTouchingPointerIdsByDevice.erase(it); + break; + } + case AMOTION_EVENT_ACTION_CANCEL: { + if ((flags & AMOTION_EVENT_FLAG_CANCELED) != AMOTION_EVENT_FLAG_CANCELED) { + LOG(FATAL) << "For ACTION_CANCEL, must set FLAG_CANCELED"; + } + ensureTouchingPointersMatch(deviceId, pointerCount, pointerProperties, "CANCEL"); + mTouchingPointerIdsByDevice.erase(deviceId); + break; + } + } +} + +void InputVerifier::ensureTouchingPointersMatch(int32_t deviceId, uint32_t pointerCount, + const PointerProperties* pointerProperties, + const char* action) const { + auto it = mTouchingPointerIdsByDevice.find(deviceId); + if (it == mTouchingPointerIdsByDevice.end()) { + LOG(FATAL) << "Got " << action << ", but no touching pointers for device " << deviceId + << " on " << mName; + } + const auto& [_, touchingPointerIds] = *it; + for (size_t i = 0; i < pointerCount; i++) { + const int32_t pointerId = pointerProperties[i].id; + if (!touchingPointerIds.test(pointerId)) { + LOG(FATAL) << "Got " << action << " for pointerId " << pointerId + << " but the touching pointers are " << touchingPointerIds << " on " + << mName; + } + } +}; + +} // namespace android diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp index 0f889e8128..0d4213c745 100644 --- a/libs/input/MotionPredictor.cpp +++ b/libs/input/MotionPredictor.cpp @@ -25,9 +25,9 @@ #include <string> #include <vector> +#include <android-base/logging.h> #include <android-base/strings.h> #include <android/input.h> -#include <log/log.h> #include <attestation/HmacKeyManager.h> #include <input/TfLiteMotionPredictor.h> @@ -35,7 +35,6 @@ 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. @@ -62,44 +61,58 @@ TfLiteMotionPredictorSample::Point convertPrediction( // --- MotionPredictor --- -MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos, const char* modelPath, +MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos, std::function<bool()> checkMotionPredictionEnabled) : mPredictionTimestampOffsetNanos(predictionTimestampOffsetNanos), - mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)), - mModel(TfLiteMotionPredictorModel::create(modelPath == nullptr ? DEFAULT_MODEL_PATH - : modelPath)) {} - -void MotionPredictor::record(const MotionEvent& event) { + mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)) {} + +android::base::Result<void> MotionPredictor::record(const MotionEvent& event) { + if (mLastEvent && mLastEvent->getDeviceId() != event.getDeviceId()) { + // We still have an active gesture for another device. The provided MotionEvent is not + // consistent the previous gesture. + LOG(ERROR) << "Inconsistent event stream: last event is " << *mLastEvent << ", but " + << __func__ << " is called with " << event; + return android::base::Error() + << "Inconsistent event stream: still have an active gesture from device " + << mLastEvent->getDeviceId() << ", but received " << event; + } if (!isPredictionAvailable(event.getDeviceId(), event.getSource())) { ALOGE("Prediction not supported for device %d's %s source", event.getDeviceId(), inputEventSourceToString(event.getSource()).c_str()); - return; + return {}; } - TfLiteMotionPredictorBuffers& buffers = - mDeviceBuffers.try_emplace(event.getDeviceId(), mModel->inputLength()).first->second; + // Initialise the model now that it's likely to be used. + if (!mModel) { + mModel = TfLiteMotionPredictorModel::create(); + } + + if (mBuffers == nullptr) { + mBuffers = std::make_unique<TfLiteMotionPredictorBuffers>(mModel->inputLength()); + } const int32_t action = event.getActionMasked(); - if (action == AMOTION_EVENT_ACTION_UP) { + if (action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_CANCEL) { ALOGD_IF(isDebug(), "End of event stream"); - buffers.reset(); - return; + mBuffers->reset(); + mLastEvent.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; + return {}; } if (event.getPointerCount() != 1) { ALOGD_IF(isDebug(), "Prediction not supported for multiple pointers"); - return; + 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; + return {}; } for (size_t i = 0; i <= event.getHistorySize(); ++i) { @@ -107,99 +120,98 @@ void MotionPredictor::record(const MotionEvent& event) { 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), - }); + mBuffers->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); + if (!mLastEvent) { + mLastEvent = MotionEvent(); + } + mLastEvent->copyFrom(&event, /*keepHistory=*/false); + return {}; } -std::vector<std::unique_ptr<MotionEvent>> MotionPredictor::predict(nsecs_t timestamp) { - std::vector<std::unique_ptr<MotionEvent>> predictions; +std::unique_ptr<MotionEvent> MotionPredictor::predict(nsecs_t timestamp) { + if (mBuffers == nullptr || !mBuffers->isReady()) { + return nullptr; + } - for (const auto& [deviceId, buffer] : mDeviceBuffers) { - if (!buffer.isReady()) { - continue; - } + LOG_ALWAYS_FATAL_IF(!mModel); + mBuffers->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 = mBuffers->axisFrom().position; + TfLiteMotionPredictorSample::Point axisTo = mBuffers->axisTo().position; + + if (isDebug()) { + 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()); + } - 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()); + LOG_ALWAYS_FATAL_IF(!mLastEvent); + const MotionEvent& event = *mLastEvent; + bool hasPredictions = false; + std::unique_ptr<MotionEvent> prediction = std::make_unique<MotionEvent>(); + int64_t predictionTime = mBuffers->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); } - 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)); - } + axisFrom = axisTo; + axisTo = point; + } + // TODO(b/266747511): Interpolate to futureTime? + if (!hasPredictions) { + return nullptr; } - return predictions; + return prediction; } bool MotionPredictor::isPredictionAvailable(int32_t /*deviceId*/, int32_t source) { diff --git a/libs/input/TfLiteMotionPredictor.cpp b/libs/input/TfLiteMotionPredictor.cpp index 1a337adf13..691e87c366 100644 --- a/libs/input/TfLiteMotionPredictor.cpp +++ b/libs/input/TfLiteMotionPredictor.cpp @@ -17,27 +17,32 @@ #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 <fstream> -#include <ios> -#include <iterator> #include <memory> #include <span> -#include <string> #include <type_traits> #include <utility> +#include <android-base/file.h> +#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/register.h" +#include "tensorflow/lite/kernels/builtin_op_kernels.h" #include "tensorflow/lite/model.h" +#include "tensorflow/lite/mutable_op_resolver.h" namespace android { namespace { @@ -56,6 +61,14 @@ constexpr char OUTPUT_R[] = "r"; constexpr char OUTPUT_PHI[] = "phi"; constexpr char OUTPUT_PRESSURE[] = "pressure"; +std::string getModelPath() { +#if defined(__ANDROID__) + return "/system/etc/motion_predictor_model.fb"; +#else + return base::GetExecutableDirectory() + "/motion_predictor_model.fb"; +#endif +} + // A TFLite ErrorReporter that logs to logcat. class LoggingErrorReporter : public tflite::ErrorReporter { public: @@ -102,15 +115,24 @@ void checkTensor(const TfLiteTensor* 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) { +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"); - mInputR.resize(inputLength); - mInputPhi.resize(inputLength); - mInputPressure.resize(inputLength); - mInputTilt.resize(inputLength); - mInputOrientation.resize(inputLength); } void TfLiteMotionPredictorBuffers::reset() { @@ -186,42 +208,51 @@ void TfLiteMotionPredictorBuffers::pushSample(int64_t timestamp, mAxisTo = sample; // Push the current sample onto the end of the input buffers. - mInputR.erase(mInputR.begin()); - mInputPhi.erase(mInputPhi.begin()); - mInputPressure.erase(mInputPressure.begin()); - mInputTilt.erase(mInputTilt.begin()); - mInputOrientation.erase(mInputOrientation.begin()); - - mInputR.push_back(r); - mInputPhi.push_back(phi); - mInputPressure.push_back(sample.pressure); - mInputTilt.push_back(sample.tilt); - mInputOrientation.push_back(orientation); + 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) { - std::ifstream f(modelPath, std::ios::binary); - LOG_ALWAYS_FATAL_IF(!f, "Could not read model from %s", modelPath); +std::unique_ptr<TfLiteMotionPredictorModel> TfLiteMotionPredictorModel::create() { + const std::string modelPath = getModelPath(); + const int fd = open(modelPath.c_str(), O_RDONLY); + if (fd == -1) { + PLOG(FATAL) << "Could not read model from " << modelPath; + } - std::string data; - data.assign(std::istreambuf_iterator<char>(f), std::istreambuf_iterator<char>()); + 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(data))); + new TfLiteMotionPredictorModel(std::move(modelBuffer))); } -TfLiteMotionPredictorModel::TfLiteMotionPredictorModel(std::string model) +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.length(), + mModel = tflite::FlatBufferModel::VerifyAndBuildFromBuffer(mFlatBuffer->data(), + mFlatBuffer->size(), /*extra_verifier=*/nullptr, mErrorReporter.get()); LOG_ALWAYS_FATAL_IF(!mModel); - tflite::ops::builtin::BuiltinOpResolver resolver; - tflite::InterpreterBuilder builder(*mModel, resolver); + auto resolver = createOpResolver(); + tflite::InterpreterBuilder builder(*mModel, *resolver); if (builder(&mInterpreter) != kTfLiteOk || !mInterpreter) { LOG_ALWAYS_FATAL("Failed to build interpreter"); @@ -233,6 +264,8 @@ TfLiteMotionPredictorModel::TfLiteMotionPredictorModel(std::string model) allocateTensors(); } +TfLiteMotionPredictorModel::~TfLiteMotionPredictorModel() {} + void TfLiteMotionPredictorModel::allocateTensors() { if (mRunner->AllocateTensors() != kTfLiteOk) { LOG_ALWAYS_FATAL("Failed to allocate tensors"); diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp index 3632914b93..ba266b3969 100644 --- a/libs/input/VelocityTracker.cpp +++ b/libs/input/VelocityTracker.cpp @@ -22,7 +22,6 @@ #include <math.h> #include <optional> -#include <android-base/stringprintf.h> #include <input/PrintTools.h> #include <input/VelocityTracker.h> #include <utils/BitSet.h> @@ -268,12 +267,8 @@ void VelocityTracker::addMovement(nsecs_t eventTime, int32_t pointerId, int32_t ", activePointerId=%s", eventTime, pointerId, toString(mActivePointerId).c_str()); - std::optional<Estimator> estimator = getEstimator(axis, pointerId); - ALOGD(" %d: axis=%d, position=%0.3f, " - "estimator (degree=%d, coeff=%s, confidence=%f)", - pointerId, axis, position, int((*estimator).degree), - vectorToString((*estimator).coeff.data(), (*estimator).degree + 1).c_str(), - (*estimator).confidence); + ALOGD(" %d: axis=%d, position=%0.3f, velocity=%s", pointerId, axis, position, + toString(getVelocity(axis, pointerId)).c_str()); } } @@ -349,9 +344,9 @@ void VelocityTracker::addMovement(const MotionEvent* event) { } std::optional<float> VelocityTracker::getVelocity(int32_t axis, int32_t pointerId) const { - std::optional<Estimator> estimator = getEstimator(axis, pointerId); - if (estimator && (*estimator).degree >= 1) { - return (*estimator).coeff[1]; + const auto& it = mConfiguredStrategies.find(axis); + if (it != mConfiguredStrategies.end()) { + return it->second->getVelocity(pointerId); } return {}; } @@ -374,57 +369,37 @@ VelocityTracker::ComputedVelocity VelocityTracker::getComputedVelocity(int32_t u return computedVelocity; } -std::optional<VelocityTracker::Estimator> VelocityTracker::getEstimator(int32_t axis, - int32_t pointerId) const { - const auto& it = mConfiguredStrategies.find(axis); - if (it == mConfiguredStrategies.end()) { - return std::nullopt; - } - return it->second->getEstimator(pointerId); -} - -// --- LeastSquaresVelocityTrackerStrategy --- - -LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(uint32_t degree, - Weighting weighting) - : mDegree(degree), mWeighting(weighting) {} - -LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() { -} - -void LeastSquaresVelocityTrackerStrategy::clearPointer(int32_t pointerId) { - mIndex.erase(pointerId); +void AccumulatingVelocityTrackerStrategy::clearPointer(int32_t pointerId) { mMovements.erase(pointerId); } -void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId, +void AccumulatingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId, float position) { - // If data for this pointer already exists, we have a valid entry at the position of - // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index - // to the next position in the circular buffer and write the new Movement there. Otherwise, - // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements - // for this pointer and write to the first position. - auto [movementIt, inserted] = mMovements.insert({pointerId, {}}); - auto [indexIt, _] = mIndex.insert({pointerId, 0}); - size_t& index = indexIt->second; - if (!inserted && movementIt->second[index].eventTime != eventTime) { + auto [movementIt, _] = mMovements.insert({pointerId, RingBuffer<Movement>(HISTORY_SIZE)}); + RingBuffer<Movement>& movements = movementIt->second; + const size_t size = movements.size(); + + if (size != 0 && movements[size - 1].eventTime == eventTime) { // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include // the new pointer. If the eventtimes for both events are identical, just update the data - // for this time. + // for this time (i.e. pop out the last element, and insert the updated movement). // We only compare against the last value, as it is likely that addMovement is called // in chronological order as events occur. - index++; - } - if (index == HISTORY_SIZE) { - index = 0; + movements.popBack(); } - Movement& movement = movementIt->second[index]; - movement.eventTime = eventTime; - movement.position = position; + movements.pushBack({eventTime, position}); } +// --- LeastSquaresVelocityTrackerStrategy --- + +LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(uint32_t degree, + Weighting weighting) + : mDegree(degree), mWeighting(weighting) {} + +LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {} + /** * Solves a linear least squares problem to obtain a N degree polynomial that fits * the specified input data as nearly as possible. @@ -474,10 +449,9 @@ void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares * http://en.wikipedia.org/wiki/Gram-Schmidt */ -static bool solveLeastSquares(const std::vector<float>& x, const std::vector<float>& y, - const std::vector<float>& w, uint32_t n, - std::array<float, VelocityTracker::Estimator::MAX_DEGREE + 1>& outB, - float* outDet) { +static std::optional<float> solveLeastSquares(const std::vector<float>& x, + const std::vector<float>& y, + const std::vector<float>& w, uint32_t n) { const size_t m = x.size(); ALOGD_IF(DEBUG_STRATEGY, "solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n), @@ -515,7 +489,7 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo if (norm < 0.000001f) { // vectors are linearly dependent or zero so no solution ALOGD_IF(DEBUG_STRATEGY, " - no solution, norm=%f", norm); - return false; + return {}; } float invNorm = 1.0f / norm; @@ -549,6 +523,7 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo for (uint32_t h = 0; h < m; h++) { wy[h] = y[h] * w[h]; } + std::array<float, VelocityTracker::MAX_DEGREE + 1> outB; for (uint32_t i = n; i != 0; ) { i--; outB[i] = vectorDot(&q[i][0], wy, m); @@ -570,34 +545,33 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo } ymean /= m; - float sserr = 0; - float sstot = 0; - for (uint32_t h = 0; h < m; h++) { - float err = y[h] - outB[0]; - float term = 1; - for (uint32_t i = 1; i < n; i++) { - term *= x[h]; - err -= term * outB[i]; + if (DEBUG_STRATEGY) { + float sserr = 0; + float sstot = 0; + for (uint32_t h = 0; h < m; h++) { + float err = y[h] - outB[0]; + float term = 1; + for (uint32_t i = 1; i < n; i++) { + term *= x[h]; + err -= term * outB[i]; + } + sserr += w[h] * w[h] * err * err; + float var = y[h] - ymean; + sstot += w[h] * w[h] * var * var; } - sserr += w[h] * w[h] * err * err; - float var = y[h] - ymean; - sstot += w[h] * w[h] * var * var; + ALOGD(" - sserr=%f", sserr); + ALOGD(" - sstot=%f", sstot); } - *outDet = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1; - ALOGD_IF(DEBUG_STRATEGY, " - sserr=%f", sserr); - ALOGD_IF(DEBUG_STRATEGY, " - sstot=%f", sstot); - ALOGD_IF(DEBUG_STRATEGY, " - det=%f", *outDet); - - return true; + return outB[1]; } /* * Optimized unweighted second-order least squares fit. About 2x speed improvement compared to * the default implementation */ -static std::optional<std::array<float, 3>> solveUnweightedLeastSquaresDeg2( - const std::vector<float>& x, const std::vector<float>& y) { +static std::optional<float> solveUnweightedLeastSquaresDeg2(const std::vector<float>& x, + const std::vector<float>& y) { const size_t count = x.size(); LOG_ALWAYS_FATAL_IF(count != y.size(), "Mismatching array sizes"); // Solving y = a*x^2 + b*x + c @@ -632,57 +606,39 @@ static std::optional<std::array<float, 3>> solveUnweightedLeastSquaresDeg2( ALOGW("division by 0 when computing velocity, Sxx=%f, Sx2x2=%f, Sxx2=%f", Sxx, Sx2x2, Sxx2); return std::nullopt; } - // Compute a - float numerator = Sx2y*Sxx - Sxy*Sxx2; - float a = numerator / denominator; - - // Compute b - numerator = Sxy*Sx2x2 - Sx2y*Sxx2; - float b = numerator / denominator; - - // Compute c - float c = syi/count - b * sxi/count - a * sxi2/count; - return std::make_optional(std::array<float, 3>({c, b, a})); + return (Sxy * Sx2x2 - Sx2y * Sxx2) / denominator; } -std::optional<VelocityTracker::Estimator> LeastSquaresVelocityTrackerStrategy::getEstimator( - int32_t pointerId) const { +std::optional<float> LeastSquaresVelocityTrackerStrategy::getVelocity(int32_t pointerId) const { const auto movementIt = mMovements.find(pointerId); if (movementIt == mMovements.end()) { return std::nullopt; // no data } + + const RingBuffer<Movement>& movements = movementIt->second; + const size_t size = movements.size(); + if (size == 0) { + return std::nullopt; // no data + } + // Iterate over movement samples in reverse time order and collect samples. std::vector<float> positions; std::vector<float> w; std::vector<float> time; - uint32_t index = mIndex.at(pointerId); - const Movement& newestMovement = movementIt->second[index]; - do { - const Movement& movement = movementIt->second[index]; + const Movement& newestMovement = movements[size - 1]; + for (ssize_t i = size - 1; i >= 0; i--) { + const Movement& movement = movements[i]; nsecs_t age = newestMovement.eventTime - movement.eventTime; if (age > HORIZON) { break; } - if (movement.eventTime == 0 && index != 0) { - // All eventTime's are initialized to 0. In this fixed-width circular buffer, it's - // possible that not all entries are valid. We use a time=0 as a signal for those - // uninitialized values. If we encounter a time of 0 in a position - // that's > 0, it means that we hit the block where the data wasn't initialized. - // We still don't know whether the value at index=0, with eventTime=0 is valid. - // However, that's only possible when the value is by itself. So there's no hard in - // processing it anyways, since the velocity for a single point is zero, and this - // situation will only be encountered in artificial circumstances (in tests). - // In practice, time will never be 0. - break; - } positions.push_back(movement.position); - w.push_back(chooseWeight(pointerId, index)); + w.push_back(chooseWeight(pointerId, i)); time.push_back(-age * 0.000000001f); - index = (index == 0 ? HISTORY_SIZE : index) - 1; - } while (positions.size() < HISTORY_SIZE); + } const size_t m = positions.size(); if (m == 0) { @@ -695,61 +651,31 @@ std::optional<VelocityTracker::Estimator> LeastSquaresVelocityTrackerStrategy::g degree = m - 1; } + if (degree <= 0) { + return std::nullopt; + } if (degree == 2 && mWeighting == Weighting::NONE) { // Optimize unweighted, quadratic polynomial fit - std::optional<std::array<float, 3>> coeff = - solveUnweightedLeastSquaresDeg2(time, positions); - if (coeff) { - VelocityTracker::Estimator estimator; - estimator.time = newestMovement.eventTime; - estimator.degree = 2; - estimator.confidence = 1; - for (size_t i = 0; i <= estimator.degree; i++) { - estimator.coeff[i] = (*coeff)[i]; - } - return estimator; - } - } else if (degree >= 1) { - // General case for an Nth degree polynomial fit - float det; - uint32_t n = degree + 1; - VelocityTracker::Estimator estimator; - if (solveLeastSquares(time, positions, w, n, estimator.coeff, &det)) { - estimator.time = newestMovement.eventTime; - estimator.degree = degree; - estimator.confidence = det; - - ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, coeff=%s, confidence=%f", - int(estimator.degree), vectorToString(estimator.coeff.data(), n).c_str(), - estimator.confidence); - - return estimator; - } + return solveUnweightedLeastSquaresDeg2(time, positions); } - - // No velocity data available for this pointer, but we do have its current position. - VelocityTracker::Estimator estimator; - estimator.coeff[0] = positions[0]; - estimator.time = newestMovement.eventTime; - estimator.degree = 0; - estimator.confidence = 1; - return estimator; + // General case for an Nth degree polynomial fit + return solveLeastSquares(time, positions, w, degree + 1); } float LeastSquaresVelocityTrackerStrategy::chooseWeight(int32_t pointerId, uint32_t index) const { - const std::array<Movement, HISTORY_SIZE>& movements = mMovements.at(pointerId); + const RingBuffer<Movement>& movements = mMovements.at(pointerId); + const size_t size = movements.size(); switch (mWeighting) { case Weighting::DELTA: { // Weight points based on how much time elapsed between them and the next // point so that points that "cover" a shorter time span are weighed less. // delta 0ms: 0.5 // delta 10ms: 1.0 - if (index == mIndex.at(pointerId)) { + if (index == size - 1) { return 1.0f; } - uint32_t nextIndex = (index + 1) % HISTORY_SIZE; float deltaMillis = - (movements[nextIndex].eventTime - movements[index].eventTime) * 0.000001f; + (movements[index + 1].eventTime - movements[index].eventTime) * 0.000001f; if (deltaMillis < 0) { return 0.5f; } @@ -766,8 +692,7 @@ float LeastSquaresVelocityTrackerStrategy::chooseWeight(int32_t pointerId, uint3 // age 50ms: 1.0 // age 60ms: 0.5 float ageMillis = - (movements[mIndex.at(pointerId)].eventTime - movements[index].eventTime) * - 0.000001f; + (movements[size - 1].eventTime - movements[index].eventTime) * 0.000001f; if (ageMillis < 0) { return 0.5f; } @@ -789,8 +714,7 @@ float LeastSquaresVelocityTrackerStrategy::chooseWeight(int32_t pointerId, uint3 // age 50ms: 1.0 // age 100ms: 0.5 float ageMillis = - (movements[mIndex.at(pointerId)].eventTime - movements[index].eventTime) * - 0.000001f; + (movements[size - 1].eventTime - movements[index].eventTime) * 0.000001f; if (ageMillis < 50) { return 1.0f; } @@ -830,13 +754,9 @@ void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t mPointerIdBits.markBit(pointerId); } -std::optional<VelocityTracker::Estimator> IntegratingVelocityTrackerStrategy::getEstimator( - int32_t pointerId) const { +std::optional<float> IntegratingVelocityTrackerStrategy::getVelocity(int32_t pointerId) const { if (mPointerIdBits.hasBit(pointerId)) { - const State& state = mPointerState[pointerId]; - VelocityTracker::Estimator estimator; - populateEstimator(state, &estimator); - return estimator; + return mPointerState[pointerId].vel; } return std::nullopt; @@ -886,17 +806,6 @@ void IntegratingVelocityTrackerStrategy::updateState(State& state, nsecs_t event state.pos = pos; } -void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state, - VelocityTracker::Estimator* outEstimator) const { - outEstimator->time = state.updateTime; - outEstimator->confidence = 1.0f; - outEstimator->degree = state.degree; - outEstimator->coeff[0] = state.pos; - outEstimator->coeff[1] = state.vel; - outEstimator->coeff[2] = state.accel / 2; -} - - // --- LegacyVelocityTrackerStrategy --- LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() {} @@ -904,59 +813,30 @@ LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() {} LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() { } -void LegacyVelocityTrackerStrategy::clearPointer(int32_t pointerId) { - mIndex.erase(pointerId); - mMovements.erase(pointerId); -} - -void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId, - float position) { - // If data for this pointer already exists, we have a valid entry at the position of - // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index - // to the next position in the circular buffer and write the new Movement there. Otherwise, - // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements - // for this pointer and write to the first position. - auto [movementIt, inserted] = mMovements.insert({pointerId, {}}); - auto [indexIt, _] = mIndex.insert({pointerId, 0}); - size_t& index = indexIt->second; - if (!inserted && movementIt->second[index].eventTime != eventTime) { - // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates - // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include - // the new pointer. If the eventtimes for both events are identical, just update the data - // for this time. - // We only compare against the last value, as it is likely that addMovement is called - // in chronological order as events occur. - index++; - } - if (index == HISTORY_SIZE) { - index = 0; - } - - Movement& movement = movementIt->second[index]; - movement.eventTime = eventTime; - movement.position = position; -} - -std::optional<VelocityTracker::Estimator> LegacyVelocityTrackerStrategy::getEstimator( - int32_t pointerId) const { +std::optional<float> LegacyVelocityTrackerStrategy::getVelocity(int32_t pointerId) const { const auto movementIt = mMovements.find(pointerId); if (movementIt == mMovements.end()) { return std::nullopt; // no data } - const Movement& newestMovement = movementIt->second[mIndex.at(pointerId)]; + + const RingBuffer<Movement>& movements = movementIt->second; + const size_t size = movements.size(); + if (size == 0) { + return std::nullopt; // no data + } + + const Movement& newestMovement = movements[size - 1]; // Find the oldest sample that contains the pointer and that is not older than HORIZON. nsecs_t minTime = newestMovement.eventTime - HORIZON; - uint32_t oldestIndex = mIndex.at(pointerId); - uint32_t numTouches = 1; - do { - uint32_t nextOldestIndex = (oldestIndex == 0 ? HISTORY_SIZE : oldestIndex) - 1; - const Movement& nextOldestMovement = mMovements.at(pointerId)[nextOldestIndex]; + uint32_t oldestIndex = size - 1; + for (ssize_t i = size - 1; i >= 0; i--) { + const Movement& nextOldestMovement = movements[i]; if (nextOldestMovement.eventTime < minTime) { break; } - oldestIndex = nextOldestIndex; - } while (++numTouches < HISTORY_SIZE); + oldestIndex = i; + } // Calculate an exponentially weighted moving average of the velocity estimate // at different points in time measured relative to the oldest sample. @@ -970,17 +850,13 @@ std::optional<VelocityTracker::Estimator> LegacyVelocityTrackerStrategy::getEsti // 16ms apart but some consecutive samples could be only 0.5sm apart because // the hardware or driver reports them irregularly or in bursts. float accumV = 0; - uint32_t index = oldestIndex; uint32_t samplesUsed = 0; - const Movement& oldestMovement = mMovements.at(pointerId)[oldestIndex]; + const Movement& oldestMovement = movements[oldestIndex]; float oldestPosition = oldestMovement.position; nsecs_t lastDuration = 0; - while (numTouches-- > 1) { - if (++index == HISTORY_SIZE) { - index = 0; - } - const Movement& movement = mMovements.at(pointerId)[index]; + for (size_t i = oldestIndex; i < size; i++) { + const Movement& movement = movements[i]; nsecs_t duration = movement.eventTime - oldestMovement.eventTime; // If the duration between samples is small, we may significantly overestimate @@ -996,19 +872,10 @@ std::optional<VelocityTracker::Estimator> LegacyVelocityTrackerStrategy::getEsti } } - // Report velocity. - float newestPosition = newestMovement.position; - VelocityTracker::Estimator estimator; - estimator.time = newestMovement.eventTime; - estimator.confidence = 1; - estimator.coeff[0] = newestPosition; if (samplesUsed) { - estimator.coeff[1] = accumV; - estimator.degree = 1; - } else { - estimator.degree = 0; + return accumV; } - return estimator; + return std::nullopt; } // --- ImpulseVelocityTrackerStrategy --- @@ -1019,39 +886,6 @@ ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy(bool deltaValues) ImpulseVelocityTrackerStrategy::~ImpulseVelocityTrackerStrategy() { } -void ImpulseVelocityTrackerStrategy::clearPointer(int32_t pointerId) { - mIndex.erase(pointerId); - mMovements.erase(pointerId); -} - -void ImpulseVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId, - float position) { - // If data for this pointer already exists, we have a valid entry at the position of - // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index - // to the next position in the circular buffer and write the new Movement there. Otherwise, - // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements - // for this pointer and write to the first position. - auto [movementIt, inserted] = mMovements.insert({pointerId, {}}); - auto [indexIt, _] = mIndex.insert({pointerId, 0}); - size_t& index = indexIt->second; - if (!inserted && movementIt->second[index].eventTime != eventTime) { - // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates - // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include - // the new pointer. If the eventtimes for both events are identical, just update the data - // for this time. - // We only compare against the last value, as it is likely that addMovement is called - // in chronological order as events occur. - index++; - } - if (index == HISTORY_SIZE) { - index = 0; - } - - Movement& movement = movementIt->second[index]; - movement.eventTime = eventTime; - movement.position = position; -} - /** * Calculate the total impulse provided to the screen and the resulting velocity. * @@ -1177,53 +1011,42 @@ static float calculateImpulseVelocity(const nsecs_t* t, const float* x, size_t c return kineticEnergyToVelocity(work); } -std::optional<VelocityTracker::Estimator> ImpulseVelocityTrackerStrategy::getEstimator( - int32_t pointerId) const { +std::optional<float> ImpulseVelocityTrackerStrategy::getVelocity(int32_t pointerId) const { const auto movementIt = mMovements.find(pointerId); if (movementIt == mMovements.end()) { return std::nullopt; // no data } + const RingBuffer<Movement>& movements = movementIt->second; + const size_t size = movements.size(); + if (size == 0) { + return std::nullopt; // no data + } + // Iterate over movement samples in reverse time order and collect samples. float positions[HISTORY_SIZE]; nsecs_t time[HISTORY_SIZE]; size_t m = 0; // number of points that will be used for fitting - size_t index = mIndex.at(pointerId); - const Movement& newestMovement = movementIt->second[index]; - do { - const Movement& movement = movementIt->second[index]; + const Movement& newestMovement = movements[size - 1]; + for (ssize_t i = size - 1; i >= 0; i--) { + const Movement& movement = movements[i]; nsecs_t age = newestMovement.eventTime - movement.eventTime; if (age > HORIZON) { break; } - if (movement.eventTime == 0 && index != 0) { - // All eventTime's are initialized to 0. If we encounter a time of 0 in a position - // that's >0, it means that we hit the block where the data wasn't initialized. - // It's also possible that the sample at 0 would be invalid, but there's no harm in - // processing it, since it would be just a single point, and will only be encountered - // in artificial circumstances (in tests). - break; - } positions[m] = movement.position; time[m] = movement.eventTime; - index = (index == 0 ? HISTORY_SIZE : index) - 1; - } while (++m < HISTORY_SIZE); + m++; + } if (m == 0) { return std::nullopt; // no data } - VelocityTracker::Estimator estimator; - estimator.coeff[0] = 0; - estimator.coeff[1] = calculateImpulseVelocity(time, positions, m, mDeltaValues); - estimator.coeff[2] = 0; - - estimator.time = newestMovement.eventTime; - estimator.degree = 2; // similar results to 2nd degree fit - estimator.confidence = 1; - ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", estimator.coeff[1]); + const float velocity = calculateImpulseVelocity(time, positions, m, mDeltaValues); + ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", velocity); if (DEBUG_IMPULSE) { // TODO(b/134179997): delete this block once the switch to 'impulse' is complete. @@ -1240,7 +1063,7 @@ std::optional<VelocityTracker::Estimator> ImpulseVelocityTrackerStrategy::getEst ALOGD("lsq2 velocity: could not compute velocity"); } } - return estimator; + return velocity; } } // namespace android diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp new file mode 100644 index 0000000000..3c1f2b6b56 --- /dev/null +++ b/libs/input/VirtualInputDevice.cpp @@ -0,0 +1,378 @@ +/* + * 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 "VirtualInputDevice" + +#include <android/input.h> +#include <android/keycodes.h> +#include <fcntl.h> +#include <input/Input.h> +#include <input/VirtualInputDevice.h> +#include <linux/uinput.h> +#include <math.h> +#include <utils/Log.h> + +#include <map> +#include <string> + +using android::base::unique_fd; + +/** + * Log debug messages about native virtual input devices. + * Enable this via "adb shell setprop log.tag.VirtualInputDevice DEBUG" + */ +static bool isDebug() { + return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO); +} + +namespace android { +VirtualInputDevice::VirtualInputDevice(unique_fd fd) : mFd(std::move(fd)) {} +VirtualInputDevice::~VirtualInputDevice() { + ioctl(mFd, UI_DEV_DESTROY); +} + +bool VirtualInputDevice::writeInputEvent(uint16_t type, uint16_t code, int32_t value) { + struct input_event ev = {.type = type, .code = code, .value = value}; + return TEMP_FAILURE_RETRY(write(mFd, &ev, sizeof(struct input_event))) == sizeof(ev); +} + +/** Utility method to write keyboard key events or mouse button events. */ +bool VirtualInputDevice::writeEvKeyEvent(int32_t androidCode, int32_t androidAction, + const std::map<int, int>& evKeyCodeMapping, + const std::map<int, UinputAction>& actionMapping) { + auto evKeyCodeIterator = evKeyCodeMapping.find(androidCode); + if (evKeyCodeIterator == evKeyCodeMapping.end()) { + ALOGE("Unsupported native EV keycode for android code %d", androidCode); + return false; + } + auto actionIterator = actionMapping.find(androidAction); + if (actionIterator == actionMapping.end()) { + return false; + } + if (!writeInputEvent(EV_KEY, static_cast<uint16_t>(evKeyCodeIterator->second), + static_cast<int32_t>(actionIterator->second))) { + return false; + } + if (!writeInputEvent(EV_SYN, SYN_REPORT, 0)) { + return false; + } + return true; +} + +// --- VirtualKeyboard --- +const std::map<int, UinputAction> VirtualKeyboard::KEY_ACTION_MAPPING = { + {AKEY_EVENT_ACTION_DOWN, UinputAction::PRESS}, + {AKEY_EVENT_ACTION_UP, UinputAction::RELEASE}, +}; +// Keycode mapping from https://source.android.com/devices/input/keyboard-devices +const std::map<int, int> VirtualKeyboard::KEY_CODE_MAPPING = { + {AKEYCODE_0, KEY_0}, + {AKEYCODE_1, KEY_1}, + {AKEYCODE_2, KEY_2}, + {AKEYCODE_3, KEY_3}, + {AKEYCODE_4, KEY_4}, + {AKEYCODE_5, KEY_5}, + {AKEYCODE_6, KEY_6}, + {AKEYCODE_7, KEY_7}, + {AKEYCODE_8, KEY_8}, + {AKEYCODE_9, KEY_9}, + {AKEYCODE_A, KEY_A}, + {AKEYCODE_B, KEY_B}, + {AKEYCODE_C, KEY_C}, + {AKEYCODE_D, KEY_D}, + {AKEYCODE_E, KEY_E}, + {AKEYCODE_F, KEY_F}, + {AKEYCODE_G, KEY_G}, + {AKEYCODE_H, KEY_H}, + {AKEYCODE_I, KEY_I}, + {AKEYCODE_J, KEY_J}, + {AKEYCODE_K, KEY_K}, + {AKEYCODE_L, KEY_L}, + {AKEYCODE_M, KEY_M}, + {AKEYCODE_N, KEY_N}, + {AKEYCODE_O, KEY_O}, + {AKEYCODE_P, KEY_P}, + {AKEYCODE_Q, KEY_Q}, + {AKEYCODE_R, KEY_R}, + {AKEYCODE_S, KEY_S}, + {AKEYCODE_T, KEY_T}, + {AKEYCODE_U, KEY_U}, + {AKEYCODE_V, KEY_V}, + {AKEYCODE_W, KEY_W}, + {AKEYCODE_X, KEY_X}, + {AKEYCODE_Y, KEY_Y}, + {AKEYCODE_Z, KEY_Z}, + {AKEYCODE_GRAVE, KEY_GRAVE}, + {AKEYCODE_MINUS, KEY_MINUS}, + {AKEYCODE_EQUALS, KEY_EQUAL}, + {AKEYCODE_LEFT_BRACKET, KEY_LEFTBRACE}, + {AKEYCODE_RIGHT_BRACKET, KEY_RIGHTBRACE}, + {AKEYCODE_BACKSLASH, KEY_BACKSLASH}, + {AKEYCODE_SEMICOLON, KEY_SEMICOLON}, + {AKEYCODE_APOSTROPHE, KEY_APOSTROPHE}, + {AKEYCODE_COMMA, KEY_COMMA}, + {AKEYCODE_PERIOD, KEY_DOT}, + {AKEYCODE_SLASH, KEY_SLASH}, + {AKEYCODE_ALT_LEFT, KEY_LEFTALT}, + {AKEYCODE_ALT_RIGHT, KEY_RIGHTALT}, + {AKEYCODE_CTRL_LEFT, KEY_LEFTCTRL}, + {AKEYCODE_CTRL_RIGHT, KEY_RIGHTCTRL}, + {AKEYCODE_SHIFT_LEFT, KEY_LEFTSHIFT}, + {AKEYCODE_SHIFT_RIGHT, KEY_RIGHTSHIFT}, + {AKEYCODE_META_LEFT, KEY_LEFTMETA}, + {AKEYCODE_META_RIGHT, KEY_RIGHTMETA}, + {AKEYCODE_CAPS_LOCK, KEY_CAPSLOCK}, + {AKEYCODE_SCROLL_LOCK, KEY_SCROLLLOCK}, + {AKEYCODE_NUM_LOCK, KEY_NUMLOCK}, + {AKEYCODE_ENTER, KEY_ENTER}, + {AKEYCODE_TAB, KEY_TAB}, + {AKEYCODE_SPACE, KEY_SPACE}, + {AKEYCODE_DPAD_DOWN, KEY_DOWN}, + {AKEYCODE_DPAD_UP, KEY_UP}, + {AKEYCODE_DPAD_LEFT, KEY_LEFT}, + {AKEYCODE_DPAD_RIGHT, KEY_RIGHT}, + {AKEYCODE_MOVE_END, KEY_END}, + {AKEYCODE_MOVE_HOME, KEY_HOME}, + {AKEYCODE_PAGE_DOWN, KEY_PAGEDOWN}, + {AKEYCODE_PAGE_UP, KEY_PAGEUP}, + {AKEYCODE_DEL, KEY_BACKSPACE}, + {AKEYCODE_FORWARD_DEL, KEY_DELETE}, + {AKEYCODE_INSERT, KEY_INSERT}, + {AKEYCODE_ESCAPE, KEY_ESC}, + {AKEYCODE_BREAK, KEY_PAUSE}, + {AKEYCODE_F1, KEY_F1}, + {AKEYCODE_F2, KEY_F2}, + {AKEYCODE_F3, KEY_F3}, + {AKEYCODE_F4, KEY_F4}, + {AKEYCODE_F5, KEY_F5}, + {AKEYCODE_F6, KEY_F6}, + {AKEYCODE_F7, KEY_F7}, + {AKEYCODE_F8, KEY_F8}, + {AKEYCODE_F9, KEY_F9}, + {AKEYCODE_F10, KEY_F10}, + {AKEYCODE_F11, KEY_F11}, + {AKEYCODE_F12, KEY_F12}, + {AKEYCODE_BACK, KEY_BACK}, + {AKEYCODE_FORWARD, KEY_FORWARD}, + {AKEYCODE_NUMPAD_1, KEY_KP1}, + {AKEYCODE_NUMPAD_2, KEY_KP2}, + {AKEYCODE_NUMPAD_3, KEY_KP3}, + {AKEYCODE_NUMPAD_4, KEY_KP4}, + {AKEYCODE_NUMPAD_5, KEY_KP5}, + {AKEYCODE_NUMPAD_6, KEY_KP6}, + {AKEYCODE_NUMPAD_7, KEY_KP7}, + {AKEYCODE_NUMPAD_8, KEY_KP8}, + {AKEYCODE_NUMPAD_9, KEY_KP9}, + {AKEYCODE_NUMPAD_0, KEY_KP0}, + {AKEYCODE_NUMPAD_ADD, KEY_KPPLUS}, + {AKEYCODE_NUMPAD_SUBTRACT, KEY_KPMINUS}, + {AKEYCODE_NUMPAD_MULTIPLY, KEY_KPASTERISK}, + {AKEYCODE_NUMPAD_DIVIDE, KEY_KPSLASH}, + {AKEYCODE_NUMPAD_DOT, KEY_KPDOT}, + {AKEYCODE_NUMPAD_ENTER, KEY_KPENTER}, + {AKEYCODE_NUMPAD_EQUALS, KEY_KPEQUAL}, + {AKEYCODE_NUMPAD_COMMA, KEY_KPCOMMA}, +}; +VirtualKeyboard::VirtualKeyboard(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} +VirtualKeyboard::~VirtualKeyboard() {} + +bool VirtualKeyboard::writeKeyEvent(int32_t androidKeyCode, int32_t androidAction) { + return writeEvKeyEvent(androidKeyCode, androidAction, KEY_CODE_MAPPING, KEY_ACTION_MAPPING); +} + +// --- VirtualDpad --- +// Dpad keycode mapping from https://source.android.com/devices/input/keyboard-devices +const std::map<int, int> VirtualDpad::DPAD_KEY_CODE_MAPPING = { + // clang-format off + {AKEYCODE_DPAD_DOWN, KEY_DOWN}, + {AKEYCODE_DPAD_UP, KEY_UP}, + {AKEYCODE_DPAD_LEFT, KEY_LEFT}, + {AKEYCODE_DPAD_RIGHT, KEY_RIGHT}, + {AKEYCODE_DPAD_CENTER, KEY_SELECT}, + {AKEYCODE_BACK, KEY_BACK}, + // clang-format on +}; + +VirtualDpad::VirtualDpad(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} + +VirtualDpad::~VirtualDpad() {} + +bool VirtualDpad::writeDpadKeyEvent(int32_t androidKeyCode, int32_t androidAction) { + return writeEvKeyEvent(androidKeyCode, androidAction, DPAD_KEY_CODE_MAPPING, + VirtualKeyboard::KEY_ACTION_MAPPING); +} + +// --- VirtualMouse --- +const std::map<int, UinputAction> VirtualMouse::BUTTON_ACTION_MAPPING = { + {AMOTION_EVENT_ACTION_BUTTON_PRESS, UinputAction::PRESS}, + {AMOTION_EVENT_ACTION_BUTTON_RELEASE, UinputAction::RELEASE}, +}; + +// Button code mapping from https://source.android.com/devices/input/touch-devices +const std::map<int, int> VirtualMouse::BUTTON_CODE_MAPPING = { + // clang-format off + {AMOTION_EVENT_BUTTON_PRIMARY, BTN_LEFT}, + {AMOTION_EVENT_BUTTON_SECONDARY, BTN_RIGHT}, + {AMOTION_EVENT_BUTTON_TERTIARY, BTN_MIDDLE}, + {AMOTION_EVENT_BUTTON_BACK, BTN_BACK}, + {AMOTION_EVENT_BUTTON_FORWARD, BTN_FORWARD}, + // clang-format on +}; + +VirtualMouse::VirtualMouse(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} + +VirtualMouse::~VirtualMouse() {} + +bool VirtualMouse::writeButtonEvent(int32_t androidButtonCode, int32_t androidAction) { + return writeEvKeyEvent(androidButtonCode, androidAction, BUTTON_CODE_MAPPING, + BUTTON_ACTION_MAPPING); +} + +bool VirtualMouse::writeRelativeEvent(float relativeX, float relativeY) { + return writeInputEvent(EV_REL, REL_X, relativeX) && writeInputEvent(EV_REL, REL_Y, relativeY) && + writeInputEvent(EV_SYN, SYN_REPORT, 0); +} + +bool VirtualMouse::writeScrollEvent(float xAxisMovement, float yAxisMovement) { + return writeInputEvent(EV_REL, REL_HWHEEL, xAxisMovement) && + writeInputEvent(EV_REL, REL_WHEEL, yAxisMovement) && + writeInputEvent(EV_SYN, SYN_REPORT, 0); +} + +// --- VirtualTouchscreen --- +const std::map<int, UinputAction> VirtualTouchscreen::TOUCH_ACTION_MAPPING = { + {AMOTION_EVENT_ACTION_DOWN, UinputAction::PRESS}, + {AMOTION_EVENT_ACTION_UP, UinputAction::RELEASE}, + {AMOTION_EVENT_ACTION_MOVE, UinputAction::MOVE}, + {AMOTION_EVENT_ACTION_CANCEL, UinputAction::CANCEL}, +}; +// Tool type mapping from https://source.android.com/devices/input/touch-devices +const std::map<int, int> VirtualTouchscreen::TOOL_TYPE_MAPPING = { + {AMOTION_EVENT_TOOL_TYPE_FINGER, MT_TOOL_FINGER}, + {AMOTION_EVENT_TOOL_TYPE_PALM, MT_TOOL_PALM}, +}; + +VirtualTouchscreen::VirtualTouchscreen(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} + +VirtualTouchscreen::~VirtualTouchscreen() {} + +bool VirtualTouchscreen::isValidPointerId(int32_t pointerId, UinputAction uinputAction) { + if (pointerId < -1 || pointerId >= (int)MAX_POINTERS) { + ALOGE("Virtual touch event has invalid pointer id %d; value must be between -1 and %zu", + pointerId, MAX_POINTERS - 0); + return false; + } + + if (uinputAction == UinputAction::PRESS && mActivePointers.test(pointerId)) { + ALOGE("Repetitive action DOWN event received on a pointer %d that is already down.", + pointerId); + return false; + } + if (uinputAction == UinputAction::RELEASE && !mActivePointers.test(pointerId)) { + ALOGE("PointerId %d action UP received with no prior action DOWN on touchscreen %d.", + pointerId, mFd.get()); + return false; + } + return true; +} + +bool VirtualTouchscreen::writeTouchEvent(int32_t pointerId, int32_t toolType, int32_t action, + float locationX, float locationY, float pressure, + float majorAxisSize) { + auto actionIterator = TOUCH_ACTION_MAPPING.find(action); + if (actionIterator == TOUCH_ACTION_MAPPING.end()) { + return false; + } + UinputAction uinputAction = actionIterator->second; + if (!isValidPointerId(pointerId, uinputAction)) { + return false; + } + if (!writeInputEvent(EV_ABS, ABS_MT_SLOT, pointerId)) { + return false; + } + auto toolTypeIterator = TOOL_TYPE_MAPPING.find(toolType); + if (toolTypeIterator == TOOL_TYPE_MAPPING.end()) { + return false; + } + if (!writeInputEvent(EV_ABS, ABS_MT_TOOL_TYPE, + static_cast<int32_t>(toolTypeIterator->second))) { + return false; + } + if (uinputAction == UinputAction::PRESS && !handleTouchDown(pointerId)) { + return false; + } + if (uinputAction == UinputAction::RELEASE && !handleTouchUp(pointerId)) { + return false; + } + if (!writeInputEvent(EV_ABS, ABS_MT_POSITION_X, locationX)) { + return false; + } + if (!writeInputEvent(EV_ABS, ABS_MT_POSITION_Y, locationY)) { + return false; + } + if (!isnan(pressure)) { + if (!writeInputEvent(EV_ABS, ABS_MT_PRESSURE, pressure)) { + return false; + } + } + if (!isnan(majorAxisSize)) { + if (!writeInputEvent(EV_ABS, ABS_MT_TOUCH_MAJOR, majorAxisSize)) { + return false; + } + } + return writeInputEvent(EV_SYN, SYN_REPORT, 0); +} + +bool VirtualTouchscreen::handleTouchUp(int32_t pointerId) { + if (!writeInputEvent(EV_ABS, ABS_MT_TRACKING_ID, static_cast<int32_t>(-1))) { + return false; + } + // When a pointer is no longer in touch, remove the pointer id from the corresponding + // entry in the unreleased touches map. + mActivePointers.reset(pointerId); + ALOGD_IF(isDebug(), "Pointer %d erased from the touchscreen %d", pointerId, mFd.get()); + + // Only sends the BTN UP event when there's no pointers on the touchscreen. + if (mActivePointers.none()) { + if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::RELEASE))) { + return false; + } + ALOGD_IF(isDebug(), "No pointers on touchscreen %d, BTN UP event sent.", mFd.get()); + } + return true; +} + +bool VirtualTouchscreen::handleTouchDown(int32_t pointerId) { + // When a new pointer is down on the touchscreen, add the pointer id in the corresponding + // entry in the unreleased touches map. + if (mActivePointers.none()) { + // Only sends the BTN Down event when the first pointer on the touchscreen is down. + if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::PRESS))) { + return false; + } + ALOGD_IF(isDebug(), "First pointer %d down under touchscreen %d, BTN DOWN event sent", + pointerId, mFd.get()); + } + + mActivePointers.set(pointerId); + ALOGD_IF(isDebug(), "Added pointer %d under touchscreen %d in the map", pointerId, mFd.get()); + if (!writeInputEvent(EV_ABS, ABS_MT_TRACKING_ID, static_cast<int32_t>(pointerId))) { + return false; + } + return true; +} + +} // namespace android diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index 916a8f2b2a..42bdf57514 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -19,6 +19,7 @@ cc_test { "InputEvent_test.cpp", "InputPublisherAndConsumer_test.cpp", "MotionPredictor_test.cpp", + "RingBuffer_test.cpp", "TfLiteMotionPredictor_test.cpp", "TouchResampling_test.cpp", "TouchVideoFrame_test.cpp", @@ -33,6 +34,7 @@ cc_test { "libgmock", "libgui_window_info_static", "libinput", + "libtflite_static", "libui-types", ], cflags: [ @@ -41,13 +43,19 @@ cc_test { "-Werror", "-Wno-unused-parameter", ], + sanitize: { + undefined: true, + all_undefined: true, + diag: { + undefined: true, + }, + }, shared_libs: [ "libbase", "libbinder", "libcutils", "liblog", "libPlatformProperties", - "libtflite", "libutils", "libvintf", ], diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp index ce87c8617e..c61efbf9ed 100644 --- a/libs/input/tests/MotionPredictor_test.cpp +++ b/libs/input/tests/MotionPredictor_test.cpp @@ -30,13 +30,6 @@ 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; @@ -73,83 +66,74 @@ static MotionEvent getMotionEvent(int32_t action, float x, float y, } TEST(MotionPredictorTest, IsPredictionAvailable) { - MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH, + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, []() { 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, MODEL_PATH, + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/1, []() { return true /*enable prediction*/; }); 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); + std::unique_ptr<MotionEvent> predicted = predictor.predict(40 * NSEC_PER_MSEC); + ASSERT_NE(nullptr, predicted); + ASSERT_GE(predicted->getEventTime(), 41); } TEST(MotionPredictorTest, FollowsGesture) { - MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH, + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, []() { 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()); + EXPECT_EQ(nullptr, predictor.predict(20 * NSEC_PER_MSEC)); 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)); + EXPECT_NE(nullptr, predictor.predict(50 * NSEC_PER_MSEC)); predictor.record(getMotionEvent(UP, 4, 11, 50ms)); - EXPECT_THAT(predictor.predict(20 * NSEC_PER_MSEC), IsEmpty()); + EXPECT_EQ(nullptr, predictor.predict(20 * NSEC_PER_MSEC)); } -TEST(MotionPredictorTest, MultipleDevicesTracked) { - MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH, +TEST(MotionPredictorTest, MultipleDevicesNotSupported) { + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, []() { 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)); - } + ASSERT_TRUE(predictor.record(getMotionEvent(DOWN, 1, 3, 0ms, /*deviceId=*/0)).ok()); + ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 1, 3, 10ms, /*deviceId=*/0)).ok()); + ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 2, 5, 20ms, /*deviceId=*/0)).ok()); + ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 3, 7, 30ms, /*deviceId=*/0)).ok()); - // End the gesture for device 0. - predictor.record(getMotionEvent(UP, 4, 9, 40ms, /*deviceId=*/0)); - predictor.record(getMotionEvent(MOVE, 400, 900, 40ms, /*deviceId=*/1)); + ASSERT_FALSE(predictor.record(getMotionEvent(DOWN, 100, 300, 40ms, /*deviceId=*/1)).ok()); + ASSERT_FALSE(predictor.record(getMotionEvent(MOVE, 100, 300, 50ms, /*deviceId=*/1)).ok()); +} - { - 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, IndividualGesturesFromDifferentDevicesAreSupported) { + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, + []() { return true /*enable prediction*/; }); + + ASSERT_TRUE(predictor.record(getMotionEvent(DOWN, 1, 3, 0ms, /*deviceId=*/0)).ok()); + ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 1, 3, 10ms, /*deviceId=*/0)).ok()); + ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 2, 5, 20ms, /*deviceId=*/0)).ok()); + ASSERT_TRUE(predictor.record(getMotionEvent(UP, 2, 5, 30ms, /*deviceId=*/0)).ok()); + + // Now, send a gesture from a different device. Since we have no active gesture, the new gesture + // should be processed correctly. + ASSERT_TRUE(predictor.record(getMotionEvent(DOWN, 100, 300, 40ms, /*deviceId=*/1)).ok()); + ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 100, 300, 50ms, /*deviceId=*/1)).ok()); } TEST(MotionPredictorTest, FlagDisablesPrediction) { - MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH, + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, []() { return false /*disable prediction*/; }); 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()); + std::unique_ptr<MotionEvent> predicted = predictor.predict(40 * NSEC_PER_MSEC); + ASSERT_EQ(nullptr, predicted); 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 0000000000..8a6ef4c21b --- /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 index 454f2aaac4..6e76ac1e52 100644 --- a/libs/input/tests/TfLiteMotionPredictor_test.cpp +++ b/libs/input/tests/TfLiteMotionPredictor_test.cpp @@ -21,7 +21,6 @@ #include <iterator> #include <string> -#include <android-base/file.h> #include <gmock/gmock.h> #include <gtest/gtest.h> #include <input/TfLiteMotionPredictor.h> @@ -33,14 +32,6 @@ 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()); @@ -92,8 +83,7 @@ TEST(TfLiteMotionPredictorTest, BuffersRecentData) { } TEST(TfLiteMotionPredictorTest, BuffersCopyTo) { - std::unique_ptr<TfLiteMotionPredictorModel> model = - TfLiteMotionPredictorModel::create(getModelPath().c_str()); + std::unique_ptr<TfLiteMotionPredictorModel> model = TfLiteMotionPredictorModel::create(); TfLiteMotionPredictorBuffers buffers(model->inputLength()); buffers.pushSample(/*timestamp=*/1, @@ -137,8 +127,7 @@ TEST(TfLiteMotionPredictorTest, BuffersCopyTo) { } TEST(TfLiteMotionPredictorTest, ModelInputOutputLength) { - std::unique_ptr<TfLiteMotionPredictorModel> model = - TfLiteMotionPredictorModel::create(getModelPath().c_str()); + std::unique_ptr<TfLiteMotionPredictorModel> model = TfLiteMotionPredictorModel::create(); ASSERT_GT(model->inputLength(), 0u); const int inputLength = model->inputLength(); @@ -155,8 +144,7 @@ TEST(TfLiteMotionPredictorTest, ModelInputOutputLength) { } TEST(TfLiteMotionPredictorTest, ModelOutput) { - std::unique_ptr<TfLiteMotionPredictorModel> model = - TfLiteMotionPredictorModel::create(getModelPath().c_str()); + std::unique_ptr<TfLiteMotionPredictorModel> model = TfLiteMotionPredictorModel::create(); TfLiteMotionPredictorBuffers buffers(model->inputLength()); buffers.pushSample(/*timestamp=*/1, {.position = {.x = 100, .y = 200}, .pressure = 0.2}); diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp index c6ad3a2218..40c6bbbf3c 100644 --- a/libs/input/tests/VelocityTracker_test.cpp +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -42,8 +42,8 @@ constexpr int32_t DEFAULT_POINTER_ID = 0; // pointer ID used for manually define // here EV = expected value, tol = VELOCITY_TOLERANCE constexpr float VELOCITY_TOLERANCE = 0.2; -// estimate coefficients must be within 0.001% of the target value -constexpr float COEFFICIENT_TOLERANCE = 0.00001; +// quadratic velocity must be within 0.001% of the target value +constexpr float QUADRATIC_VELOCITY_TOLERANCE = 0.00001; // --- VelocityTrackerTest --- class VelocityTrackerTest : public testing::Test { }; @@ -76,10 +76,6 @@ static void checkVelocity(std::optional<float> Vactual, std::optional<float> Vta } } -static void checkCoefficient(float actual, float target) { - EXPECT_NEAR_BY_FRACTION(actual, target, COEFFICIENT_TOLERANCE); -} - struct Position { float x; float y; @@ -284,21 +280,20 @@ static void computeAndCheckAxisScrollVelocity( checkVelocity(computeVelocity(strategy, motions, AMOTION_EVENT_AXIS_SCROLL), targetVelocity); } -static void computeAndCheckQuadraticEstimate(const std::vector<PlanarMotionEventEntry>& motions, - const std::array<float, 3>& coefficients) { +static void computeAndCheckQuadraticVelocity(const std::vector<PlanarMotionEventEntry>& motions, + float velocity) { VelocityTracker vt(VelocityTracker::Strategy::LSQ2); std::vector<MotionEvent> events = createTouchMotionEventStream(motions); for (MotionEvent event : events) { vt.addMovement(&event); } - std::optional<VelocityTracker::Estimator> estimatorX = vt.getEstimator(AMOTION_EVENT_AXIS_X, 0); - std::optional<VelocityTracker::Estimator> estimatorY = vt.getEstimator(AMOTION_EVENT_AXIS_Y, 0); - EXPECT_TRUE(estimatorX); - EXPECT_TRUE(estimatorY); - for (size_t i = 0; i< coefficients.size(); i++) { - checkCoefficient((*estimatorX).coeff[i], coefficients[i]); - checkCoefficient((*estimatorY).coeff[i], coefficients[i]); - } + std::optional<float> velocityX = vt.getVelocity(AMOTION_EVENT_AXIS_X, 0); + std::optional<float> velocityY = vt.getVelocity(AMOTION_EVENT_AXIS_Y, 0); + ASSERT_TRUE(velocityX); + ASSERT_TRUE(velocityY); + + EXPECT_NEAR_BY_FRACTION(*velocityX, velocity, QUADRATIC_VELOCITY_TOLERANCE); + EXPECT_NEAR_BY_FRACTION(*velocityY, velocity, QUADRATIC_VELOCITY_TOLERANCE); } /* @@ -461,8 +456,6 @@ TEST_F(VelocityTrackerTest, TestApiInteractionsWithNoMotionEvents) { EXPECT_FALSE(vt.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)); - EXPECT_FALSE(vt.getEstimator(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)); - VelocityTracker::ComputedVelocity computedVelocity = vt.getComputedVelocity(1000, 1000); for (uint32_t id = 0; id <= MAX_POINTER_ID; id++) { EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, id)); @@ -1074,7 +1067,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFast3) { * If the events with POINTER_UP or POINTER_DOWN are not handled correctly (these should not be * part of the fitted data), this can cause large velocity values to be reported instead. */ -TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_ThreeFingerTap) { +TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_ThreeFingerTap) { std::vector<PlanarMotionEventEntry> motions = { { 0us, {{1063, 1128}, {NAN, NAN}, {NAN, NAN}} }, { 10800us, {{1063, 1128}, {682, 1318}, {NAN, NAN}} }, // POINTER_DOWN @@ -1162,7 +1155,7 @@ TEST_F(VelocityTrackerTest, LongDelayBeforeActionPointerUp) { * ================== Tests for least squares fitting ============================================== * * Special care must be taken when constructing tests for LeastSquaresVelocityTrackerStrategy - * getEstimator function. In particular: + * getVelocity function. In particular: * - inside the function, time gets converted from nanoseconds to seconds * before being used in the fit. * - any values that are older than 100 ms are being discarded. @@ -1183,7 +1176,7 @@ TEST_F(VelocityTrackerTest, LongDelayBeforeActionPointerUp) { * The coefficients are (0, 0, 1). * In the test, we would convert these coefficients to (0*(1E3)^0, 0*(1E3)^1, 1*(1E3)^2). */ -TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Constant) { +TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Constant) { std::vector<PlanarMotionEventEntry> motions = { { 0ms, {{1, 1}} }, // 0 s { 1ms, {{1, 1}} }, // 0.001 s @@ -1195,13 +1188,13 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Constan // -0.002, 1 // -0.001, 1 // -0.ms, 1 - computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({1, 0, 0})); + computeAndCheckQuadraticVelocity(motions, 0); } /* * Straight line y = x :: the constant and quadratic coefficients are zero. */ -TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Linear) { +TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Linear) { std::vector<PlanarMotionEventEntry> motions = { { 0ms, {{-2, -2}} }, { 1ms, {{-1, -1}} }, @@ -1213,13 +1206,13 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Linear) // -0.002, -2 // -0.001, -1 // -0.000, 0 - computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({0, 1E3, 0})); + computeAndCheckQuadraticVelocity(motions, 1E3); } /* * Parabola */ -TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic) { +TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Parabolic) { std::vector<PlanarMotionEventEntry> motions = { { 0ms, {{1, 1}} }, { 1ms, {{4, 4}} }, @@ -1231,13 +1224,13 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabol // -0.002, 1 // -0.001, 4 // -0.000, 8 - computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({8, 4.5E3, 0.5E6})); + computeAndCheckQuadraticVelocity(motions, 4.5E3); } /* * Parabola */ -TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic2) { +TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Parabolic2) { std::vector<PlanarMotionEventEntry> motions = { { 0ms, {{1, 1}} }, { 1ms, {{4, 4}} }, @@ -1249,13 +1242,13 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabol // -0.002, 1 // -0.001, 4 // -0.000, 9 - computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({9, 6E3, 1E6})); + computeAndCheckQuadraticVelocity(motions, 6E3); } /* * Parabola :: y = x^2 :: the constant and linear coefficients are zero. */ -TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic3) { +TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Parabolic3) { std::vector<PlanarMotionEventEntry> motions = { { 0ms, {{4, 4}} }, { 1ms, {{1, 1}} }, @@ -1267,7 +1260,7 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabol // -0.002, 4 // -0.001, 1 // -0.000, 0 - computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({0, 0E3, 1E6})); + computeAndCheckQuadraticVelocity(motions, 0E3); } // Recorded by hand on sailfish, but only the diffs are taken to test cumulative axis velocity. diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp index 2c4b3bff77..0c03ede9e8 100644 --- a/libs/jpegrecoverymap/Android.bp +++ b/libs/jpegrecoverymap/Android.bp @@ -29,9 +29,10 @@ cc_library { local_include_dirs: ["include"], srcs: [ - "recoverymap.cpp", + "jpegr.cpp", "recoverymapmath.cpp", - "recoverymaputils.cpp", + "jpegrutils.cpp", + "multipictureformat.cpp", ], shared_libs: [ @@ -40,6 +41,7 @@ cc_library { "libjpegencoder", "libjpegdecoder", "liblog", + "libutils", ], static_libs: ["libskia"], @@ -57,7 +59,7 @@ cc_library { export_include_dirs: ["include"], srcs: [ - "jpegencoder.cpp", + "jpegencoderhelper.cpp", ], } @@ -73,6 +75,6 @@ cc_library { export_include_dirs: ["include"], srcs: [ - "jpegdecoder.cpp", + "jpegdecoderhelper.cpp", ], } diff --git a/libs/jpegrecoverymap/OWNERS b/libs/jpegrecoverymap/OWNERS index 133af5bcd4..6ace354d0b 100644 --- a/libs/jpegrecoverymap/OWNERS +++ b/libs/jpegrecoverymap/OWNERS @@ -1,4 +1,3 @@ arifdikici@google.com -deakin@google.com dichenzhang@google.com kyslov@google.com
\ No newline at end of file diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h index 419b63d1de..874823709c 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 @@ extern "C" { } #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. @@ -116,6 +115,6 @@ private: // 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 index 61aeb8ace7..8b82b2b00a 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 @@ extern "C" { #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 @@ private: 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 index 1a4b679a73..a433e8ace4 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,7 +29,7 @@ typedef enum { 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, @@ -37,6 +38,14 @@ typedef enum { JPEGR_TF_SRGB = 3, } jpegr_transfer_function; +// Target output formats for decoder +typedef enum { + JPEGR_OUTPUT_SDR, // SDR in RGBA_8888 color format + JPEGR_OUTPUT_HDR_LINEAR, // HDR in F16 color format (linear) + JPEGR_OUTPUT_HDR_PQ, // HDR in RGBA_1010102 color format (PQ transfer function) + JPEGR_OUTPUT_HDR_HLG, // HDR in RGBA_1010102 color format (HLG transfer function) +} jpegr_output_format; + struct jpegr_info_struct { size_t width; size_t height; @@ -82,45 +91,13 @@ struct jpegr_exif_struct { 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; @@ -129,7 +106,7 @@ typedef struct jpegr_exif_struct* jr_exif_ptr; typedef struct jpegr_metadata* jr_metadata_ptr; typedef struct jpegr_info_struct* jr_info_ptr; -class RecoveryMap { +class JpegR { public: /* * Encode API-0 @@ -226,20 +203,15 @@ public: * @param compressed_jpegr_image compressed JPEGR image * @param dest destination of the uncompressed JPEGR image * @param exif destination of the decoded EXIF metadata. - * @param request_sdr flag that request SDR output. If set to true, decoder will only decode - * the primary image which is SDR. Setting of request_sdr and input source - * (HDR or SDR) can be found in the table below: - * | input source | request_sdr | output of decoding | - * | HDR | true | SDR | - * | HDR | false | HDR | - * | SDR | true | SDR | - * | SDR | false | SDR | + * @param output_format flag for setting output color format. if set to + * {@code JPEGR_OUTPUT_SDR}, decoder will only decode the primary image + * which is SDR. Default value is JPEGR_OUTPUT_HDR_LINEAR. * @return NO_ERROR if decoding succeeds, error code if error occurs. */ status_t decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, jr_uncompressed_ptr dest, jr_exif_ptr exif = nullptr, - bool request_sdr = false); + jpegr_output_format output_format = JPEGR_OUTPUT_HDR_LINEAR); /* * Gets Info from JPEGR file without decoding it. @@ -252,17 +224,7 @@ public: */ 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 @@ -270,14 +232,14 @@ private: * * @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); @@ -285,20 +247,34 @@ private: * 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 * @param metadata JPEG/R metadata extracted from XMP. + * @param output_format flag for setting output color format. if set to + * {@code JPEGR_OUTPUT_SDR}, decoder will only decode the primary image + * which is SDR. Default value is JPEGR_OUTPUT_HDR_LINEAR. * @param dest reconstructed HDR image * @return NO_ERROR if calculation succeeds, error code if error occurs. */ status_t applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, jr_uncompressed_ptr uncompressed_recovery_map, jr_metadata_ptr metadata, + jpegr_output_format output_format, 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 * @@ -354,6 +330,6 @@ private: 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 699c0d3ca1..f73034338b 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 @@ enum { 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 0000000000..41458532b9 --- /dev/null +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h @@ -0,0 +1,144 @@ +/* + * 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 <utils/RefBase.h> + +#include <sstream> +#include <stdint.h> +#include <string> +#include <cstdio> + +namespace android::jpegrecoverymap { + +struct jpegr_metadata; +/* + * Mutable data structure. Holds information for metadata. + */ +class DataStruct : public RefBase { +private: + void* data; + int writePos; + int length; + ~DataStruct(); + +public: + DataStruct(int s); + void* getData(); + int getLength(); + int getBytesWritten(); + bool write8(uint8_t value); + bool write16(uint16_t value); + bool write32(uint32_t value); + bool write(const void* src, int size); +}; + +/* + * 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 for the primary image. + * + * below is an example of the XMP metadata that this function generates where + * secondary_image_length = 1000 + * + * <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/"> + * <Container:Directory> + * <rdf:Seq> + * <rdf:li> + * <Container:Item + * Item:Semantic="Primary" + * Item:Mime="image/jpeg"/> + * </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 + * @return XMP metadata in type of string + */ +std::string generateXmpForPrimaryImage(int secondary_image_length); + +/* + * This method generates XMP metadata for the recovery map image. + * + * below is an example of the XMP metadata that this function generates where + * max_content_boost = 8.0 + * min_content_boost = 0.5 + * + * <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:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/" + * hdrgm:Version="1" + * hdrgm:GainMapMin="0.5" + * hdrgm:GainMapMax="8.5" + * hdrgm:Gamma="1" + * hdrgm:OffsetSDR="0" + * hdrgm:OffsetHDR="0" + * hdrgm:HDRCapacityMin="0.5" + * hdrgm:HDRCapacityMax="8.5" + * hdrgm:BaseRendition="SDR"/> + * </rdf:RDF> + * </x:xmpmeta> + * + * @param metadata JPEG/R metadata to encode as XMP + * @return XMP metadata in type of string + */ + std::string generateXmpForSecondaryImage(jpegr_metadata& metadata); +} // namespace android::jpegrecoverymap + +#endif //ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h b/libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h new file mode 100644 index 0000000000..7dca91637e --- /dev/null +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h @@ -0,0 +1,76 @@ +/* + * 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_MULTIPICTUREFORMAT_H +#define ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H + +#include <jpegrecoverymap/jpegrutils.h> + +namespace android::jpegrecoverymap { +static constexpr uint32_t EndianSwap32(uint32_t value) { + return ((value & 0xFF) << 24) | + ((value & 0xFF00) << 8) | + ((value & 0xFF0000) >> 8) | + (value >> 24); +} +static inline uint16_t EndianSwap16(uint16_t value) { + return static_cast<uint16_t>((value >> 8) | ((value & 0xFF) << 8)); +} +#define USE_BIG_ENDIAN true +#if USE_BIG_ENDIAN + #define Endian_SwapBE32(n) EndianSwap32(n) + #define Endian_SwapBE16(n) EndianSwap16(n) +#else + #define Endian_SwapBE32(n) (n) + #define Endian_SwapBE16(n) (n) +#endif + +constexpr size_t kNumPictures = 2; +constexpr size_t kMpEndianSize = 4; +constexpr uint16_t kTagSerializedCount = 3; +constexpr uint32_t kTagSize = 12; + +constexpr uint16_t kTypeLong = 0x4; +constexpr uint16_t kTypeUndefined = 0x7; + +static constexpr uint8_t kMpfSig[] = {'M', 'P', 'F', '\0'}; +constexpr uint8_t kMpLittleEndian[kMpEndianSize] = {0x49, 0x49, 0x2A, 0x00}; +constexpr uint8_t kMpBigEndian[kMpEndianSize] = {0x4D, 0x4D, 0x00, 0x2A}; + +constexpr uint16_t kVersionTag = 0xB000; +constexpr uint16_t kVersionType = kTypeUndefined; +constexpr uint32_t kVersionCount = 4; +constexpr size_t kVersionSize = 4; +constexpr uint8_t kVersionExpected[kVersionSize] = {'0', '1', '0', '0'}; + +constexpr uint16_t kNumberOfImagesTag = 0xB001; +constexpr uint16_t kNumberOfImagesType = kTypeLong; +constexpr uint32_t kNumberOfImagesCount = 1; + +constexpr uint16_t kMPEntryTag = 0xB002; +constexpr uint16_t kMPEntryType = kTypeUndefined; +constexpr uint32_t kMPEntrySize = 16; + +constexpr uint32_t kMPEntryAttributeFormatJpeg = 0x0000000; +constexpr uint32_t kMPEntryAttributeTypePrimary = 0x030000; + +size_t calculateMpfSize(); +sp<DataStruct> generateMpf(int primary_image_size, int primary_image_offset, + int secondary_image_size, int secondary_image_offset); + +} // namespace android::jpegrecoverymap + +#endif //ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h index 0695bb74ac..8b5318f452 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) @@ -115,14 +115,23 @@ inline Color operator/(const Color& lhs, const float rhs) { return temp /= rhs; } +inline uint16_t floatToHalf(float f) { + uint32_t x = *((uint32_t*)&f); + uint16_t h = ((x >> 16) & 0x8000) + | ((((x & 0x7f800000) - 0x38000000) >> 13) & 0x7c00) + | ((x >> 13) & 0x03ff); + return h; +} + 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 +139,10 @@ struct RecoveryLUT { } 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 +228,9 @@ Color srgbInvOetf(Color e_gamma); 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 +272,9 @@ Color hlgOetf(Color e); 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 +285,9 @@ Color hlgInvOetf(Color e_gamma); 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 +298,9 @@ Color pqOetf(Color e); 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 +311,9 @@ Color pqInvOetf(Color e_gamma); 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 +350,13 @@ ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gam * 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 +400,13 @@ float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size */ uint32_t colorToRgba1010102(Color e_gamma); -} // namespace android::recoverymap +/* + * Convert from Color to F16. + * + * Alpha always set to 1.0. + */ +uint64_t colorToRgbaF16(Color e_gamma); + +} // 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 8696851155..0000000000 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h +++ /dev/null @@ -1,102 +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; - -/* - * 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 - * 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); -} - -#endif //ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoderhelper.cpp index 1bf609a54c..d36bbf8165 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 @@ static void jpegrerror_exit(j_common_ptr cinfo) { 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 @@ bool JpegDecoder::decompressImage(const void* image, int length, bool decodeToRG 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,7 +248,7 @@ bool JpegDecoder::decode(const void* image, int length, bool decodeToRGBA) { 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); @@ -259,7 +259,7 @@ bool JpegDecoder::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, 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; @@ -326,7 +326,7 @@ bool JpegDecoder::getCompressedImageParameters(const void* image, int length, 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 @@ -341,7 +341,7 @@ bool JpegDecoder::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* d 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]; @@ -386,7 +386,7 @@ bool JpegDecoder::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* de 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}; @@ -413,4 +413,4 @@ bool JpegDecoder::decompressSingleChannel(jpeg_decompress_struct* cinfo, const u return true; } -} // namespace android +} // namespace jpegrecoverymap diff --git a/libs/jpegrecoverymap/jpegencoder.cpp b/libs/jpegrecoverymap/jpegencoderhelper.cpp index 627dcdf6ee..586cd346e4 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 @@ bool JpegEncoder::compressImage(const void* image, int width, int height, int qu 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 @@ void JpegEncoder::initDestination(j_compress_ptr cinfo) { 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 @@ boolean JpegEncoder::emptyOutputBuffer(j_compress_ptr cinfo) { 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 @@ void JpegEncoder::outputErrorMessage(j_common_ptr cinfo) { 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 @@ bool JpegEncoder::encode(const void* image, int width, int height, int jpegQuali 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 @@ void JpegEncoder::setJpegDestination(jpeg_compress_struct* cinfo) { 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 @@ void JpegEncoder::setJpegCompressStruct(int width, int height, int quality, } } -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 @@ bool JpegEncoder::compress( 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 @@ bool JpegEncoder::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) { 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 @@ bool JpegEncoder::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8 return true; } -} // namespace android +} // namespace jpegrecoverymap diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/jpegr.cpp index e06bd24cfa..79b1ae3139 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/jpegr.cpp @@ -14,11 +14,12 @@ * 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 <jpegrecoverymap/multipictureformat.h> #include <image_io/jpeg/jpeg_marker.h> #include <image_io/jpeg/jpeg_info.h> @@ -27,7 +28,9 @@ #include <image_io/base/data_segment_data_source.h> #include <utils/Log.h> #include "SkColorSpace.h" +#include "SkData.h" #include "SkICC.h" +#include "SkRefCnt.h" #include <map> #include <memory> @@ -43,7 +46,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 @@ -72,16 +75,6 @@ static const size_t kJpegBlock = 8; // 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; @@ -96,14 +89,15 @@ int GetCPUCoreCount() { return cpuCoreCount; } -static const map<recoverymap::jpegr_color_gamut, skcms_Matrix3x3> jrGamut_to_skGamut { +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}, }; static const map< - recoverymap::jpegr_transfer_function, skcms_TransferFunction> jrTransFunc_to_skTransFunc { + jpegrecoverymap::jpegr_transfer_function, + skcms_TransferFunction> jrTransFunc_to_skTransFunc { {JPEGR_TF_SRGB, SkNamedTransferFn::kSRGB}, {JPEGR_TF_LINEAR, SkNamedTransferFn::kLinear}, {JPEGR_TF_HLG, SkNamedTransferFn::kHLG}, @@ -111,11 +105,11 @@ static const map< }; /* Encode API-0 */ -status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, - jpegr_transfer_function hdr_tf, - jr_compressed_ptr dest, - int quality, - jr_exif_ptr exif) { +status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, + jpegr_transfer_function hdr_tf, + jr_compressed_ptr dest, + int quality, + jr_exif_ptr exif) { if (uncompressed_p010_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } @@ -133,10 +127,6 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, 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[]>( @@ -146,7 +136,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, 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)); @@ -160,7 +150,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jrTransFunc_to_skTransFunc.at(JPEGR_TF_SRGB), jrGamut_to_skGamut.at(uncompressed_yuv_420_image.colorGamut)); - JpegEncoder jpeg_encoder; + JpegEncoderHelper jpeg_encoder; if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data, uncompressed_yuv_420_image.width, uncompressed_yuv_420_image.height, quality, @@ -177,12 +167,12 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, } /* Encode API-1 */ -status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, - jr_uncompressed_ptr uncompressed_yuv_420_image, - jpegr_transfer_function hdr_tf, - jr_compressed_ptr dest, - int quality, - jr_exif_ptr exif) { +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, + int quality, + jr_exif_ptr exif) { if (uncompressed_p010_image == nullptr || uncompressed_yuv_420_image == nullptr || dest == nullptr) { @@ -207,14 +197,10 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, 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)); @@ -228,7 +214,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jrTransFunc_to_skTransFunc.at(JPEGR_TF_SRGB), jrGamut_to_skGamut.at(uncompressed_yuv_420_image->colorGamut)); - JpegEncoder jpeg_encoder; + JpegEncoderHelper jpeg_encoder; if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data, uncompressed_yuv_420_image->width, uncompressed_yuv_420_image->height, quality, @@ -245,11 +231,11 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, } /* Encode API-2 */ -status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, - jr_uncompressed_ptr uncompressed_yuv_420_image, - jr_compressed_ptr compressed_jpeg_image, - jpegr_transfer_function hdr_tf, - jr_compressed_ptr dest) { +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, + jr_compressed_ptr dest) { if (uncompressed_p010_image == nullptr || uncompressed_yuv_420_image == nullptr || compressed_jpeg_image == nullptr @@ -271,14 +257,10 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, 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)); @@ -294,10 +276,10 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, } /* Encode API-3 */ -status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, - jr_compressed_ptr compressed_jpeg_image, - jpegr_transfer_function hdr_tf, - jr_compressed_ptr dest) { +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) { if (uncompressed_p010_image == nullptr || compressed_jpeg_image == nullptr || dest == nullptr) { @@ -311,7 +293,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, 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; } @@ -328,14 +310,10 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, 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)); @@ -350,8 +328,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return NO_ERROR; } -status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, - jr_info_ptr jpegr_info) { +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; } @@ -360,7 +337,7 @@ status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, 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)) { @@ -371,18 +348,18 @@ status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, } /* Decode API */ -status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, - jr_uncompressed_ptr dest, - jr_exif_ptr exif, - bool request_sdr) { +status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, + jr_uncompressed_ptr dest, + jr_exif_ptr exif, + jpegr_output_format output_format) { if (compressed_jpegr_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } // TODO: fill EXIF data (void) exif; - if (request_sdr) { - JpegDecoder jpeg_decoder; + if (output_format == JPEGR_OUTPUT_SDR) { + JpegDecoderHelper jpeg_decoder; if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length, true)) { return ERROR_JPEGR_DECODE_ERROR; @@ -402,12 +379,12 @@ status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, 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; } @@ -422,23 +399,22 @@ status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth(); uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight(); - if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()), - jpeg_decoder.getXMPSize(), &metadata)) { + if (!getMetadataFromXMP(static_cast<uint8_t*>(recovery_map_decoder.getXMPPtr()), + recovery_map_decoder.getXMPSize(), &metadata)) { return ERROR_JPEGR_DECODE_ERROR; } - JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest)); + JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, output_format, dest)); return NO_ERROR; } -status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, - jr_compressed_ptr dest) { +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, @@ -516,10 +492,11 @@ void JobQueue::reset() { mQueuedAllJobs = false; } -status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, - jr_uncompressed_ptr uncompressed_p010_image, - jr_metadata_ptr metadata, - jr_uncompressed_ptr dest) { +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 || uncompressed_p010_image == nullptr || metadata == nullptr @@ -554,7 +531,7 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 ColorTransformFn hdrInvOetf = nullptr; float hdr_white_nits = 0.0f; - switch (metadata->transferFunction) { + switch (hdr_tf) { case JPEGR_TF_LINEAR: hdrInvOetf = identityConversion; break; @@ -579,6 +556,9 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 return ERROR_JPEGR_INVALID_TRANS_FUNC; } + metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits; + metadata->minContentBoost = 1.0f; + ColorTransformFn hdrGamutConversionFn = getHdrConversionFn( uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut); @@ -599,47 +579,19 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 } std::mutex mutex; - float hdr_y_nits_max = 0.0f; - double hdr_y_nits_avg = 0.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 { - size_t rowStart, rowEnd; - float hdr_y_nits_max_th = 0.0f; - double hdr_y_nits_avg_th = 0.0f; - while (jobQueue.dequeueJob(rowStart, rowEnd)) { - for (size_t y = rowStart; y < rowEnd; ++y) { - for (size_t x = 0; x < uncompressed_p010_image->width; ++x) { - Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y); - Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma); - Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma); - hdr_rgb = hdrGamutConversionFn(hdr_rgb); - float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits; - - hdr_y_nits_avg_th += hdr_y_nits; - if (hdr_y_nits > hdr_y_nits_max_th) { - hdr_y_nits_max_th = hdr_y_nits; - } - } - } - } - std::unique_lock<std::mutex> lock{mutex}; - hdr_y_nits_avg += hdr_y_nits_avg_th; - hdr_y_nits_max = std::max(hdr_y_nits_max, hdr_y_nits_max_th); - }; - std::function<void()> generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image, metadata, dest, hdrInvOetf, hdrGamutConversionFn, luminanceFn, hdr_white_nits, &jobQueue]() -> void { size_t rowStart, rowEnd; + 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); @@ -656,39 +608,16 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 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); } } } }; - std::vector<std::thread> workers; - for (int th = 0; th < threads - 1; th++) { - workers.push_back(std::thread(computeMetadata)); - } - - // compute metadata - for (size_t rowStart = 0; rowStart < image_height;) { - size_t rowEnd = std::min(rowStart + rowStep, image_height); - jobQueue.enqueueJob(rowStart, rowEnd); - rowStart = rowEnd; - } - jobQueue.markQueueForEnd(); - computeMetadata(); - std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); }); - workers.clear(); - hdr_y_nits_avg /= image_width * image_height; - - metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits; - if (metadata->transferFunction == JPEGR_TF_PQ) { - metadata->hdr10Metadata.maxFALL = hdr_y_nits_avg; - metadata->hdr10Metadata.maxCLL = hdr_y_nits_max; - } - // generate map - jobQueue.reset(); + std::vector<std::thread> workers; for (int th = 0; th < threads - 1; th++) { workers.push_back(std::thread(generateMap)); } @@ -707,10 +636,11 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 return NO_ERROR; } -status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, - jr_uncompressed_ptr uncompressed_recovery_map, - jr_metadata_ptr metadata, - jr_uncompressed_ptr dest) { +status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, + jr_uncompressed_ptr uncompressed_recovery_map, + jr_metadata_ptr metadata, + jpegr_output_format output_format, + jr_uncompressed_ptr dest) { if (uncompressed_yuv_420_image == nullptr || uncompressed_recovery_map == nullptr || metadata == nullptr @@ -721,40 +651,16 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ 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, + metadata, dest, &jobQueue, &idwTable, output_format, &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; -#else - hdrOetf = hlgOetf; -#endif - break; - case JPEGR_TF_PQ: -#if USE_PQ_OETF_LUT - hdrOetf = pqOetfLUT; -#else - hdrOetf = pqOetf; -#endif - break; - default: - // Should be impossible to hit after input validation. - hdrOetf = identityConversion; - } - size_t rowStart, rowEnd; while (jobQueue.dequeueJob(rowStart, rowEnd)) { for (size_t y = rowStart; y < rowEnd; ++y) { @@ -775,19 +681,51 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ 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); - uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr); - + rgb_hdr = rgb_hdr / metadata->maxContentBoost; size_t pixel_idx = x + y * width; - reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102; + + switch (output_format) { + case JPEGR_OUTPUT_HDR_LINEAR: + { + uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr); + reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16; + break; + } + case JPEGR_OUTPUT_HDR_HLG: + { +#if USE_HLG_OETF_LUT + ColorTransformFn hdrOetf = hlgOetfLUT; +#else + ColorTransformFn hdrOetf = hlgOetf; +#endif + Color rgb_gamma_hdr = hdrOetf(rgb_hdr); + uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr); + reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102; + break; + } + case JPEGR_OUTPUT_HDR_PQ: + { +#if USE_HLG_OETF_LUT + ColorTransformFn hdrOetf = pqOetfLUT; +#else + ColorTransformFn hdrOetf = pqOetf; +#endif + Color rgb_gamma_hdr = hdrOetf(rgb_hdr); + uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr); + reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102; + break; + } + default: + {} + // Should be impossible to hit after input validation. + } } } } @@ -810,9 +748,9 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ 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; } @@ -860,8 +798,8 @@ status_t RecoveryMap::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compre } -status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image, - jr_compressed_ptr dest) { +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; } @@ -880,22 +818,33 @@ status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_imag // (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 +// XMP +// +// (Required, MPF package) APP2 (ff e2) +// 2 bytes of length +// MPF // // (Required) primary image (without the first two bytes (SOI), may have other packages) // -// (Required) secondary image (the recovery map) +// SOI (ff d8) +// +// (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 +// +// (Required) secondary image (the recovery map, without the first two bytes (SOI)) // // 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, - jr_compressed_ptr compressed_recovery_map, - jr_exif_ptr exif, - jr_metadata_ptr metadata, - jr_compressed_ptr dest) { +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 || metadata == nullptr @@ -903,8 +852,25 @@ status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, return ERROR_JPEGR_INVALID_NULL_PTR; } - int pos = 0; + const string nameSpace = "http://ns.adobe.com/xap/1.0/"; + const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator + // calculate secondary image length first, because the length will be written into the primary + // image xmp + const string xmp_secondary = generateXmpForSecondaryImage(*metadata); + const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */ + + nameSpaceLength /* 29 bytes length of name space including \0 */ + + xmp_secondary.size(); /* length of xmp packet */ + const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */ + + xmp_secondary_length + + compressed_recovery_map->length; + // primary image + const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size); + // same as primary + const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size(); + + int pos = 0; + // Begin primary image // Write SOI JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos)); @@ -923,13 +889,7 @@ status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, // Prepare and write XMP { - const string xmp = generateXmp(compressed_recovery_map->length, *metadata); - const string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; - const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator - // 2 bytes: representing the length of the package - // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0", - // x bytes: length of xmp packet - const int length = 2 + nameSpaceLength + xmp.size(); + const int length = xmp_primary_length; const uint8_t lengthH = ((length >> 8) & 0xff); const uint8_t lengthL = (length & 0xff); JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); @@ -937,15 +897,57 @@ status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos)); - JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos)); + JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos)); + } + + // Prepare and write MPF + { + const int length = 2 + calculateMpfSize(); + const uint8_t lengthH = ((length >> 8) & 0xff); + const uint8_t lengthL = (length & 0xff); + int primary_image_size = pos + length + compressed_jpeg_image->length; + // between APP2 + package size + signature + // ff e2 00 58 4d 50 46 00 + // 2 + 2 + 4 = 8 (bytes) + // and ff d8 sign of the secondary image + int secondary_image_offset = primary_image_size - pos - 8; + sp<DataStruct> mpf = generateMpf(primary_image_size, + 0, /* primary_image_offset */ + secondary_image_size, + secondary_image_offset); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos)); + JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); + JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); + JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos)); } // Write primary image JPEGR_CHECK(Write(dest, (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos)); + // Finish primary image + + // Begin secondary image (recovery map) + // Write SOI + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos)); + + // Prepare and write XMP + { + const int length = xmp_secondary_length; + const uint8_t lengthH = ((length >> 8) & 0xff); + const uint8_t lengthL = (length & 0xff); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos)); + JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); + JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); + JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos)); + JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos)); + } // Write secondary image - JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos)); + JPEGR_CHECK(Write(dest, + (uint8_t*)compressed_recovery_map->data + 2, compressed_recovery_map->length - 2, pos)); // Set back length dest->length = pos; @@ -954,8 +956,7 @@ status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, return NO_ERROR; } -status_t RecoveryMap::toneMap(jr_uncompressed_ptr src, - jr_uncompressed_ptr dest) { +status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) { if (src == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } @@ -991,4 +992,4 @@ status_t RecoveryMap::toneMap(jr_uncompressed_ptr src, 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 0000000000..38b78ad19c --- /dev/null +++ b/libs/jpegrecoverymap/jpegrutils.cpp @@ -0,0 +1,357 @@ +/* + * 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 <utils/Log.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 <cmath> + +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". + */ +static inline string Name(const string &prefix, const string &suffix) { + std::stringstream ss; + ss << prefix << ":" << suffix; + return ss.str(); +} + +DataStruct::DataStruct(int s) { + data = malloc(s); + length = s; + memset(data, 0, s); + writePos = 0; +} + +DataStruct::~DataStruct() { + if (data != nullptr) { + free(data); + } +} + +void* DataStruct::getData() { + return data; +} + +int DataStruct::getLength() { + return length; +} + +int DataStruct::getBytesWritten() { + return writePos; +} + +bool DataStruct::write8(uint8_t value) { + uint8_t v = value; + return write(&v, 1); +} + +bool DataStruct::write16(uint16_t value) { + uint16_t v = value; + return write(&v, 2); +} +bool DataStruct::write32(uint32_t value) { + uint32_t v = value; + return write(&v, 4); +} + +bool DataStruct::write(const void* src, int size) { + if (writePos + size > length) { + ALOGE("Writing out of boundary: write position: %d, size: %d, capacity: %d", + writePos, size, length); + return false; + } + memcpy((uint8_t*) data + writePos, src, size); + writePos += size; + return true; +} + +/* + * 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() { + state = NotStrarted; + } + + enum ParseState { + NotStrarted, + Started, + Done + }; + + virtual DataMatchResult StartElement(const XmlTokenContext& context) { + string val; + if (context.BuildTokenValue(&val)) { + if (!val.compare(containerName)) { + state = Started; + } else { + if (state != Done) { + state = NotStrarted; + } + } + } + return context.GetResult(); + } + + virtual DataMatchResult FinishElement(const XmlTokenContext& context) { + if (state == Started) { + state = Done; + lastAttributeName = ""; + } + return context.GetResult(); + } + + virtual DataMatchResult AttributeName(const XmlTokenContext& context) { + string val; + if (state == Started) { + if (context.BuildTokenValue(&val)) { + if (!val.compare(maxContentBoostAttrName)) { + lastAttributeName = maxContentBoostAttrName; + } else if (!val.compare(minContentBoostAttrName)) { + lastAttributeName = minContentBoostAttrName; + } else { + lastAttributeName = ""; + } + } + } + return context.GetResult(); + } + + virtual DataMatchResult AttributeValue(const XmlTokenContext& context) { + string val; + if (state == Started) { + if (context.BuildTokenValue(&val, true)) { + if (!lastAttributeName.compare(maxContentBoostAttrName)) { + maxContentBoostStr = val; + } else if (!lastAttributeName.compare(minContentBoostAttrName)) { + minContentBoostStr = val; + } + } + } + return context.GetResult(); + } + + bool getMaxContentBoost(float* max_content_boost) { + if (state == Done) { + stringstream ss(maxContentBoostStr); + float val; + if (ss >> val) { + *max_content_boost = exp2(val); + return true; + } else { + return false; + } + } else { + return false; + } + } + + bool getMinContentBoost(float* min_content_boost) { + if (state == Done) { + stringstream ss(minContentBoostStr); + float val; + if (ss >> val) { + *min_content_boost = exp2(val); + return true; + } else { + return false; + } + } else { + return false; + } + } + +private: + static const string containerName; + static const string maxContentBoostAttrName; + string maxContentBoostStr; + static const string minContentBoostAttrName; + string minContentBoostStr; + string lastAttributeName; + ParseState state; +}; + +// 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::containerName = "rdf:Description"; +// 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.adobe.com/hdr-gain-map/1.0/"; +const string kRecoveryMapPrefix = "hdrgm"; + +// RecoveryMap XMP constants - element and attribute names +const string kMapVersion = Name(kRecoveryMapPrefix, "Version"); +const string kMapGainMapMin = Name(kRecoveryMapPrefix, "GainMapMin"); +const string kMapGainMapMax = Name(kRecoveryMapPrefix, "GainMapMax"); +const string kMapGamma = Name(kRecoveryMapPrefix, "Gamma"); +const string kMapOffsetSdr = Name(kRecoveryMapPrefix, "OffsetSDR"); +const string kMapOffsetHdr = Name(kRecoveryMapPrefix, "OffsetHDR"); +const string kMapHDRCapacityMin = Name(kRecoveryMapPrefix, "HDRCapacityMin"); +const string kMapHDRCapacityMax = Name(kRecoveryMapPrefix, "HDRCapacityMax"); +const string kMapBaseRendition = Name(kRecoveryMapPrefix, "BaseRendition"); + +// RecoveryMap XMP constants - names for XMP handlers +const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin; +const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax; + +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; + } + + if (!handler.getMinContentBoost(&metadata->minContentBoost)) { + return false; + } + + return true; +} + +string generateXmpForPrimaryImage(int secondary_image_length) { + 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.StartWritingElements(kConDirSeq); + size_t item_depth = writer.StartWritingElements(kLiItem); + writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary); + writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg); + 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(); +} + +string generateXmpForSecondaryImage(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(kRecoveryMapPrefix, kRecoveryMapUri); + writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); + writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost)); + writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost)); + writer.WriteAttributeNameAndValue(kMapGamma, "1"); + writer.WriteAttributeNameAndValue(kMapOffsetSdr, "0"); + writer.WriteAttributeNameAndValue(kMapOffsetHdr, "0"); + writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, "0"); + writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, "2.3"); + writer.WriteAttributeNameAndValue(kMapBaseRendition, "SDR"); + writer.FinishWriting(); + + return ss.str(); +} + +} // namespace android::jpegrecoverymap diff --git a/libs/jpegrecoverymap/multipictureformat.cpp b/libs/jpegrecoverymap/multipictureformat.cpp new file mode 100644 index 0000000000..a219aef106 --- /dev/null +++ b/libs/jpegrecoverymap/multipictureformat.cpp @@ -0,0 +1,94 @@ +/* + * 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 <jpegrecoverymap/multipictureformat.h> +#include <jpegrecoverymap/jpegrutils.h> + +namespace android::jpegrecoverymap { +size_t calculateMpfSize() { + return sizeof(kMpfSig) + // Signature + kMpEndianSize + // Endianness + sizeof(uint32_t) + // Index IFD Offset + sizeof(uint16_t) + // Tag count + kTagSerializedCount * kTagSize + // 3 tags at 12 bytes each + sizeof(uint32_t) + // Attribute IFD offset + kNumPictures * kMPEntrySize; // MP Entries for each image +} + +sp<DataStruct> generateMpf(int primary_image_size, int primary_image_offset, + int secondary_image_size, int secondary_image_offset) { + size_t mpf_size = calculateMpfSize(); + sp<DataStruct> dataStruct = new DataStruct(mpf_size); + + dataStruct->write(static_cast<const void*>(kMpfSig), sizeof(kMpfSig)); +#if USE_BIG_ENDIAN + dataStruct->write(static_cast<const void*>(kMpBigEndian), kMpEndianSize); +#else + dataStruct->write(static_cast<const void*>(kMpLittleEndian), kMpEndianSize); +#endif + + // Set the Index IFD offset be the position after the endianness value and this offset. + constexpr uint32_t indexIfdOffset = + static_cast<uint16_t>(kMpEndianSize + sizeof(kMpfSig)); + dataStruct->write32(Endian_SwapBE32(indexIfdOffset)); + + // We will write 3 tags (version, number of images, MP entries). + dataStruct->write16(Endian_SwapBE16(kTagSerializedCount)); + + // Write the version tag. + dataStruct->write16(Endian_SwapBE16(kVersionTag)); + dataStruct->write16(Endian_SwapBE16(kVersionType)); + dataStruct->write32(Endian_SwapBE32(kVersionCount)); + dataStruct->write(kVersionExpected, kVersionSize); + + // Write the number of images. + dataStruct->write16(Endian_SwapBE16(kNumberOfImagesTag)); + dataStruct->write16(Endian_SwapBE16(kNumberOfImagesType)); + dataStruct->write32(Endian_SwapBE32(kNumberOfImagesCount)); + dataStruct->write32(Endian_SwapBE32(kNumPictures)); + + // Write the MP entries. + dataStruct->write16(Endian_SwapBE16(kMPEntryTag)); + dataStruct->write16(Endian_SwapBE16(kMPEntryType)); + dataStruct->write32(Endian_SwapBE32(kMPEntrySize * kNumPictures)); + const uint32_t mpEntryOffset = + static_cast<uint32_t>(dataStruct->getBytesWritten() - // The bytes written so far + sizeof(kMpfSig) + // Excluding the MPF signature + sizeof(uint32_t) + // The 4 bytes for this offset + sizeof(uint32_t)); // The 4 bytes for the attribute IFD offset. + dataStruct->write32(Endian_SwapBE32(mpEntryOffset)); + + // Write the attribute IFD offset (zero because we don't write it). + dataStruct->write32(0); + + // Write the MP entries for primary image + dataStruct->write32( + Endian_SwapBE32(kMPEntryAttributeFormatJpeg | kMPEntryAttributeTypePrimary)); + dataStruct->write32(Endian_SwapBE32(primary_image_size)); + dataStruct->write32(Endian_SwapBE32(primary_image_offset)); + dataStruct->write16(0); + dataStruct->write16(0); + + // Write the MP entries for secondary image + dataStruct->write32(Endian_SwapBE32(kMPEntryAttributeFormatJpeg)); + dataStruct->write32(Endian_SwapBE32(secondary_image_size)); + dataStruct->write32(Endian_SwapBE32(secondary_image_offset)); + dataStruct->write16(0); + dataStruct->write16(0); + + return dataStruct; +} + +} // namespace android::jpegrecoverymap diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp index 4f21ac6372..20c32ed888 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 @@ Color srgbInvOetf(Color e_gamma) { // 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 @@ ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gam //////////////////////////////////////////////////////////////////////////////// // 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 size_t clamp(const size_t& val, const size_t& low, const size_t& high) { } 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 @@ static float pythDistance(float x_diff, float y_diff) { } // 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,11 @@ uint32_t colorToRgba1010102(Color e_gamma) { | (0x3 << 30); // Set alpha to 1.0 } -} // namespace android::recoverymap +uint64_t colorToRgbaF16(Color e_gamma) { + return (uint64_t) floatToHalf(e_gamma.r) + | (((uint64_t) floatToHalf(e_gamma.g)) << 16) + | (((uint64_t) floatToHalf(e_gamma.b)) << 32) + | (((uint64_t) floatToHalf(1.0f)) << 48); +} + +} // namespace android::jpegrecoverymap diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/recoverymaputils.cpp deleted file mode 100644 index 1617b8b97a..0000000000 --- a/libs/jpegrecoverymap/recoverymaputils.cpp +++ /dev/null @@ -1,339 +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> - -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; -} - -// 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(); -} - -} // namespace android::recoverymap diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp index cad273e437..61b3db9c62 100644 --- a/libs/jpegrecoverymap/tests/Android.bp +++ b/libs/jpegrecoverymap/tests/Android.bp @@ -25,49 +25,53 @@ cc_test { name: "libjpegrecoverymap_test", test_suites: ["device-tests"], srcs: [ - "recoverymap_test.cpp", + "jpegr_test.cpp", "recoverymapmath_test.cpp", ], shared_libs: [ - "libjpeg", - "libjpegrecoverymap", "libimage_io", + "libjpeg", "liblog", ], static_libs: [ "libgmock", "libgtest", + "libjpegdecoder", + "libjpegencoder", + "libjpegrecoverymap", + "libskia", + "libutils", ], } cc_test { - name: "libjpegencoder_test", + name: "libjpegencoderhelper_test", test_suites: ["device-tests"], srcs: [ - "jpegencoder_test.cpp", + "jpegencoderhelper_test.cpp", ], shared_libs: [ "libjpeg", - "libjpegencoder", "liblog", ], static_libs: [ "libgtest", + "libjpegencoder", ], } cc_test { - name: "libjpegdecoder_test", + name: "libjpegdecoderhelper_test", test_suites: ["device-tests"], srcs: [ - "jpegdecoder_test.cpp", + "jpegdecoderhelper_test.cpp", ], shared_libs: [ "libjpeg", - "libjpegdecoder", "liblog", ], static_libs: [ "libgtest", + "libjpegdecoder", ], } diff --git a/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp b/libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp index 8e013517fb..2f32a5685b 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 @@ protected: Image mYuvImage, mGreyImage; }; -JpegDecoderTest::JpegDecoderTest() {} +JpegDecoderHelperTest::JpegDecoderHelperTest() {} -JpegDecoderTest::~JpegDecoderTest() {} +JpegDecoderHelperTest::~JpegDecoderHelperTest() {} static size_t getFileSize(int fd) { struct stat st; @@ -55,7 +55,7 @@ static size_t getFileSize(int fd) { 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 @@ static bool loadFile(const char filename[], JpegDecoderTest::Image* result) { return true; } -void JpegDecoderTest::SetUp() { +void JpegDecoderHelperTest::SetUp() { if (!loadFile(YUV_IMAGE, &mYuvImage)) { FAIL() << "Load file " << YUV_IMAGE << " failed"; } @@ -85,18 +85,18 @@ void JpegDecoderTest::SetUp() { 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 index 4cd2a5ef8c..095ac2fbf6 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 @@ namespace android::recoverymap { #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 @@ protected: 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 @@ static size_t getFileSize(int fd) { 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 @@ static bool loadFile(const char filename[], JpegEncoderTest::Image* result) { return true; } -void JpegEncoderTest::SetUp() { +void JpegEncoderHelperTest::SetUp() { if (!loadFile(VALID_IMAGE, &mValidImage)) { FAIL() << "Load file " << VALID_IMAGE << " failed"; } @@ -99,27 +99,27 @@ void JpegEncoderTest::SetUp() { 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 index dfab76a2c9..0a7d20a434 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,27 +87,100 @@ static bool loadFile(const char filename[], void*& result, int* fileLength) { return true; } -TEST_F(RecoveryMapTest, build) { +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); +} + +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, JPEGR_OUTPUT_HDR_HLG, 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. - 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); + 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); } -TEST_F(RecoveryMapTest, writeXmpThenRead) { +TEST_F(JpegRTest, writeXmpThenRead) { jpegr_metadata metadata_expected; - metadata_expected.transferFunction = JPEGR_TF_HLG; - metadata_expected.rangeScalingFactor = 1.25; - int length_expected = 1000; + metadata_expected.maxContentBoost = 1.25; + metadata_expected.minContentBoost = 0.75; const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator - std::string xmp = generateXmp(1000, metadata_expected); + std::string xmp = generateXmpForSecondaryImage(metadata_expected); std::vector<uint8_t> xmpData; xmpData.reserve(nameSpaceLength + xmp.size()); @@ -120,12 +191,12 @@ TEST_F(RecoveryMapTest, writeXmpThenRead) { 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); + ASSERT_EQ(metadata_expected.minContentBoost, metadata_read.minContentBoost); } /* Test Encode API-0 and decode */ -TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) { +TEST_F(JpegRTest, encodeFromP010ThenDecode) { int ret; // Load input files. @@ -136,19 +207,19 @@ TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) { 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; } if (SAVE_ENCODING_RESULT) { // Output image data to file - std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr"; + std::string filePath = "/sdcard/Documents/encoded_from_p010_input.jpgr"; std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); if (!imageFile.is_open()) { ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); @@ -157,15 +228,15 @@ TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) { } jpegr_uncompressed_struct decodedJpegR; - int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4; + int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8; decodedJpegR.data = malloc(decodedJpegRSize); - ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR); + ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR); if (ret != OK) { FAIL() << "Error code is " << ret; } if (SAVE_DECODING_RESULT) { // Output image data to file - std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10"; + std::string filePath = "/sdcard/Documents/decoded_from_p010_input.rgb"; std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); if (!imageFile.is_open()) { ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); @@ -178,7 +249,7 @@ TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) { } /* Test Encode API-1 and decode */ -TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrThenDecode) { +TEST_F(JpegRTest, encodeFromRawHdrAndSdrThenDecode) { int ret; // Load input files. @@ -196,12 +267,12 @@ TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrThenDecode) { 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) { @@ -209,7 +280,7 @@ TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrThenDecode) { } if (SAVE_ENCODING_RESULT) { // Output image data to file - std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr"; + std::string filePath = "/sdcard/Documents/encoded_from_p010_yuv420p_input.jpgr"; std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); if (!imageFile.is_open()) { ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); @@ -218,15 +289,15 @@ TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrThenDecode) { } jpegr_uncompressed_struct decodedJpegR; - int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4; + int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8; decodedJpegR.data = malloc(decodedJpegRSize); - ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR); + ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR); if (ret != OK) { FAIL() << "Error code is " << ret; } if (SAVE_DECODING_RESULT) { // Output image data to file - std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10"; + std::string filePath = "/sdcard/Documents/decoded_from_p010_yuv420p_input.rgb"; std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); if (!imageFile.is_open()) { ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); @@ -239,7 +310,7 @@ TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrThenDecode) { } /* Test Encode API-2 and decode */ -TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrAndJpegThenDecode) { +TEST_F(JpegRTest, encodeFromRawHdrAndSdrAndJpegThenDecode) { int ret; // Load input files. @@ -262,19 +333,19 @@ TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrAndJpegThenDecode) { } 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; } if (SAVE_ENCODING_RESULT) { // Output image data to file - std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr"; + std::string filePath = "/sdcard/Documents/encoded_from_p010_yuv420p_jpeg_input.jpgr"; std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); if (!imageFile.is_open()) { ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); @@ -283,15 +354,15 @@ TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrAndJpegThenDecode) { } jpegr_uncompressed_struct decodedJpegR; - int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4; + int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8; decodedJpegR.data = malloc(decodedJpegRSize); - ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR); + ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR); if (ret != OK) { FAIL() << "Error code is " << ret; } if (SAVE_DECODING_RESULT) { // Output image data to file - std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10"; + std::string filePath = "/sdcard/Documents/decoded_from_p010_yuv420p_jpeg_input.rgb"; std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); if (!imageFile.is_open()) { ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); @@ -304,7 +375,7 @@ TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrAndJpegThenDecode) { } /* Test Encode API-3 and decode */ -TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) { +TEST_F(JpegRTest, encodeFromJpegThenDecode) { int ret; // Load input files. @@ -343,19 +414,19 @@ TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) { } 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; } if (SAVE_ENCODING_RESULT) { // Output image data to file - std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr"; + std::string filePath = "/sdcard/Documents/encoded_from_p010_jpeg_input.jpgr"; std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); if (!imageFile.is_open()) { ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); @@ -364,15 +435,15 @@ TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) { } jpegr_uncompressed_struct decodedJpegR; - int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4; + int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8; decodedJpegR.data = malloc(decodedJpegRSize); - ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR); + ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR); if (ret != OK) { FAIL() << "Error code is " << ret; } if (SAVE_DECODING_RESULT) { // Output image data to file - std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10"; + std::string filePath = "/sdcard/Documents/decoded_from_p010_jpeg_input.rgb"; std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); if (!imageFile.is_open()) { ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); @@ -384,4 +455,46 @@ TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) { free(decodedJpegR.data); } +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 diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp index 1d522d1860..6c61ff13d7 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 @@ public: } 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 @@ public: 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, PqInvOetf) { } 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, ColorConversionLookup) { } 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); - - 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(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, 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); + jpegr_metadata metadata = { .maxContentBoost = 4.0f, + .minContentBoost = 1.0f / 4.0f }; + + 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, 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); + + 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()); - - 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(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(), -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); + jpegr_metadata metadata = { .maxContentBoost = 4.0f, + .minContentBoost = 1.0f / 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(), 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); + + 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 }}}; - - 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); + metadata.maxContentBoost = 4.0f; + metadata.minContentBoost = 1.0f / 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 @@ TEST_F(RecoveryMapMathTest, SampleMap) { // 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, GenerateMapLuminancePq) { } 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/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp index b7b2926c73..5306529fcb 100644 --- a/libs/nativewindow/ANativeWindow.cpp +++ b/libs/nativewindow/ANativeWindow.cpp @@ -24,14 +24,49 @@ #include <private/android/AHardwareBufferHelpers.h> +#include <android/binder_libbinder.h> +#include <dlfcn.h> #include <log/log.h> #include <ui/GraphicBuffer.h> -#include <gui/Surface.h> -#include <gui/view/Surface.h> -#include <android/binder_libbinder.h> using namespace android; +#if defined(__ANDROID_APEX__) || defined(__ANDROID_VNDK__) +#error libnativewindow can only be built for system +#endif + +using android_view_Surface_writeToParcel = status_t (*)(ANativeWindow* _Nonnull window, + Parcel* _Nonnull parcel); + +using android_view_Surface_readFromParcel = + status_t (*)(const Parcel* _Nonnull parcel, ANativeWindow* _Nullable* _Nonnull outWindow); + +struct SurfaceParcelables { + android_view_Surface_writeToParcel write = nullptr; + android_view_Surface_readFromParcel read = nullptr; +}; + +const SurfaceParcelables* getSurfaceParcelFunctions() { + static SurfaceParcelables funcs = []() -> SurfaceParcelables { + SurfaceParcelables ret; + void* dl = dlopen("libgui.so", RTLD_NOW); + LOG_ALWAYS_FATAL_IF(!dl, "Failed to find libgui.so"); + ret.write = + (android_view_Surface_writeToParcel)dlsym(dl, "android_view_Surface_writeToParcel"); + LOG_ALWAYS_FATAL_IF(!ret.write, + "libgui.so missing android_view_Surface_writeToParcel; " + "loaded wrong libgui?"); + ret.read = + (android_view_Surface_readFromParcel)dlsym(dl, + "android_view_Surface_readFromParcel"); + LOG_ALWAYS_FATAL_IF(!ret.read, + "libgui.so missing android_view_Surface_readFromParcel; " + "loaded wrong libgui?"); + return ret; + }(); + return &funcs; +} + static int32_t query(ANativeWindow* window, int what) { int value; int res = window->query(window, what, &value); @@ -64,13 +99,6 @@ static bool isDataSpaceValid(ANativeWindow* window, int32_t dataSpace) { return false; } } -static sp<IGraphicBufferProducer> IGraphicBufferProducer_from_ANativeWindow(ANativeWindow* window) { - return Surface::getIGraphicBufferProducer(window); -} - -static sp<IBinder> SurfaceControlHandle_from_ANativeWindow(ANativeWindow* window) { - return Surface::getSurfaceControlHandle(window); -} /************************************************************************************************** * NDK @@ -355,38 +383,24 @@ int ANativeWindow_setAutoPrerotation(ANativeWindow* window, bool autoPrerotation binder_status_t ANativeWindow_readFromParcel( const AParcel* _Nonnull parcel, ANativeWindow* _Nullable* _Nonnull outWindow) { - const Parcel* nativeParcel = AParcel_viewPlatformParcel(parcel); - - // Use a android::view::Surface to unparcel the window - std::shared_ptr<android::view::Surface> shimSurface = std::shared_ptr<android::view::Surface>(); - status_t ret = shimSurface->readFromParcel(nativeParcel); - if (ret != OK) { - ALOGE("%s: Error: Failed to create android::view::Surface from AParcel", __FUNCTION__); - return STATUS_BAD_VALUE; + auto funcs = getSurfaceParcelFunctions(); + if (funcs->read == nullptr) { + ALOGE("Failed to load Surface_readFromParcel implementation"); + return STATUS_FAILED_TRANSACTION; } - sp<Surface> surface = sp<Surface>::make( - shimSurface->graphicBufferProducer, false, shimSurface->surfaceControlHandle); - ANativeWindow* anw = surface.get(); - ANativeWindow_acquire(anw); - *outWindow = anw; - return STATUS_OK; + const Parcel* nativeParcel = AParcel_viewPlatformParcel(parcel); + return funcs->read(nativeParcel, outWindow); } binder_status_t ANativeWindow_writeToParcel( ANativeWindow* _Nonnull window, AParcel* _Nonnull parcel) { - int value; - int err = (*window->query)(window, NATIVE_WINDOW_CONCRETE_TYPE, &value); - if (err != OK || value != NATIVE_WINDOW_SURFACE) { - ALOGE("Error: ANativeWindow is not backed by Surface"); - return STATUS_BAD_VALUE; + auto funcs = getSurfaceParcelFunctions(); + if (funcs->write == nullptr) { + ALOGE("Failed to load Surface_writeToParcel implementation"); + return STATUS_FAILED_TRANSACTION; } - // Use a android::view::Surface to parcelize the window - std::shared_ptr<android::view::Surface> shimSurface = std::shared_ptr<android::view::Surface>(); - shimSurface->graphicBufferProducer = IGraphicBufferProducer_from_ANativeWindow(window); - shimSurface->surfaceControlHandle = SurfaceControlHandle_from_ANativeWindow(window); - Parcel* nativeParcel = AParcel_viewPlatformParcel(parcel); - return shimSurface->writeToParcel(nativeParcel); + return funcs->write(window, nativeParcel); } /************************************************************************************************** diff --git a/libs/renderengine/ExternalTexture.cpp b/libs/renderengine/ExternalTexture.cpp index 84771c0917..210dca5429 100644 --- a/libs/renderengine/ExternalTexture.cpp +++ b/libs/renderengine/ExternalTexture.cpp @@ -39,7 +39,7 @@ ExternalTexture::ExternalTexture(const sp<GraphicBuffer>& buffer, } ExternalTexture::~ExternalTexture() { - mRenderEngine.unmapExternalTextureBuffer(mBuffer); + mRenderEngine.unmapExternalTextureBuffer(std::move(mBuffer)); } } // namespace android::renderengine::impl diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp index 13f766c3e1..0d7df101f4 100644 --- a/libs/renderengine/gl/GLESRenderEngine.cpp +++ b/libs/renderengine/gl/GLESRenderEngine.cpp @@ -800,7 +800,7 @@ status_t GLESRenderEngine::cacheExternalTextureBufferInternal(const sp<GraphicBu return NO_ERROR; } -void GLESRenderEngine::unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) { +void GLESRenderEngine::unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) { mImageManager->releaseAsync(buffer->getId(), nullptr); } @@ -1262,7 +1262,7 @@ void GLESRenderEngine::drawLayersInternal( // Do not cache protected EGLImage, protected memory is limited. if (gBuf->getUsage() & GRALLOC_USAGE_PROTECTED) { - unmapExternalTextureBuffer(gBuf); + unmapExternalTextureBuffer(std::move(gBuf)); } } diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h index 1b3492154b..402ff529d7 100644 --- a/libs/renderengine/gl/GLESRenderEngine.h +++ b/libs/renderengine/gl/GLESRenderEngine.h @@ -101,7 +101,7 @@ protected: size_t getMaxViewportDims() const override; void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, bool isRenderable) EXCLUDES(mRenderingMutex); - void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) EXCLUDES(mRenderingMutex); + void unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) EXCLUDES(mRenderingMutex); bool canSkipPostRenderCleanup() const override; void drawLayersInternal(const std::shared_ptr<std::promise<FenceResult>>&& resultPromise, const DisplaySettings& display, diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h index 39621cd080..0d910c9b29 100644 --- a/libs/renderengine/include/renderengine/RenderEngine.h +++ b/libs/renderengine/include/renderengine/RenderEngine.h @@ -231,7 +231,7 @@ protected: // asynchronously, but the caller can expect that map/unmap calls are performed in a manner // that's conflict serializable, i.e. unmap a buffer should never occur before binding the // buffer if the caller called mapExternalTextureBuffer before calling unmap. - virtual void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) = 0; + virtual void unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) = 0; // A thread safe query to determine if any post rendering cleanup is necessary. Returning true // is a signal that calling the postRenderCleanup method would be a no-op and that callers can diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h index e3ce85dd07..d3035e24a5 100644 --- a/libs/renderengine/include/renderengine/mock/RenderEngine.h +++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h @@ -63,7 +63,7 @@ public: protected: // mock renderengine still needs to implement these, but callers should never need to call them. void mapExternalTextureBuffer(const sp<GraphicBuffer>&, bool) {} - void unmapExternalTextureBuffer(const sp<GraphicBuffer>&) {} + void unmapExternalTextureBuffer(sp<GraphicBuffer>&&) {} }; } // namespace mock diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index 413811ef99..5965d417f4 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -423,7 +423,7 @@ void SkiaRenderEngine::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, } } -void SkiaRenderEngine::unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) { +void SkiaRenderEngine::unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) { ATRACE_CALL(); std::lock_guard<std::mutex> lock(mRenderingMutex); if (const auto& iter = mGraphicBufferExternalRefs.find(buffer->getId()); diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h index 1973c7d065..dd6646ba71 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.h +++ b/libs/renderengine/skia/SkiaRenderEngine.h @@ -130,7 +130,7 @@ protected: private: void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, bool isRenderable) override final; - void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) override final; + void unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) override final; bool canSkipPostRenderCleanup() const override final; void initCanvas(SkCanvas* canvas, const DisplaySettings& display); diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp index 8d99f3d320..936e31679f 100644 --- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp @@ -444,8 +444,11 @@ VulkanInterface initVulkanInterface(bool protectedContent = false) { ALOGD("Trying to create Vk device with protectedContent=%d (success)", protectedContent); VkQueue graphicsQueue; - VK_GET_DEV_PROC(device, GetDeviceQueue); - vkGetDeviceQueue(device, graphicsQueueIndex, 0, &graphicsQueue); + VK_GET_DEV_PROC(device, GetDeviceQueue2); + const VkDeviceQueueInfo2 deviceQueueInfo2 = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2, nullptr, + deviceQueueCreateFlags, + (uint32_t)graphicsQueueIndex, 0}; + vkGetDeviceQueue2(device, &deviceQueueInfo2, &graphicsQueue); VK_GET_DEV_PROC(device, DeviceWaitIdle); VK_GET_DEV_PROC(device, DestroyDevice); diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp index 8aa41b3e50..6a1561abcd 100644 --- a/libs/renderengine/threaded/RenderEngineThreaded.cpp +++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp @@ -230,16 +230,17 @@ void RenderEngineThreaded::mapExternalTextureBuffer(const sp<GraphicBuffer>& buf mCondition.notify_one(); } -void RenderEngineThreaded::unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) { +void RenderEngineThreaded::unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) { ATRACE_CALL(); // This function is designed so it can run asynchronously, so we do not need to wait // for the futures. { std::lock_guard lock(mThreadMutex); - mFunctionCalls.push([=](renderengine::RenderEngine& instance) { - ATRACE_NAME("REThreaded::unmapExternalTextureBuffer"); - instance.unmapExternalTextureBuffer(buffer); - }); + mFunctionCalls.push( + [=, buffer = std::move(buffer)](renderengine::RenderEngine& instance) mutable { + ATRACE_NAME("REThreaded::unmapExternalTextureBuffer"); + instance.unmapExternalTextureBuffer(std::move(buffer)); + }); } mCondition.notify_one(); } diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h index 168e2d2b06..6eb108e064 100644 --- a/libs/renderengine/threaded/RenderEngineThreaded.h +++ b/libs/renderengine/threaded/RenderEngineThreaded.h @@ -69,7 +69,7 @@ public: protected: void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, bool isRenderable) override; - void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) override; + void unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) override; bool canSkipPostRenderCleanup() const override; void drawLayersInternal(const std::shared_ptr<std::promise<FenceResult>>&& resultPromise, const DisplaySettings& display, diff --git a/libs/sensor/ISensorServer.cpp b/libs/sensor/ISensorServer.cpp index 2278d391b5..019d6cb070 100644 --- a/libs/sensor/ISensorServer.cpp +++ b/libs/sensor/ISensorServer.cpp @@ -67,7 +67,11 @@ public: v.setCapacity(n); while (n) { n--; - reply.read(s); + if(reply.read(s) != OK) { + ALOGE("Failed to read reply from getSensorList"); + v.clear(); + break; + } v.add(s); } return v; @@ -85,7 +89,11 @@ public: v.setCapacity(n); while (n) { n--; - reply.read(s); + if(reply.read(s) != OK) { + ALOGE("Failed to read reply from getDynamicSensorList"); + v.clear(); + break; + } v.add(s); } return v; @@ -131,10 +139,12 @@ public: } virtual sp<ISensorEventConnection> createSensorDirectConnection(const String16& opPackageName, - uint32_t size, int32_t type, int32_t format, const native_handle_t *resource) { + int deviceId, uint32_t size, int32_t type, int32_t format, + const native_handle_t *resource) { Parcel data, reply; data.writeInterfaceToken(ISensorServer::getInterfaceDescriptor()); data.writeString16(opPackageName); + data.writeInt32(deviceId); data.writeUint32(size); data.writeInt32(type); data.writeInt32(format); @@ -229,6 +239,7 @@ status_t BnSensorServer::onTransact( case CREATE_SENSOR_DIRECT_CONNECTION: { CHECK_INTERFACE(ISensorServer, data, reply); const String16& opPackageName = data.readString16(); + const int deviceId = data.readInt32(); uint32_t size = data.readUint32(); int32_t type = data.readInt32(); int32_t format = data.readInt32(); @@ -238,8 +249,8 @@ status_t BnSensorServer::onTransact( return BAD_VALUE; } native_handle_set_fdsan_tag(resource); - sp<ISensorEventConnection> ch = - createSensorDirectConnection(opPackageName, size, type, format, resource); + sp<ISensorEventConnection> ch = createSensorDirectConnection( + opPackageName, deviceId, size, type, format, resource); native_handle_close_with_tag(resource); native_handle_delete(resource); reply->writeStrongBinder(IInterface::asBinder(ch)); diff --git a/libs/sensor/Sensor.cpp b/libs/sensor/Sensor.cpp index fb895f59b9..b6ea77deb5 100644 --- a/libs/sensor/Sensor.cpp +++ b/libs/sensor/Sensor.cpp @@ -628,7 +628,13 @@ bool Sensor::unflattenString8(void const*& buffer, size_t& size, String8& output return false; } outputString8.setTo(static_cast<char const*>(buffer), len); + + if (size < FlattenableUtils::align<4>(len)) { + ALOGE("Malformed Sensor String8 field. Should be in a 4-byte aligned buffer but is not."); + return false; + } FlattenableUtils::advance(buffer, size, FlattenableUtils::align<4>(len)); + return true; } diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp index 27482768f2..ba190e0855 100644 --- a/libs/sensor/SensorManager.cpp +++ b/libs/sensor/SensorManager.cpp @@ -92,6 +92,16 @@ SensorManager& SensorManager::getInstanceForPackage(const String16& packageName) return *sensorManager; } +void SensorManager::removeInstanceForPackage(const String16& packageName) { + Mutex::Autolock _l(sLock); + auto iterator = sPackageInstances.find(packageName); + if (iterator != sPackageInstances.end()) { + SensorManager* sensorManager = iterator->second; + delete sensorManager; + sPackageInstances.erase(iterator); + } +} + SensorManager::SensorManager(const String16& opPackageName) : mSensorList(nullptr), mOpPackageName(opPackageName), mDirectConnectionHandle(1) { Mutex::Autolock _l(mLock); @@ -166,6 +176,11 @@ status_t SensorManager::assertStateLocked() { mSensors = mSensorServer->getSensorList(mOpPackageName); size_t count = mSensors.size(); + if (count == 0) { + ALOGE("Failed to get Sensor list"); + mSensorServer.clear(); + return UNKNOWN_ERROR; + } mSensorList = static_cast<Sensor const**>(malloc(count * sizeof(Sensor*))); LOG_ALWAYS_FATAL_IF(mSensorList == nullptr, "mSensorList NULL"); @@ -300,6 +315,12 @@ bool SensorManager::isDataInjectionEnabled() { int SensorManager::createDirectChannel( size_t size, int channelType, const native_handle_t *resourceHandle) { + static constexpr int DEFAULT_DEVICE_ID = 0; + return createDirectChannel(DEFAULT_DEVICE_ID, size, channelType, resourceHandle); +} + +int SensorManager::createDirectChannel( + int deviceId, size_t size, int channelType, const native_handle_t *resourceHandle) { Mutex::Autolock _l(mLock); if (assertStateLocked() != NO_ERROR) { return NO_INIT; @@ -312,7 +333,7 @@ int SensorManager::createDirectChannel( } sp<ISensorEventConnection> conn = - mSensorServer->createSensorDirectConnection(mOpPackageName, + mSensorServer->createSensorDirectConnection(mOpPackageName, deviceId, static_cast<uint32_t>(size), static_cast<int32_t>(channelType), SENSOR_DIRECT_FMT_SENSORS_EVENT, resourceHandle); diff --git a/libs/sensor/include/sensor/ISensorServer.h b/libs/sensor/include/sensor/ISensorServer.h index 3295196ac4..58157280b1 100644 --- a/libs/sensor/include/sensor/ISensorServer.h +++ b/libs/sensor/include/sensor/ISensorServer.h @@ -50,7 +50,8 @@ public: virtual int32_t isDataInjectionEnabled() = 0; virtual sp<ISensorEventConnection> createSensorDirectConnection(const String16& opPackageName, - uint32_t size, int32_t type, int32_t format, const native_handle_t *resource) = 0; + int deviceId, uint32_t size, int32_t type, int32_t format, + const native_handle_t *resource) = 0; virtual int setOperationParameter( int32_t handle, int32_t type, const Vector<float> &floats, const Vector<int32_t> &ints) = 0; diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h index 0798da292a..bb44cb8869 100644 --- a/libs/sensor/include/sensor/SensorManager.h +++ b/libs/sensor/include/sensor/SensorManager.h @@ -54,6 +54,7 @@ class SensorManager : public ASensorManager { public: static SensorManager& getInstanceForPackage(const String16& packageName); + static void removeInstanceForPackage(const String16& packageName); ~SensorManager(); ssize_t getSensorList(Sensor const* const** list); @@ -65,6 +66,8 @@ public: String8 packageName = String8(""), int mode = 0, String16 attributionTag = String16("")); bool isDataInjectionEnabled(); int createDirectChannel(size_t size, int channelType, const native_handle_t *channelData); + int createDirectChannel( + int deviceId, size_t size, int channelType, const native_handle_t *channelData); void destroyDirectChannel(int channelNativeHandle); int configureDirectChannel(int channelNativeHandle, int sensorHandle, int rateLevel); int setOperationParameter(int handle, int type, const Vector<float> &floats, const Vector<int32_t> &ints); diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp index 7459466d31..c3af996b13 100644 --- a/libs/ui/Gralloc4.cpp +++ b/libs/ui/Gralloc4.cpp @@ -22,6 +22,8 @@ #include <aidlcommonsupport/NativeHandle.h> #include <android/binder_enums.h> #include <android/binder_manager.h> +#include <cutils/android_filesystem_config.h> +#include <cutils/multiuser.h> #include <gralloctypes/Gralloc4.h> #include <hidl/ServiceManagement.h> #include <hwbinder/IPCThreadState.h> @@ -1195,8 +1197,15 @@ Gralloc4Allocator::Gralloc4Allocator(const Gralloc4Mapper& mapper) : mMapper(map mAllocator = IAllocator::getService(); if (__builtin_available(android 31, *)) { if (hasIAllocatorAidl()) { - mAidlAllocator = AidlIAllocator::fromBinder(ndk::SpAIBinder( - AServiceManager_waitForService(kAidlAllocatorServiceName.c_str()))); + // TODO(b/269517338): Perform the isolated checking for this in service manager instead. + uid_t aid = multiuser_get_app_id(getuid()); + if (aid >= AID_ISOLATED_START && aid <= AID_ISOLATED_END) { + mAidlAllocator = AidlIAllocator::fromBinder(ndk::SpAIBinder( + AServiceManager_getService(kAidlAllocatorServiceName.c_str()))); + } else { + mAidlAllocator = AidlIAllocator::fromBinder(ndk::SpAIBinder( + AServiceManager_waitForService(kAidlAllocatorServiceName.c_str()))); + } ALOGE_IF(!mAidlAllocator, "AIDL IAllocator declared but failed to get service"); } } diff --git a/opengl/OWNERS b/opengl/OWNERS index 379f7638f0..3d60a1dad6 100644 --- a/opengl/OWNERS +++ b/opengl/OWNERS @@ -1,11 +1,6 @@ -abdolrashidi@google.com -cclao@google.com chrisforbes@google.com cnorthrop@google.com ianelliott@google.com jessehall@google.com -lfy@google.com lpy@google.com -romanl@google.com vantablack@google.com -yuxinhu@google.com diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp index 750338bd84..49e1cbafb4 100644 --- a/opengl/libs/Android.bp +++ b/opengl/libs/Android.bp @@ -144,6 +144,7 @@ cc_library_static { srcs: [ "EGL/BlobCache.cpp", "EGL/FileBlobCache.cpp", + "EGL/MultifileBlobCache.cpp", ], export_include_dirs: ["EGL"], } @@ -160,7 +161,6 @@ cc_library_shared { 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 @@ cc_test { srcs: [ "EGL/BlobCache.cpp", "EGL/BlobCache_test.cpp", + "EGL/MultifileBlobCache.cpp", + "EGL/MultifileBlobCache_test.cpp", + ], + shared_libs: [ + "libutils", ], } diff --git a/opengl/libs/EGL/BlobCache.cpp b/opengl/libs/EGL/BlobCache.cpp index 86c788d3b3..aecfc6b077 100644 --- a/opengl/libs/EGL/BlobCache.cpp +++ b/opengl/libs/EGL/BlobCache.cpp @@ -231,7 +231,7 @@ int BlobCache::flatten(void* buffer, size_t size) const { int BlobCache::unflatten(void const* buffer, size_t size) { // All errors should result in the BlobCache being in an empty state. - mCacheEntries.clear(); + clear(); // Read the cache header if (size < sizeof(Header)) { @@ -258,7 +258,7 @@ int BlobCache::unflatten(void const* buffer, size_t size) { size_t numEntries = header->mNumEntries; for (size_t i = 0; i < numEntries; i++) { if (byteOffset + sizeof(EntryHeader) > size) { - mCacheEntries.clear(); + clear(); ALOGE("unflatten: not enough room for cache entry headers"); return -EINVAL; } @@ -270,7 +270,7 @@ int BlobCache::unflatten(void const* buffer, size_t size) { size_t totalSize = align4(entrySize); if (byteOffset + totalSize > size) { - mCacheEntries.clear(); + clear(); ALOGE("unflatten: not enough room for cache entry headers"); return -EINVAL; } diff --git a/opengl/libs/EGL/BlobCache.h b/opengl/libs/EGL/BlobCache.h index ff03d30099..52078ff5fd 100644 --- a/opengl/libs/EGL/BlobCache.h +++ b/opengl/libs/EGL/BlobCache.h @@ -117,7 +117,10 @@ public: // clear flushes out all contents of the cache then the BlobCache, leaving // it in an empty state. - void clear() { mCacheEntries.clear(); } + void clear() { + mCacheEntries.clear(); + mTotalSize = 0; + } protected: // mMaxTotalSize is the maximum size that all cache entries can occupy. This diff --git a/opengl/libs/EGL/BlobCache_test.cpp b/opengl/libs/EGL/BlobCache_test.cpp index ceea0fb979..450c12837b 100644 --- a/opengl/libs/EGL/BlobCache_test.cpp +++ b/opengl/libs/EGL/BlobCache_test.cpp @@ -466,4 +466,31 @@ TEST_F(BlobCacheFlattenTest, UnflattenCatchesBufferTooSmall) { ASSERT_EQ(size_t(0), mBC2->get("abcd", 4, buf, 4)); } +// Test for a divide by zero bug (b/239862516). Before the fix, unflatten() would not reset +// mTotalSize when it encountered an error, which would trigger division by 0 in clean() in the +// right conditions. +TEST_F(BlobCacheFlattenTest, SetAfterFailedUnflatten) { + // isCleanable() must be true, so mTotalSize must be > mMaxTotalSize / 2 after unflattening + // after one entry is lost. To make this the case, MaxTotalSize is 30 and three 10 sized + // entries are used. One of those entries is lost, resulting in mTotalSize=20 + const size_t kMaxKeySize = 10; + const size_t kMaxValueSize = 10; + const size_t kMaxTotalSize = 30; + mBC.reset(new BlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize)); + mBC2.reset(new BlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize)); + mBC->set("aaaaa", 5, "aaaaa", 5); + mBC->set("bbbbb", 5, "bbbbb", 5); + mBC->set("ccccc", 5, "ccccc", 5); + + size_t size = mBC->getFlattenedSize(); + uint8_t* flat = new uint8_t[size]; + ASSERT_EQ(OK, mBC->flatten(flat, size)); + + ASSERT_EQ(BAD_VALUE, mBC2->unflatten(flat, size - 10)); + delete[] flat; + + // This line will trigger clean() which caused a crash. + mBC2->set("dddddddddd", 10, "dddddddddd", 10); +} + } // namespace android diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp new file mode 100644 index 0000000000..99af299f8d --- /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 0000000000..c0cc9dc2a9 --- /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 0000000000..1a55a4fcdd --- /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 1e8a34863d..3dc93ee0b7 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; - -// Multifile cache size limit -constexpr size_t kMultifileCacheByteLimit = 64 * 1024 * 1024; +static const unsigned int kDeferredMonolithicSaveDelay = 4; -// 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 @@ static EGLsizeiANDROID getBlob(const void* key, EGLsizeiANDROID keySize, void* v // 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 @@ void egl_cache_t::initialize(egl_display_t* display) { 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,16 +110,6 @@ void egl_cache_t::initialize(egl_display_t* display) { } } - // 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\""); - mMultifileMode = false; - } - - if (mMultifileMode) { - mCacheByteLimit = kMultifileCacheByteLimit; - } - mInitialized = true; } @@ -133,10 +119,10 @@ void egl_cache_t::terminate() { mBlobCache->writeToFile(); } mBlobCache = nullptr; - if (mMultifileMode) { - checkMultifileCacheSize(mCacheByteLimit); + if (mMultifileBlobCache) { + mMultifileBlobCache->finish(); } - mMultifileMode = false; + mMultifileBlobCache = nullptr; mInitialized = false; } @@ -149,22 +135,12 @@ void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void* return; } + updateMode(); + 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 +148,7 @@ void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void* if (!mSavePending) { mSavePending = true; std::thread deferredSaveThread([this]() { - sleep(deferredSaveDelay); + sleep(kDeferredMonolithicSaveDelay); std::lock_guard<std::mutex> lock(mMutex); if (mInitialized && mBlobCache) { mBlobCache->writeToFile(); @@ -194,17 +170,25 @@ EGLsizeiANDROID egl_cache_t::getBlob(const void* key, EGLsizeiANDROID keySize, v return 0; } + updateMode(); + 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 +200,7 @@ void egl_cache_t::setCacheLimit(int64_t cacheByteLimit) { 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 +210,8 @@ void egl_cache_t::setCacheLimit(int64_t cacheByteLimit) { 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(); @@ -235,11 +219,65 @@ size_t egl_cache_t::getCacheSize() { return 0; } +void egl_cache_t::updateMode() { + // We don't set the mode in the constructor because these checks have + // a non-trivial cost, and not all processes that instantiate egl_cache_t + // will use it. + + // If we've already set the mode, skip these checks + static bool checked = false; + if (checked) { + return; + } + checked = true; + + // 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 = 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); + } +} + 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 2dcd803324..ae6d38146e 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 egl_display_t; 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 @@ public: // 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); @@ -79,12 +88,18 @@ private: egl_cache_t(const egl_cache_t&); // not implemented void operator=(const egl_cache_t&); // not implemented + // Check system properties to determine which blobcache mode should be used + void updateMode(); + // getBlobCacheLocked returns the BlobCache object being used to store the // key/value blob pairs. If the BlobCache object has not yet been created, // this will do so, loading the serialized cache contents from disk if // 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 +113,9 @@ private: // 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 +141,7 @@ private: 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 48e557f190..0000000000 --- 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 ee5fe8108d..0000000000 --- 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 265bec492e..2b3e3a46af 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 @@ using namespace std::literals; 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 @@ protected: 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 @@ TEST_F(EGLCacheTest, UninitializedCacheAlwaysMisses) { 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 @@ TEST_F(EGLCacheTest, InitializedCacheAlwaysHits) { 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 @@ TEST_F(EGLCacheTest, TerminatedCacheAlwaysMisses) { 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 @@ TEST_F(EGLCacheTest, ReinitializedCacheContainsValues) { 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 @@ std::string EGLCacheTest::getCachefileName() { 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 @@ std::string EGLCacheTest::getCachefileName() { // 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 @@ TEST_F(EGLCacheTest, ModifiedCacheMisses) { 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 @@ TEST_F(EGLCacheTest, ModifiedCacheMisses) { // 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 @@ TEST_F(EGLCacheTest, TerminatedCacheBelowCacheLimit) { 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/batteryservice/include/batteryservice/BatteryService.h b/services/batteryservice/include/batteryservice/BatteryService.h index a2e4115bd6..bf6189d7af 100644 --- a/services/batteryservice/include/batteryservice/BatteryService.h +++ b/services/batteryservice/include/batteryservice/BatteryService.h @@ -37,6 +37,7 @@ enum { BATTERY_PROP_CHARGING_POLICY = 7, // equals BATTERY_PROPERTY_CHARGING_POLICY BATTERY_PROP_MANUFACTURING_DATE = 8, // equals BATTERY_PROPERTY_MANUFACTURING_DATE BATTERY_PROP_FIRST_USAGE_DATE = 9, // equals BATTERY_PROPERTY_FIRST_USAGE_DATE + BATTERY_PROP_STATE_OF_HEALTH = 10, // equals BATTERY_PROPERTY_STATE_OF_HEALTH }; struct BatteryProperties { diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING index cacf30bd82..495334e99a 100644 --- a/services/inputflinger/TEST_MAPPING +++ b/services/inputflinger/TEST_MAPPING @@ -42,10 +42,15 @@ "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.VerifyInputEventTest" + "include-filter": "android.view.cts.TouchDelegateTest", + "include-filter": "android.view.cts.VelocityTrackerTest", + "include-filter": "android.view.cts.VerifyInputEventTest", + "include-filter": "android.view.cts.ViewTest", + "include-filter": "android.view.cts.ViewUnbufferedTest" } ] }, @@ -53,7 +58,8 @@ "name": "CtsWidgetTestCases", "options": [ { - "include-filter": "android.widget.cts.NumberPickerTest" + "include-filter": "android.widget.cts.NumberPickerTest", + "include-filter": "android.widget.cts.SeekBarTest" } ] }, @@ -131,10 +137,16 @@ "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.VerifyInputEventTest" + "include-filter": "android.view.cts.TouchDelegateTest", + "include-filter": "android.view.cts.VelocityTrackerTest", + "include-filter": "android.view.cts.VerifyInputEventTest", + "include-filter": "android.view.cts.ViewTest", + "include-filter": "android.view.cts.ViewUnbufferedTest" } ] }, @@ -142,7 +154,8 @@ "name": "CtsWidgetTestCases", "options": [ { - "include-filter": "android.widget.cts.NumberPickerTest" + "include-filter": "android.widget.cts.NumberPickerTest", + "include-filter": "android.widget.cts.SeekBarTest" } ] }, diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp index ab5c5ef99c..da4e42f4fe 100644 --- a/services/inputflinger/dispatcher/Android.bp +++ b/services/inputflinger/dispatcher/Android.bp @@ -34,6 +34,7 @@ filegroup { srcs: [ "AnrTracker.cpp", "Connection.cpp", + "DebugConfig.cpp", "DragState.cpp", "Entry.cpp", "FocusResolver.cpp", diff --git a/services/inputflinger/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h index 512cb6e692..83e6a60602 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 @@ struct CancelationOptions { 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 @@ struct CancelationOptions { 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/DebugConfig.cpp b/services/inputflinger/dispatcher/DebugConfig.cpp new file mode 100644 index 0000000000..764194d3d0 --- /dev/null +++ b/services/inputflinger/dispatcher/DebugConfig.cpp @@ -0,0 +1,40 @@ +/* + * 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 "DebugConfig.h" + +#include <android-base/properties.h> + +namespace android::inputdispatcher { + +const bool IS_DEBUGGABLE_BUILD = +#if defined(__ANDROID__) + android::base::GetBoolProperty("ro.debuggable", false); +#else + true; +#endif + +bool debugInboundEventDetails() { + if (!IS_DEBUGGABLE_BUILD) { + static const bool DEBUG_INBOUND_EVENT_DETAILS = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundEvent", + ANDROID_LOG_INFO); + return DEBUG_INBOUND_EVENT_DETAILS; + } + return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundEvent", ANDROID_LOG_INFO); +} + +} // namespace android::inputdispatcher diff --git a/services/inputflinger/dispatcher/DebugConfig.h b/services/inputflinger/dispatcher/DebugConfig.h index d2ad40754a..0e260a7a03 100644 --- a/services/inputflinger/dispatcher/DebugConfig.h +++ b/services/inputflinger/dispatcher/DebugConfig.h @@ -22,12 +22,20 @@ #include <log/log_event_list.h> namespace android::inputdispatcher { + +/** + * Signals whether this is a debuggable Android build. + * This is populated by reading the value of the "ro.debuggable" property. + */ +extern const bool IS_DEBUGGABLE_BUILD; + /** * Log detailed debug messages about each inbound event notification to the dispatcher. - * Enable this via "adb shell setprop log.tag.InputDispatcherInboundEvent DEBUG" (requires restart) + * Enable this via "adb shell setprop log.tag.InputDispatcherInboundEvent DEBUG". + * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately + * on debuggable builds (e.g. userdebug). */ -const bool DEBUG_INBOUND_EVENT_DETAILS = - __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundEvent", ANDROID_LOG_INFO); +bool debugInboundEventDetails(); /** * Log detailed debug messages about each outbound event processed by the dispatcher. @@ -90,4 +98,5 @@ const bool DEBUG_APP_SWITCH = */ const bool DEBUG_HOVER = __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Hover", ANDROID_LOG_INFO); + } // namespace android::inputdispatcher diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp index ce7c882f7d..b625a1b95b 100644 --- a/services/inputflinger/dispatcher/Entry.cpp +++ b/services/inputflinger/dispatcher/Entry.cpp @@ -14,16 +14,17 @@ * limitations under the License. */ +#define LOG_TAG "InputDispatcher" + #include "Entry.h" #include "Connection.h" +#include "DebugConfig.h" -#include <android-base/properties.h> #include <android-base/stringprintf.h> #include <cutils/atomic.h> #include <inttypes.h> -using android::base::GetBoolProperty; using android::base::StringPrintf; namespace android::inputdispatcher { @@ -172,7 +173,7 @@ KeyEntry::KeyEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t sou KeyEntry::~KeyEntry() {} std::string KeyEntry::getDescription() const { - if (!GetBoolProperty("ro.debuggable", false)) { + if (!IS_DEBUGGABLE_BUILD) { return "KeyEvent"; } return StringPrintf("KeyEvent(deviceId=%d, eventTime=%" PRIu64 ", source=%s, displayId=%" PRId32 @@ -242,7 +243,7 @@ MotionEntry::MotionEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32 MotionEntry::~MotionEntry() {} std::string MotionEntry::getDescription() const { - if (!GetBoolProperty("ro.debuggable", false)) { + if (!IS_DEBUGGABLE_BUILD) { return "MotionEvent"; } std::string msg; @@ -292,7 +293,7 @@ std::string SensorEntry::getDescription() const { deviceId, inputEventSourceToString(source).c_str(), ftl::enum_string(sensorType).c_str(), accuracy, hwTimestamp); - if (!GetBoolProperty("ro.debuggable", false)) { + if (IS_DEBUGGABLE_BUILD) { for (size_t i = 0; i < values.size(); i++) { if (i > 0) { msg += ", "; diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 143d25ce44..e793f56f30 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -190,7 +190,7 @@ bool validateMotionEvent(int32_t action, int32_t actionButton, size_t pointerCou 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 @@ bool validateMotionEvent(int32_t action, int32_t actionButton, size_t pointerCou 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 @@ bool haveSameApplicationToken(const WindowInfo* first, const WindowInfo* second) 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 @@ std::unique_ptr<DispatchEntry> createDispatchEntry( // 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 @@ std::vector<TouchedWindow> getHoveringWindowsLocked(const TouchState* oldState, 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 @@ std::vector<TouchedWindow> getHoveringWindowsLocked(const TouchState* oldState, 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; } @@ -1165,7 +1176,7 @@ InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x, int32_t 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); } } @@ -1196,7 +1207,7 @@ void InputDispatcher::dropInboundEventLocked(const EventEntry& entry, DropReason const char* reason; switch (dropReason) { case DropReason::POLICY: - if (DEBUG_INBOUND_EVENT_DETAILS) { + if (debugInboundEventDetails()) { ALOGD("Dropped event because policy consumed it."); } reason = "inbound event was dropped because the policy consumed it"; @@ -1585,7 +1596,7 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<Key } else if (entry->action == AKEY_EVENT_ACTION_UP && mKeyRepeatState.lastKeyEntry && mKeyRepeatState.lastKeyEntry->deviceId != entry->deviceId) { // The key on device 'deviceId' is still down, do not stop key repeat - if (DEBUG_INBOUND_EVENT_DETAILS) { + if (debugInboundEventDetails()) { ALOGD("deviceId=%d got KEY_UP as stale", entry->deviceId); } } else if (!entry->syntheticRepeat) { @@ -1662,7 +1673,7 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<Key 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)); @@ -1771,7 +1782,7 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr< addWindowTargetLocked(focusedWindow, InputTarget::Flags::FOREGROUND | InputTarget::Flags::DISPATCH_AS_IS, - BitSet32(0), getDownTime(*entry), inputTargets); + /*pointerIds=*/{}, getDownTime(*entry), inputTargets); } } if (injectionResult == InputEventInjectionResult::PENDING) { @@ -2126,7 +2137,7 @@ bool InputDispatcher::shouldSplitTouch(const TouchState& touchState, // 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; } } @@ -2173,17 +2184,20 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction; const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE); + // If pointers are already down, let's finish the current gesture and ignore the new events + // from another device. However, if the new event is a down event, let's cancel the current + // touch and let the new one take over. + if (switchedDevice && wasDown && !isDown) { + LOG(INFO) << "Dropping event because a pointer for device " << oldState->deviceId + << " is already down in display " << displayId << ": " << entry.getDescription(); + // TODO(b/211379801): test multiple simultaneous input streams. + outInjectionResult = InputEventInjectionResult::FAILED; + return {}; // wrong device + } + if (newGesture) { - bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN; - if (switchedDevice && tempTouchState.isDown() && !down && !isHoverAction) { - ALOGI("Dropping event because a pointer for a different device is already down " - "in display %" PRId32, - displayId); - // TODO: test multiple simultaneous input streams. - outInjectionResult = InputEventInjectionResult::FAILED; - return {}; // wrong device - } - tempTouchState.clearWindowsWithoutPointers(); + // If a new gesture is starting, clear the touch state completely. + tempTouchState.reset(); tempTouchState.deviceId = entry.deviceId; tempTouchState.source = entry.source; isSplit = false; @@ -2191,7 +2205,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( ALOGI("Dropping move event because a pointer for a different device is already active " "in display %" PRId32, displayId); - // TODO: test multiple simultaneous input streams. + // TODO(b/211379801): test multiple simultaneous input streams. outInjectionResult = InputEventInjectionResult::FAILED; return {}; // wrong device } @@ -2297,14 +2311,18 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( } // 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; + // TODO(b/211379801): Currently, even if pointerIds are empty (hover case), we would + // still add a window to the touch state. We should avoid doing that, but some of the + // later checks ("at least one foreground window") rely on this in order to dispatch + // the event properly, so that needs to be updated, possibly by looking at InputTargets. tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds, isDownOrPointerDown ? std::make_optional(entry.eventTime) @@ -2341,7 +2359,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( // which is a specific behaviour that we want. const int32_t pointerId = entry.pointerProperties[pointerIndex].id; for (TouchedWindow& touchedWindow : tempTouchState.windows) { - if (touchedWindow.pointerIds.hasBit(pointerId) && + 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 @@ -2357,10 +2375,9 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( // If the pointer is not currently down, then ignore the event. if (!tempTouchState.isDown()) { - ALOGD_IF(DEBUG_FOCUS, - "Dropping event because the pointer is not down or we previously " - "dropped the pointer down event in display %" PRId32 ": %s", - displayId, entry.getDescription().c_str()); + LOG(INFO) << "Dropping event because the pointer is not down or we previously " + "dropped the pointer down event in display " + << displayId << ": " << entry.getDescription(); outInjectionResult = InputEventInjectionResult::FAILED; return {}; } @@ -2397,9 +2414,9 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( 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); @@ -2446,7 +2463,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) { continue; } - touchedWindow.pointerIds.markBit(entry.pointerProperties[pointerIndex].id); + touchedWindow.pointerIds.set(entry.pointerProperties[pointerIndex].id); } } } @@ -2518,10 +2535,8 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( } // Success! Output targets from the touch state. - tempTouchState.clearWindowsWithoutPointers(); for (const TouchedWindow& touchedWindow : tempTouchState.windows) { - if (touchedWindow.pointerIds.isEmpty() && - !touchedWindow.hasHoveringPointers(entry.deviceId)) { + 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; @@ -2559,14 +2574,13 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( } else if (maskedAction == AMOTION_EVENT_ACTION_UP) { // Pointer went up. tempTouchState.removeTouchedPointer(entry.pointerProperties[0].id); - tempTouchState.clearWindowsWithoutPointers(); } else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) { // All pointers up or canceled. tempTouchState.reset(); } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { // First pointer went down. - if (oldState && oldState->isDown()) { - ALOGD("Conflicting pointer actions: Down received while already down."); + if (oldState && (oldState->isDown() || oldState->hasHoveringPointers())) { + ALOGD("Conflicting pointer actions: Down received while already down or hovering."); *outConflictingPointerActions = true; } } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) { @@ -2576,8 +2590,8 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( 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; } @@ -2589,6 +2603,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( // state was only valid for this one action. if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) { if (displayId >= 0) { + tempTouchState.clearWindowsWithoutPointers(); mTouchStatesByDisplay[displayId] = tempTouchState; } else { mTouchStatesByDisplay.erase(displayId); @@ -2702,7 +2717,7 @@ void InputDispatcher::addDragEventLocked(const MotionEntry& entry) { 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 = @@ -2730,7 +2745,8 @@ void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHa if (displayInfoIt != mDisplayInfos.end()) { inputTarget.displayTransform = displayInfoIt->second.transform; } else { - ALOGE("DisplayInfo not found for window on display: %d", windowInfo->displayId); + // DisplayInfo not found for this window on display windowInfo->displayId. + // TODO(b/198444055): Make this an error message after 'setInputWindows' API is removed. } inputTargets.push_back(inputTarget); it = inputTargets.end() - 1; @@ -3012,9 +3028,9 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, } 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()); } @@ -3037,9 +3053,13 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, const MotionEntry& originalMotionEntry = static_cast<const MotionEntry&>(*eventEntry); if (inputTarget.pointerIds.count() != originalMotionEntry.pointerCount) { - LOG_ALWAYS_FATAL_IF(!inputTarget.firstDownTimeInTarget.has_value(), - "Splitting motion events requires a down time to be set for the " - "target"); + if (!inputTarget.firstDownTimeInTarget.has_value()) { + logDispatchStateLocked(); + LOG(FATAL) << "Splitting motion events requires a down time to be set for the " + "target on connection " + << connection->getInputChannelName() << " for " + << originalMotionEntry.getDescription(); + } std::unique_ptr<MotionEntry> splitMotionEntry = splitMotionEvent(originalMotionEntry, inputTarget.pointerIds, inputTarget.firstDownTimeInTarget.value()); @@ -3751,9 +3771,9 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( } 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); @@ -3887,8 +3907,9 @@ void InputDispatcher::synthesizeCancelationEventsForWindowLocked( } 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]; @@ -3902,7 +3923,7 @@ std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent( 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( @@ -3918,9 +3939,9 @@ std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent( // 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 " - "a broken sequence of pointer ids from the input device.", - splitPointerCount, pointerIds.count()); + "we expected there to be %zu pointers. This probably means we received " + "a broken sequence of pointer ids from the input device: %s", + splitPointerCount, pointerIds.count(), originalMotionEntry.getDescription().c_str()); return nullptr; } @@ -3932,7 +3953,7 @@ std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent( 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 @@ -3995,7 +4016,7 @@ std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent( } void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) { - if (DEBUG_INBOUND_EVENT_DETAILS) { + if (debugInboundEventDetails()) { ALOGD("notifyConfigurationChanged - eventTime=%" PRId64, args->eventTime); } @@ -4015,18 +4036,20 @@ void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChange /** * If one of the meta shortcuts is detected, process them here: - * Meta + Backspace -> generate BACK - * Meta + Enter -> generate HOME - * This will potentially overwrite keyCode and metaState. + * Meta + Backspace; Meta + Grave; Meta + Left arrow -> generate BACK + * Most System shortcuts are handled in PhoneWindowManager.java except 'Back' shortcuts. Unlike + * Back, other shortcuts DO NOT need to be sent to applications and are fully handled by the system. + * But for Back key and Back shortcuts, we need to send KEYCODE_BACK to applications which can + * potentially handle the back key presses. + * Note: We don't send any Meta based KeyEvents to applications, so we need to convert to a KeyEvent + * where meta modifier is off before sending. Currently only use case is 'Back'. */ void InputDispatcher::accelerateMetaShortcuts(const int32_t deviceId, const int32_t action, int32_t& keyCode, int32_t& metaState) { if (metaState & AMETA_META_ON && action == AKEY_EVENT_ACTION_DOWN) { int32_t newKeyCode = AKEYCODE_UNKNOWN; - if (keyCode == AKEYCODE_DEL) { + if (keyCode == AKEYCODE_DEL || keyCode == AKEYCODE_GRAVE || keyCode == AKEYCODE_DPAD_LEFT) { newKeyCode = AKEYCODE_BACK; - } else if (keyCode == AKEYCODE_ENTER) { - newKeyCode = AKEYCODE_HOME; } if (newKeyCode != AKEYCODE_UNKNOWN) { std::scoped_lock _l(mLock); @@ -4051,14 +4074,15 @@ void InputDispatcher::accelerateMetaShortcuts(const int32_t deviceId, const int3 } void InputDispatcher::notifyKey(const NotifyKeyArgs* args) { - if (DEBUG_INBOUND_EVENT_DETAILS) { - ALOGD("notifyKey - eventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%" PRId32 - "policyFlags=0x%x, action=0x%x, " - "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%" PRId64, - args->eventTime, args->deviceId, args->source, args->displayId, args->policyFlags, - args->action, args->flags, args->keyCode, args->scanCode, args->metaState, - args->downTime); - } + ALOGD_IF(debugInboundEventDetails(), + "notifyKey - id=%" PRIx32 ", eventTime=%" PRId64 + ", deviceId=%d, source=%s, displayId=%" PRId32 + "policyFlags=0x%x, action=%s, flags=0x%x, keyCode=%s, scanCode=0x%x, metaState=0x%x, " + "downTime=%" PRId64, + args->id, args->eventTime, args->deviceId, + inputEventSourceToString(args->source).c_str(), args->displayId, args->policyFlags, + KeyEvent::actionToString(args->action), args->flags, KeyEvent::getLabel(args->keyCode), + args->scanCode, args->metaState, args->downTime); if (!validateKeyEvent(args->action)) { return; } @@ -4129,23 +4153,22 @@ bool InputDispatcher::shouldSendKeyToInputFilterLocked(const NotifyKeyArgs* args } void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) { - if (DEBUG_INBOUND_EVENT_DETAILS) { - ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=0x%x, " + if (debugInboundEventDetails()) { + ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=%s, " "displayId=%" PRId32 ", policyFlags=0x%x, " "action=%s, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, " "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, xCursorPosition=%f, " "yCursorPosition=%f, downTime=%" PRId64, - args->id, args->eventTime, args->deviceId, args->source, args->displayId, - args->policyFlags, MotionEvent::actionToString(args->action).c_str(), - args->actionButton, args->flags, args->metaState, args->buttonState, args->edgeFlags, - args->xPrecision, args->yPrecision, args->xCursorPosition, args->yCursorPosition, - args->downTime); + args->id, args->eventTime, args->deviceId, + inputEventSourceToString(args->source).c_str(), args->displayId, args->policyFlags, + MotionEvent::actionToString(args->action).c_str(), args->actionButton, args->flags, + args->metaState, args->buttonState, args->edgeFlags, args->xPrecision, + args->yPrecision, args->xCursorPosition, args->yCursorPosition, args->downTime); for (uint32_t i = 0; i < args->pointerCount; i++) { - ALOGD(" Pointer %d: id=%d, toolType=%d, " - "x=%f, y=%f, pressure=%f, size=%f, " - "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, " - "orientation=%f", - i, args->pointerProperties[i].id, args->pointerProperties[i].toolType, + ALOGD(" Pointer %d: id=%d, toolType=%s, x=%f, y=%f, pressure=%f, size=%f, " + "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, orientation=%f", + i, args->pointerProperties[i].id, + motionToolTypeToString(args->pointerProperties[i].toolType), args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X), args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y), args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), @@ -4174,6 +4197,17 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) { 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; @@ -4229,7 +4263,7 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) { } void InputDispatcher::notifySensor(const NotifySensorArgs* args) { - if (DEBUG_INBOUND_EVENT_DETAILS) { + if (debugInboundEventDetails()) { ALOGD("notifySensor - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=0x%x, " " sensorType=%s", args->id, args->eventTime, args->deviceId, args->source, @@ -4257,7 +4291,7 @@ void InputDispatcher::notifySensor(const NotifySensorArgs* args) { } void InputDispatcher::notifyVibratorState(const NotifyVibratorStateArgs* args) { - if (DEBUG_INBOUND_EVENT_DETAILS) { + if (debugInboundEventDetails()) { ALOGD("notifyVibratorState - eventTime=%" PRId64 ", device=%d, isOn=%d", args->eventTime, args->deviceId, args->isOn); } @@ -4269,7 +4303,7 @@ bool InputDispatcher::shouldSendMotionToInputFilterLocked(const NotifyMotionArgs } void InputDispatcher::notifySwitch(const NotifySwitchArgs* args) { - if (DEBUG_INBOUND_EVENT_DETAILS) { + if (debugInboundEventDetails()) { ALOGD("notifySwitch - eventTime=%" PRId64 ", policyFlags=0x%x, switchValues=0x%08x, " "switchMask=0x%08x", args->eventTime, args->policyFlags, args->switchValues, args->switchMask); @@ -4281,7 +4315,7 @@ void InputDispatcher::notifySwitch(const NotifySwitchArgs* args) { } void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args) { - if (DEBUG_INBOUND_EVENT_DETAILS) { + if (debugInboundEventDetails()) { ALOGD("notifyDeviceReset - eventTime=%" PRId64 ", deviceId=%d", args->eventTime, args->deviceId); } @@ -4301,7 +4335,7 @@ void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args) { } void InputDispatcher::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) { - if (DEBUG_INBOUND_EVENT_DETAILS) { + if (debugInboundEventDetails()) { ALOGD("notifyPointerCaptureChanged - eventTime=%" PRId64 ", enabled=%s", args->eventTime, args->request.enable ? "true" : "false"); } @@ -4324,7 +4358,7 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev InputEventInjectionSync syncMode, std::chrono::milliseconds timeout, uint32_t policyFlags) { - if (DEBUG_INBOUND_EVENT_DETAILS) { + if (debugInboundEventDetails()) { ALOGD("injectInputEvent - eventType=%d, targetUid=%s, syncMode=%d, timeout=%lld, " "policyFlags=0x%08x", event->getType(), targetUid ? std::to_string(*targetUid).c_str() : "none", syncMode, @@ -5243,7 +5277,7 @@ bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp< // 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); @@ -5264,7 +5298,7 @@ bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp< 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); } @@ -5764,7 +5798,7 @@ status_t InputDispatcher::pilferPointersLocked(const sp<IBinder>& token) { } 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; @@ -5794,10 +5828,7 @@ status_t InputDispatcher::pilferPointersLocked(const sp<IBinder>& token) { // Prevent the gesture from being sent to any other windows. // This only blocks relevant pointers to be sent to other windows - for (BitSet32 idBits(window.pointerIds); !idBits.isEmpty();) { - uint32_t id = idBits.clearFirstMarkedBit(); - window.pilferedPointerIds.set(id); - } + window.pilferedPointerIds |= window.pointerIds; state.cancelPointersForWindowsExcept(window.pointerIds, token); return OK; @@ -6566,8 +6597,8 @@ void InputDispatcher::slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFl 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) && @@ -6603,7 +6634,8 @@ void InputDispatcher::transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldT 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 81f8de8f95..b94858b6d0 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 @@ private: 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 @@ private: // 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 @@ private: 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 de8bfd5ffe..94f38131b6 100644 --- a/services/inputflinger/dispatcher/InputState.cpp +++ b/services/inputflinger/dispatcher/InputState.cpp @@ -28,10 +28,6 @@ InputState::InputState(const IdGenerator& idGenerator) : mIdGenerator(idGenerato InputState::~InputState() {} -bool InputState::isNeutral() const { - return mKeyMementos.empty() && mMotionMementos.empty(); -} - bool InputState::isHovering(int32_t deviceId, uint32_t source, int32_t displayId) const { for (const MotionMemento& memento : mMotionMementos) { if (memento.deviceId == deviceId && memento.source == source && @@ -251,10 +247,19 @@ void InputState::addMotionMemento(const MotionEntry& entry, int32_t flags, bool } void InputState::MotionMemento::setPointers(const MotionEntry& entry) { - pointerCount = entry.pointerCount; + pointerCount = 0; for (uint32_t i = 0; i < entry.pointerCount; i++) { - pointerProperties[i].copyFrom(entry.pointerProperties[i]); - pointerCoords[i].copyFrom(entry.pointerCoords[i]); + if (MotionEvent::getActionMasked(entry.action) == AMOTION_EVENT_ACTION_POINTER_UP) { + // In POINTER_UP events, the pointer is leaving. Since the action is not stored, + // this departing pointer should not be recorded. + const uint8_t actionIndex = MotionEvent::getActionIndex(entry.action); + if (i == actionIndex) { + continue; + } + } + pointerProperties[pointerCount].copyFrom(entry.pointerProperties[i]); + pointerCoords[pointerCount].copyFrom(entry.pointerCoords[i]); + pointerCount++; } } @@ -374,7 +379,8 @@ std::vector<std::unique_ptr<EventEntry>> InputState::synthesizePointerDownEvents } 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); @@ -383,7 +389,7 @@ std::vector<std::unique_ptr<MotionEntry>> InputState::synthesizeCancelationEvent 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); } } diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h index 6ab48c9273..d788e47429 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 { @@ -33,9 +34,6 @@ public: explicit InputState(const IdGenerator& idGenerator); ~InputState(); - // Returns true if there is no state to be canceled. - bool isNeutral() const; - // Returns true if the specified source is known to have received a hover enter // motion event. bool isHovering(int32_t deviceId, uint32_t source, int32_t displayId) const; @@ -128,7 +126,8 @@ private: // 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 2f39480555..fc8b785aa5 100644 --- a/services/inputflinger/dispatcher/InputTarget.cpp +++ b/services/inputflinger/dispatcher/InputTarget.cpp @@ -24,31 +24,34 @@ using android::base::StringPrintf; 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 @@ std::string InputTarget::getPointerInfoString() const { 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 61b07feb3a..7b12f81c4e 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 @@ struct InputTarget { // 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 @@ struct InputTarget { // 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/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index c257ee540d..9c443f14cf 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -33,17 +33,17 @@ void TouchState::reset() { void TouchState::removeTouchedPointer(int32_t pointerId) { for (TouchedWindow& touchedWindow : windows) { - touchedWindow.pointerIds.clearBit(pointerId); - touchedWindow.pilferedPointerIds.reset(pointerId); + touchedWindow.removeTouchingPointer(pointerId); } + clearWindowsWithoutPointers(); } void TouchState::removeTouchedPointerFromWindow( int32_t pointerId, const sp<android::gui::WindowInfoHandle>& windowHandle) { for (TouchedWindow& touchedWindow : windows) { if (touchedWindow.windowHandle == windowHandle) { - touchedWindow.pointerIds.clearBit(pointerId); - touchedWindow.pilferedPointerIds.reset(pointerId); + touchedWindow.removeTouchingPointer(pointerId); + clearWindowsWithoutPointers(); return; } } @@ -53,16 +53,18 @@ void TouchState::clearHoveringPointers() { for (TouchedWindow& touchedWindow : windows) { touchedWindow.clearHoveringPointers(); } + clearWindowsWithoutPointers(); } 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, + 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 @@ -75,7 +77,7 @@ void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle, // 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 = firstDownTimeInTarget; } @@ -128,15 +130,15 @@ void TouchState::filterNonAsIsTouchWindows() { } } -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(); }); + clearWindowsWithoutPointers(); } /** @@ -147,7 +149,7 @@ void TouchState::cancelPointersForWindowsExcept(const BitSet32 pointerIds, */ void TouchState::cancelPointersForNonPilferingWindows() { // First, find all pointers that are being pilfered, across all windows - std::bitset<MAX_POINTERS> allPilferedPointerIds; + std::bitset<MAX_POINTER_ID + 1> allPilferedPointerIds; std::for_each(windows.begin(), windows.end(), [&allPilferedPointerIds](const TouchedWindow& w) { allPilferedPointerIds |= w.pilferedPointerIds; }); @@ -161,21 +163,11 @@ void TouchState::cancelPointersForNonPilferingWindows() { // 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_POINTERS> pilferedByOtherWindows = + std::bitset<MAX_POINTER_ID + 1> pilferedByOtherWindows = w.pilferedPointerIds ^ allPilferedPointerIds; - // TODO(b/211379801) : convert pointerIds to use std::bitset, which would allow us to - // replace the loop below with a bitwise operation. Currently, the XOR operation above is - // redundant, but is done to make the code more explicit / easier to convert later. - for (std::size_t i = 0; i < pilferedByOtherWindows.size(); i++) { - if (pilferedByOtherWindows.test(i) && !w.pilferedPointerIds.test(i)) { - // Pointer is pilfered by other windows, but not by this one! Remove it from here. - // We could call 'removeTouchedPointerFromWindow' here, but it's faster to directly - // manipulate it. - w.pointerIds.clearBit(i); - } - } + w.pointerIds &= ~pilferedByOtherWindows; }); - std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.isEmpty(); }); + clearWindowsWithoutPointers(); } sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle() const { @@ -224,7 +216,12 @@ const TouchedWindow& TouchState::getTouchedWindow(const sp<WindowInfoHandle>& wi 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(); }); +} + +bool TouchState::hasHoveringPointers() const { + return std::any_of(windows.begin(), windows.end(), + [](const TouchedWindow& window) { return window.hasHoveringPointers(); }); } std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(int32_t hoveringDeviceId, @@ -242,9 +239,7 @@ void TouchState::removeHoveringPointer(int32_t hoveringDeviceId, int32_t hoverin for (TouchedWindow& window : windows) { window.removeHoveringPointer(hoveringDeviceId, hoveringPointerId); } - std::erase_if(windows, [](const TouchedWindow& w) { - return w.pointerIds.isEmpty() && !w.hasHoveringPointers(); - }); + clearWindowsWithoutPointers(); } std::string TouchState::dump() const { diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index f1409d6d42..a20080f534 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,7 +47,8 @@ struct TouchState { 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, + 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); @@ -56,7 +58,8 @@ struct TouchState { 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(); @@ -68,6 +71,7 @@ struct TouchState { const sp<android::gui::WindowInfoHandle>& windowHandle) const; // Whether any of the windows are currently being touched bool isDown() const; + bool hasHoveringPointers() const; std::set<sp<android::gui::WindowInfoHandle>> getWindowsWithHoveringPointer( int32_t deviceId, int32_t pointerId) const; diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp index 99e1c86f38..99c4769712 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.cpp +++ b/services/inputflinger/dispatcher/TouchedWindow.cpp @@ -50,6 +50,14 @@ void TouchedWindow::addHoveringPointer(int32_t deviceId, int32_t pointerId) { 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()) { @@ -66,9 +74,9 @@ std::string TouchedWindow::dump() const { std::string out; std::string hoveringPointers = dumpMap(mHoveringPointerIdsByDevice, constToString, bitsetToString); - out += StringPrintf("name='%s', pointerIds=0x%0x, targetFlags=%s, firstDownTimeInTarget=%s, " + out += StringPrintf("name='%s', pointerIds=%s, targetFlags=%s, firstDownTimeInTarget=%s, " "mHoveringPointerIdsByDevice=%s, pilferedPointerIds=%s\n", - windowHandle->getName().c_str(), pointerIds.value, + windowHandle->getName().c_str(), bitsetToString(pointerIds).c_str(), targetFlags.string().c_str(), toString(firstDownTimeInTarget).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 4ec33ac3ef..aa2e9dd677 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -30,9 +30,9 @@ namespace inputdispatcher { struct TouchedWindow { sp<gui::WindowInfoHandle> windowHandle; ftl::Flags<InputTarget::Flags> targetFlags; - BitSet32 pointerIds; + std::bitset<MAX_POINTER_ID + 1> pointerIds; // The pointer ids of the pointers that this window is currently pilfering - std::bitset<MAX_POINTERS> pilferedPointerIds; + 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; @@ -43,11 +43,12 @@ struct TouchedWindow { 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/docs/input_coordinates.md b/services/inputflinger/docs/input_coordinates.md new file mode 100644 index 0000000000..7795710936 --- /dev/null +++ b/services/inputflinger/docs/input_coordinates.md @@ -0,0 +1,113 @@ +# Input Coordinate Processing in InputFlinger + +This document aims to illustrate why we need to take care when converting +between the discrete and continuous coordinate spaces, especially when +performing rotations. + +The Linux evdev protocol works over **discrete integral** values. The same is +true for displays, which output discrete pixels. WindowManager also tracks +window bounds in pixels in the rotated logical display. + +However, our `MotionEvent` APIs +report **floating point** axis values in a **continuous space**. This disparity +is important to note when working in InputFlinger, which has to make sure the +discrete raw coordinates are converted to the continuous space correctly in all +scenarios. + +## Disparity between continuous and discrete coordinates during rotation + +Let's consider an example of device that has a 3 x 4 screen. + +### Natural orientation: No rotation + +If the user interacts with the highlighted pixel, the touchscreen would report +the discreet coordinates (0, 2). + +``` + ┌─────┬─────┬─────┐ + │ 0,0 │ 1,0 │ 2,0 │ + ├─────┼─────┼─────┤ + │ 0,1 │ 1,1 │ 2,1 │ + ├─────┼─────┼─────┤ + │█0,2█│ 1,2 │ 2,2 │ + ├─────┼─────┼─────┤ + │ 0,3 │ 1,3 │ 2,3 │ + └─────┴─────┴─────┘ +``` + +When converted to the continuous space, the point (0, 2) corresponds to the +location shown below. + +``` + 0 1 2 3 + 0 ┌─────┬─────┬─────┐ + │ │ │ │ + 1 ├─────┼─────┼─────┤ + │ │ │ │ + 2 █─────┼─────┼─────┤ + │ │ │ │ + 3 ├─────┼─────┼─────┤ + │ │ │ │ + 4 └─────┴─────┴─────┘ +``` + +### Rotated orientation: 90-degree counter-clockwise rotation + +When the device is rotated and the same place on the touchscreen is touched, the +input device will still report the same coordinates of (0, 2). + +In the rotated display, that now corresponds to the pixel (2, 2). + +``` + ┌─────┬─────┬─────┬─────┐ + │ 0,0 │ 1,0 │ 2,0 │ 3,0 │ + ├─────┼─────┼─────┼─────┤ + │ 0,1 │ 1,1 │ 2,1 │ 3,1 │ + ├─────┼─────┼─────┼─────┤ + │ 0,2 │ 1,2 │█2,2█│ 3,2 │ + └─────┴─────┴─────┴─────┘ +``` + +*It is important to note that rotating the device 90 degrees is NOT equivalent +to rotating the continuous coordinate space by 90 degrees.* + +The point (2, 2) now corresponds to a different location in the continuous space +than before, even though the user was interacting at the same place on the +touchscreen. + +``` + 0 1 2 3 4 + 0 ┌─────┬─────┬─────┬─────┐ + │ │ │ │ │ + 1 ├─────┼─────┼─────┼─────┤ + │ │ │ │ │ + 2 ├─────┼─────█─────┼─────┤ + │ │ │ │ │ + 3 └─────┴─────┴─────┴─────┘ +``` + +If we were to simply (incorrectly) rotate the continuous space from before by +90 degrees, the touched point would correspond to the location (2, 3), shown +below. This new point is outside the bounds of the display, since it does not +correspond to any pixel at that location. + +It should be impossible for a touchscreen to generate points outside the bounds +of the display, because we assume that the area of the touchscreen maps directly +to the area of the display. Therefore, that point is an invalid coordinate that +cannot be generated by an input device. + +``` + 0 1 2 3 4 + 0 ┌─────┬─────┬─────┬─────┐ + │ │ │ │ ╏ + 1 ├─────┼─────┼─────┼─────┤ + │ │ │ │ ╏ + 2 ├─────┼─────┼─────┼─────┤ + │ │ │ │ ╏ + 3 └-----┴-----█-----┴-----┘ +``` + +The same logic applies to windows as well. When performing hit tests to +determine if a point in the continuous space falls inside a window's bounds, +hit test must be performed in the correct orientation, since points on the right +and bottom edges of the window do not fall within the window bounds. diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index 2173117fed..841c914d60 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -333,6 +333,9 @@ struct InputReaderConfiguration { // 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, @@ -358,7 +361,8 @@ struct InputReaderConfiguration { touchpadNaturalScrollingEnabled(true), touchpadTapToClickEnabled(true), touchpadRightClickZoneEnabled(false), - stylusButtonMotionEventsEnabled(true) {} + 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 7e0c1c77eb..9dbdd5ad0f 100644 --- a/services/inputflinger/include/PointerControllerInterface.h +++ b/services/inputflinger/include/PointerControllerInterface.h @@ -79,8 +79,10 @@ public: 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/Android.bp b/services/inputflinger/reader/Android.bp index d29692cf05..132c3a1004 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -39,6 +39,7 @@ filegroup { "EventHub.cpp", "InputDevice.cpp", "InputReader.cpp", + "Macros.cpp", "TouchVideoDevice.cpp", "controller/PeripheralController.cpp", "mapper/CursorInputMapper.cpp", diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index c598c0a700..9fe652ce71 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -277,7 +277,11 @@ std::list<NotifyArgs> InputDevice::configure(nsecs_t when, const InputReaderConf mHasMic = mClasses.test(InputDeviceClass::MIC); if (!isIgnored()) { - if (!changes) { // first time only + // Full configuration should happen the first time configure is called + // and when the device type is changed. Changing a device type can + // affect various other parameters so should result in a + // reconfiguration. + if (!changes || (changes & InputReaderConfiguration::CHANGE_DEVICE_TYPE)) { mConfiguration.clear(); for_each_subdevice([this](InputDeviceContext& context) { PropertyMap configuration; @@ -412,22 +416,21 @@ std::list<NotifyArgs> InputDevice::process(const RawEvent* rawEvents, size_t cou // in the order received. std::list<NotifyArgs> out; for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) { - if (DEBUG_RAW_EVENTS) { - ALOGD("Input event: device=%d type=0x%04x code=0x%04x value=0x%08x when=%" PRId64, - rawEvent->deviceId, rawEvent->type, rawEvent->code, rawEvent->value, - rawEvent->when); + if (debugRawEvents()) { + const auto [type, code, value] = + InputEventLookup::getLinuxEvdevLabel(rawEvent->type, rawEvent->code, + rawEvent->value); + ALOGD("Input event: eventHubDevice=%d type=%s code=%s value=%s when=%" PRId64, + rawEvent->deviceId, type.c_str(), code.c_str(), value.c_str(), rawEvent->when); } if (mDropUntilNextSync) { if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { mDropUntilNextSync = false; - if (DEBUG_RAW_EVENTS) { - ALOGD("Recovered from input event buffer overrun."); - } + ALOGD_IF(debugRawEvents(), "Recovered from input event buffer overrun."); } else { - if (DEBUG_RAW_EVENTS) { - ALOGD("Dropped input event while waiting for next input sync."); - } + ALOGD_IF(debugRawEvents(), + "Dropped input event while waiting for next input sync."); } } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) { ALOGI("Detected input event buffer overrun for device %s.", getName().c_str()); diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 57f679c022..9080cc1d26 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -146,7 +146,7 @@ void InputReader::loopOnce() { if (mNextTimeout != LLONG_MAX) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); if (now >= mNextTimeout) { - if (DEBUG_RAW_EVENTS) { + if (debugRawEvents()) { ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f); } mNextTimeout = LLONG_MAX; @@ -199,7 +199,7 @@ std::list<NotifyArgs> InputReader::processEventsLocked(const RawEvent* rawEvents } batchSize += 1; } - if (DEBUG_RAW_EVENTS) { + if (debugRawEvents()) { ALOGD("BatchSize: %zu Count: %zu", batchSize, count); } out += processEventsForDeviceLocked(deviceId, rawEvent, batchSize); diff --git a/services/inputflinger/reader/Macros.cpp b/services/inputflinger/reader/Macros.cpp new file mode 100644 index 0000000000..8841d0f14a --- /dev/null +++ b/services/inputflinger/reader/Macros.cpp @@ -0,0 +1,43 @@ +/* + * 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 "Macros.h" + +#include <android-base/properties.h> + +namespace { + +const bool IS_DEBUGGABLE_BUILD = +#if defined(__ANDROID__) + android::base::GetBoolProperty("ro.debuggable", false); +#else + true; +#endif + +} // namespace + +namespace android { + +bool debugRawEvents() { + if (!IS_DEBUGGABLE_BUILD) { + static const bool DEBUG_RAW_EVENTS = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "RawEvents", ANDROID_LOG_INFO); + return DEBUG_RAW_EVENTS; + } + return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "RawEvents", ANDROID_LOG_INFO); +} + +} // namespace android diff --git a/services/inputflinger/reader/Macros.h b/services/inputflinger/reader/Macros.h index d2a7ced864..2bce215b3c 100644 --- a/services/inputflinger/reader/Macros.h +++ b/services/inputflinger/reader/Macros.h @@ -25,12 +25,14 @@ #include <unordered_map> namespace android { + /** * Log debug messages for each raw event received from the EventHub. - * Enable this via "adb shell setprop log.tag.InputReaderRawEvents DEBUG" (requires restart) + * Enable this via "adb shell setprop log.tag.InputReaderRawEvents DEBUG". + * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately + * on debuggable builds (e.g. userdebug). */ -const bool DEBUG_RAW_EVENTS = - __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "RawEvents", ANDROID_LOG_INFO); +bool debugRawEvents(); /** * Log debug messages about virtual key processing. @@ -52,6 +54,7 @@ const bool DEBUG_POINTERS = */ const bool DEBUG_POINTER_ASSIGNMENT = __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "PointerAssignment", ANDROID_LOG_INFO); + /** * Log debug messages about gesture detection. * Enable this via "adb shell setprop log.tag.InputReaderGestures DEBUG" (requires restart) @@ -79,6 +82,7 @@ const bool DEBUG_STYLUS_FUSION = */ const bool DEBUG_LIGHT_DETAILS = __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "LightDetails", ANDROID_LOG_INFO); + } // namespace android #define INDENT " " diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index d415854e15..b53fc7399a 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -293,7 +293,10 @@ std::list<NotifyArgs> TouchInputMapper::configure(nsecs_t when, mConfig = *config; - if (!changes) { // first time only + // Full configuration should happen the first time configure is called and + // when the device type is changed. Changing a device type can affect + // various other parameters so should result in a reconfiguration. + if (!changes || (changes & InputReaderConfiguration::CHANGE_DEVICE_TYPE)) { // Configure basic parameters. configureParameters(); @@ -328,7 +331,8 @@ std::list<NotifyArgs> TouchInputMapper::configure(nsecs_t when, InputReaderConfiguration::CHANGE_POINTER_CAPTURE | InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT | InputReaderConfiguration::CHANGE_SHOW_TOUCHES | - InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE))) { + InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE | + InputReaderConfiguration::CHANGE_DEVICE_TYPE))) { // Configure device sources, display dimensions, orientation and // scaling factors. configureInputDevice(when, &resetNeeded); @@ -838,38 +842,60 @@ void TouchInputMapper::initializeOrientedRanges() { } void TouchInputMapper::computeInputTransforms() { - const ui::Size rawSize{mRawPointerAxes.getRawWidth(), mRawPointerAxes.getRawHeight()}; + constexpr auto isRotated = [](const ui::Transform::RotationFlags& rotation) { + return rotation == ui::Transform::ROT_90 || rotation == ui::Transform::ROT_270; + }; - ui::Size rotatedRawSize = rawSize; - if (mInputDeviceOrientation == ui::ROTATION_270 || mInputDeviceOrientation == ui::ROTATION_90) { - std::swap(rotatedRawSize.width, rotatedRawSize.height); - } - const auto rotationFlags = ui::Transform::toRotationFlags(-mInputDeviceOrientation); - mRawRotation = ui::Transform{rotationFlags}; + // See notes about input coordinates in the inputflinger docs: + // //frameworks/native/services/inputflinger/docs/input_coordinates.md // Step 1: Undo the raw offset so that the raw coordinate space now starts at (0, 0). - ui::Transform undoRawOffset; - undoRawOffset.set(-mRawPointerAxes.x.minValue, -mRawPointerAxes.y.minValue); - - // Step 2: Rotate the raw coordinates to the expected orientation. - ui::Transform rotate; - // When rotating raw coordinates, the raw size will be used as an offset. - // Account for the extra unit added to the raw range when the raw size was calculated. - rotate.set(rotationFlags, rotatedRawSize.width - 1, rotatedRawSize.height - 1); - - // Step 3: Scale the raw coordinates to the display space. - ui::Transform scaleToDisplay; - const float xScale = static_cast<float>(mDisplayBounds.width) / rotatedRawSize.width; - const float yScale = static_cast<float>(mDisplayBounds.height) / rotatedRawSize.height; - scaleToDisplay.set(xScale, 0, 0, yScale); - - mRawToDisplay = (scaleToDisplay * (rotate * undoRawOffset)); - - // Calculate the transform that takes raw coordinates to the rotated display space. - ui::Transform displayToRotatedDisplay; - displayToRotatedDisplay.set(ui::Transform::toRotationFlags(-mViewport.orientation), - mViewport.deviceWidth, mViewport.deviceHeight); - mRawToRotatedDisplay = displayToRotatedDisplay * mRawToDisplay; + ui::Transform undoOffsetInRaw; + undoOffsetInRaw.set(-mRawPointerAxes.x.minValue, -mRawPointerAxes.y.minValue); + + // Step 2: Rotate the raw coordinates to account for input device orientation. The coordinates + // will now be in the same orientation as the display in ROTATION_0. + // Note: Negating an ui::Rotation value will give its inverse rotation. + const auto inputDeviceOrientation = ui::Transform::toRotationFlags(-mParameters.orientation); + const ui::Size orientedRawSize = isRotated(inputDeviceOrientation) + ? ui::Size{mRawPointerAxes.getRawHeight(), mRawPointerAxes.getRawWidth()} + : ui::Size{mRawPointerAxes.getRawWidth(), mRawPointerAxes.getRawHeight()}; + // When rotating raw values, account for the extra unit added when calculating the raw range. + const auto orientInRaw = ui::Transform(inputDeviceOrientation, orientedRawSize.width - 1, + orientedRawSize.height - 1); + + // Step 3: Rotate the raw coordinates to account for the display rotation. The coordinates will + // now be in the same orientation as the rotated display. There is no need to rotate the + // coordinates to the display rotation if the device is not orientation-aware. + const auto viewportRotation = ui::Transform::toRotationFlags(-mViewport.orientation); + const auto rotatedRawSize = mParameters.orientationAware && isRotated(viewportRotation) + ? ui::Size{orientedRawSize.height, orientedRawSize.width} + : orientedRawSize; + // When rotating raw values, account for the extra unit added when calculating the raw range. + const auto rotateInRaw = mParameters.orientationAware + ? ui::Transform(viewportRotation, rotatedRawSize.width - 1, rotatedRawSize.height - 1) + : ui::Transform(); + + // Step 4: Scale the raw coordinates to the display space. + // - Here, we assume that the raw surface of the touch device maps perfectly to the surface + // of the display panel. This is usually true for touchscreens. + // - From this point onward, we are no longer in the discrete space of the raw coordinates but + // are in the continuous space of the logical display. + ui::Transform scaleRawToDisplay; + const float xScale = static_cast<float>(mViewport.deviceWidth) / rotatedRawSize.width; + const float yScale = static_cast<float>(mViewport.deviceHeight) / rotatedRawSize.height; + scaleRawToDisplay.set(xScale, 0, 0, yScale); + + // Step 5: Undo the display rotation to bring us back to the un-rotated display coordinate space + // that InputReader uses. + const auto undoRotateInDisplay = + ui::Transform(viewportRotation, mViewport.deviceWidth, mViewport.deviceHeight) + .inverse(); + + // Now put it all together! + mRawToRotatedDisplay = (scaleRawToDisplay * (rotateInRaw * (orientInRaw * undoOffsetInRaw))); + mRawToDisplay = (undoRotateInDisplay * mRawToRotatedDisplay); + mRawRotation = ui::Transform{mRawToDisplay.getOrientation()}; } void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) { @@ -945,6 +971,9 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) mPhysicalFrameInRotatedDisplay = {mViewport.physicalLeft, mViewport.physicalTop, mViewport.physicalRight, mViewport.physicalBottom}; + // TODO(b/257118693): Remove the dependence on the old orientation/rotation logic that + // uses mInputDeviceOrientation. The new logic uses the transforms calculated in + // computeInputTransforms(). // InputReader works in the un-rotated display coordinate space, so we don't need to do // anything if the device is already orientation-aware. If the device is not // orientation-aware, then we need to apply the inverse rotation of the display so that @@ -977,12 +1006,18 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) 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()); } @@ -1440,7 +1475,7 @@ std::list<NotifyArgs> TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { assignPointerIds(last, next); } - ALOGD_IF(DEBUG_RAW_EVENTS, + ALOGD_IF(debugRawEvents(), "syncTouch: pointerCount %d -> %d, touching ids 0x%08x -> 0x%08x, " "hovering ids 0x%08x -> 0x%08x, canceled ids 0x%08x", last.rawPointerData.pointerCount, next.rawPointerData.pointerCount, @@ -1644,7 +1679,8 @@ void TouchInputMapper::updateTouchSpots() { mPointerController->setButtonState(mCurrentRawState.buttonState); mPointerController->setSpots(mCurrentCookedState.cookedPointerData.pointerCoords.cbegin(), mCurrentCookedState.cookedPointerData.idToIndex.cbegin(), - mCurrentCookedState.cookedPointerData.touchingIdBits, + mCurrentCookedState.cookedPointerData.touchingIdBits | + mCurrentCookedState.cookedPointerData.hoveringIdBits, mViewport.displayId); } @@ -2150,6 +2186,53 @@ std::list<NotifyArgs> TouchInputMapper::dispatchButtonPress(nsecs_t when, nsecs_ 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; @@ -2534,8 +2617,13 @@ std::list<NotifyArgs> TouchInputMapper::dispatchPointerGestures(nsecs_t when, ns 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 +2668,12 @@ std::list<NotifyArgs> TouchInputMapper::dispatchPointerGestures(nsecs_t when, ns 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 +3744,14 @@ std::list<NotifyArgs> TouchInputMapper::abortPointerSimple(nsecs_t when, nsecs_t 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 +3793,35 @@ NotifyMotionArgs TouchInputMapper::dispatchMotion( 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(), diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 87deb39e20..7b464efccb 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -735,6 +735,14 @@ private: 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 9f32311e14..d3af402153 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -16,6 +16,7 @@ #include "../Macros.h" +#include <limits> #include <optional> #include <android/input.h> @@ -30,6 +31,85 @@ namespace android { namespace { +/** + * Log details of each gesture output by the gestures library. + * Enable this via "adb shell setprop log.tag.TouchpadInputMapperGestures DEBUG" (requires + * restarting the shell) + */ +const bool DEBUG_TOUCHPAD_GESTURES = + __android_log_is_loggable(ANDROID_LOG_DEBUG, "TouchpadInputMapperGestures", + ANDROID_LOG_INFO); + +// Describes a segment of the acceleration curve. +struct CurveSegment { + // The maximum pointer speed which this segment should apply. The last segment in a curve should + // always set this to infinity. + double maxPointerSpeedMmPerS; + double slope; + double intercept; +}; + +const std::vector<CurveSegment> segments = { + {10.922, 3.19, 0}, + {31.750, 4.79, -17.526}, + {98.044, 7.28, -96.52}, + {std::numeric_limits<double>::infinity(), 15.04, -857.758}, +}; + +const std::vector<double> sensitivityFactors = {1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 18, 20}; + +std::vector<double> createAccelerationCurveForSensitivity(int32_t sensitivity, + size_t propertySize) { + LOG_ALWAYS_FATAL_IF(propertySize < 4 * segments.size()); + std::vector<double> output(propertySize, 0); + + // The Gestures library uses functions of the following form to define curve segments, where a, + // b, and c can be specified by us: + // output_speed(input_speed_mm) = a * input_speed_mm ^ 2 + b * input_speed_mm + c + // + // (a, b, and c are also called sqr_, mul_, and int_ in the Gestures library code.) + // + // We are trying to implement the following function, where slope and intercept are the + // parameters specified in the `segments` array above: + // gain(input_speed_mm) = + // 0.64 * (sensitivityFactor / 10) * (slope + intercept / input_speed_mm) + // Where "gain" is a multiplier applied to the input speed to produce the output speed: + // output_speed(input_speed_mm) = input_speed_mm * gain(input_speed_mm) + // + // To put our function in the library's form, we substitute it into the function above: + // output_speed(input_speed_mm) = + // input_speed_mm * (0.64 * (sensitivityFactor / 10) * + // (slope + 25.4 * intercept / input_speed_mm)) + // then expand the brackets so that input_speed_mm cancels out for the intercept term: + // gain(input_speed_mm) = + // 0.64 * (sensitivityFactor / 10) * slope * input_speed_mm + + // 0.64 * (sensitivityFactor / 10) * intercept + // + // This gives us the following parameters for the Gestures library function form: + // a = 0 + // b = 0.64 * (sensitivityFactor / 10) * slope + // c = 0.64 * (sensitivityFactor / 10) * intercept + + double commonFactor = 0.64 * sensitivityFactors[sensitivity + 7] / 10; + + size_t i = 0; + for (CurveSegment seg : segments) { + // The library's curve format consists of four doubles per segment: + // * maximum pointer speed for the segment (mm/s) + // * multiplier for the x² term (a.k.a. "a" or "sqr") + // * multiplier for the x term (a.k.a. "b" or "mul") + // * the intercept (a.k.a. "c" or "int") + // (see struct CurveSegment in the library's AccelFilterInterpreter) + output[i + 0] = seg.maxPointerSpeedMmPerS; + output[i + 1] = 0; + output[i + 2] = commonFactor * seg.slope; + output[i + 3] = commonFactor * seg.intercept; + i += 4; + } + + return output; +} + short getMaxTouchCount(const InputDeviceContext& context) { if (context.hasScanCode(BTN_TOOL_QUINTTAP)) return 5; if (context.hasScanCode(BTN_TOOL_QUADTAP)) return 4; @@ -147,10 +227,12 @@ std::list<NotifyArgs> TouchpadInputMapper::configure(nsecs_t when, 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("Use Custom Touchpad Pointer Accel Curve") + .setBoolValues({true}); + GesturesProp accelCurveProp = mPropertyProvider.getProperty("Pointer Accel Curve"); + accelCurveProp.setRealValues( + createAccelerationCurveForSensitivity(config->touchpadPointerSpeed, + accelCurveProp.getCount())); mPropertyProvider.getProperty("Invert Scrolling") .setBoolValues({config->touchpadNaturalScrollingEnabled}); mPropertyProvider.getProperty("Tap Enable") @@ -178,6 +260,7 @@ std::list<NotifyArgs> TouchpadInputMapper::process(const RawEvent* rawEvent) { std::list<NotifyArgs> TouchpadInputMapper::sendHardwareState(nsecs_t when, nsecs_t readTime, SelfContainedHardwareState schs) { + ALOGD_IF(DEBUG_TOUCHPAD_GESTURES, "New hardware state: %s", schs.state.String().c_str()); mProcessing = true; mGestureInterpreter->PushHardwareState(&schs.state); mProcessing = false; @@ -186,7 +269,7 @@ std::list<NotifyArgs> TouchpadInputMapper::sendHardwareState(nsecs_t when, nsecs } void TouchpadInputMapper::consumeGesture(const Gesture* gesture) { - ALOGD("Gesture ready: %s", gesture->String().c_str()); + ALOGD_IF(DEBUG_TOUCHPAD_GESTURES, "Gesture ready: %s", gesture->String().c_str()); if (!mProcessing) { ALOGE("Received gesture outside of the normal processing flow; ignoring it."); return; diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp index 2e175b8fae..c091a51386 100644 --- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp @@ -80,18 +80,32 @@ SelfContainedHardwareState HardwareStateConverter::produceHardwareState(nsecs_t schs.fingers.clear(); for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) { MultiTouchMotionAccumulator::Slot slot = mMotionAccumulator.getSlot(i); - if (slot.isInUse()) { - FingerState& fingerState = schs.fingers.emplace_back(); - fingerState = {}; - fingerState.touch_major = slot.getTouchMajor(); - fingerState.touch_minor = slot.getTouchMinor(); - fingerState.width_major = slot.getToolMajor(); - fingerState.width_minor = slot.getToolMinor(); - fingerState.pressure = slot.getPressure(); - fingerState.orientation = slot.getOrientation(); - fingerState.position_x = slot.getX(); - fingerState.position_y = slot.getY(); - fingerState.tracking_id = slot.getTrackingId(); + if (!slot.isInUse()) { + continue; + } + // Some touchpads continue to report contacts even after they've identified them as palms. + // We want to exclude these contacts from the HardwareStates, but still need to report a + // tracking ID of -1 if a finger turns into a palm. + const bool isPalm = slot.getToolType() == AMOTION_EVENT_TOOL_TYPE_PALM; + if (isPalm && mFingerSlots.find(i) == mFingerSlots.end()) { + continue; + } + + FingerState& fingerState = schs.fingers.emplace_back(); + fingerState = {}; + fingerState.touch_major = slot.getTouchMajor(); + fingerState.touch_minor = slot.getTouchMinor(); + fingerState.width_major = slot.getToolMajor(); + fingerState.width_minor = slot.getToolMinor(); + fingerState.pressure = slot.getPressure(); + fingerState.orientation = slot.getOrientation(); + fingerState.position_x = slot.getX(); + fingerState.position_y = slot.getY(); + fingerState.tracking_id = isPalm ? -1 : slot.getTrackingId(); + if (fingerState.tracking_id == -1) { + mFingerSlots.erase(i); + } else { + mFingerSlots.insert(i); } } schs.state.fingers = schs.fingers.data(); @@ -103,6 +117,7 @@ SelfContainedHardwareState HardwareStateConverter::produceHardwareState(nsecs_t void HardwareStateConverter::reset() { mCursorButtonAccumulator.reset(mDeviceContext); mTouchButtonAccumulator.reset(); + mFingerSlots.clear(); mMscTimestamp = 0; } diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h index 88312993a0..d6787b7e3f 100644 --- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h +++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h @@ -17,6 +17,7 @@ #pragma once #include <optional> +#include <set> #include <utils/Timers.h> @@ -53,6 +54,7 @@ private: MultiTouchMotionAccumulator mMotionAccumulator; TouchButtonAccumulator mTouchButtonAccumulator; int32_t mMscTimestamp = 0; + std::set<size_t> mFingerSlots; }; } // namespace android diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp index cd18cd3e35..089f45a4e6 100644 --- a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp +++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp @@ -68,11 +68,11 @@ const GesturesPropProvider gesturePropProvider = { .free_fn = freeProperty, }; -bool PropertyProvider::hasProperty(const std::string name) const { +bool PropertyProvider::hasProperty(const std::string& name) const { return mProperties.find(name) != mProperties.end(); } -GesturesProp& PropertyProvider::getProperty(const std::string name) { +GesturesProp& PropertyProvider::getProperty(const std::string& name) { return mProperties.at(name); } @@ -84,7 +84,7 @@ std::string PropertyProvider::dump() const { return dump; } -GesturesProp* PropertyProvider::createIntArrayProperty(const std::string name, int* loc, +GesturesProp* PropertyProvider::createIntArrayProperty(const std::string& name, int* loc, size_t count, const int* init) { const auto [it, inserted] = mProperties.insert(std::pair{name, GesturesProp(name, loc, count, init)}); @@ -92,7 +92,7 @@ GesturesProp* PropertyProvider::createIntArrayProperty(const std::string name, i return &it->second; } -GesturesProp* PropertyProvider::createBoolArrayProperty(const std::string name, +GesturesProp* PropertyProvider::createBoolArrayProperty(const std::string& name, GesturesPropBool* loc, size_t count, const GesturesPropBool* init) { const auto [it, inserted] = @@ -101,7 +101,7 @@ GesturesProp* PropertyProvider::createBoolArrayProperty(const std::string name, return &it->second; } -GesturesProp* PropertyProvider::createRealArrayProperty(const std::string name, double* loc, +GesturesProp* PropertyProvider::createRealArrayProperty(const std::string& name, double* loc, size_t count, const double* init) { const auto [it, inserted] = mProperties.insert(std::pair{name, GesturesProp(name, loc, count, init)}); @@ -109,7 +109,7 @@ GesturesProp* PropertyProvider::createRealArrayProperty(const std::string name, return &it->second; } -GesturesProp* PropertyProvider::createStringProperty(const std::string name, const char** loc, +GesturesProp* PropertyProvider::createStringProperty(const std::string& name, const char** loc, const char* const init) { const auto [it, inserted] = mProperties.insert(std::pair{name, GesturesProp(name, loc, init)}); LOG_ALWAYS_FATAL_IF(!inserted, "Gesture property \"%s\" already exists.", name.c_str()); diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.h b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h index c21260f6c2..50451a3929 100644 --- a/services/inputflinger/reader/mapper/gestures/PropertyProvider.h +++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h @@ -31,18 +31,18 @@ extern const GesturesPropProvider gesturePropProvider; // Implementation of a gestures library property provider, which provides configuration parameters. class PropertyProvider { public: - bool hasProperty(const std::string name) const; - GesturesProp& getProperty(const std::string name); + bool hasProperty(const std::string& name) const; + GesturesProp& getProperty(const std::string& name); std::string dump() const; // Methods to be called by the gestures library: - GesturesProp* createIntArrayProperty(const std::string name, int* loc, size_t count, + GesturesProp* createIntArrayProperty(const std::string& name, int* loc, size_t count, const int* init); - GesturesProp* createBoolArrayProperty(const std::string name, GesturesPropBool* loc, + GesturesProp* createBoolArrayProperty(const std::string& name, GesturesPropBool* loc, size_t count, const GesturesPropBool* init); - GesturesProp* createRealArrayProperty(const std::string name, double* loc, size_t count, + GesturesProp* createRealArrayProperty(const std::string& name, double* loc, size_t count, const double* init); - GesturesProp* createStringProperty(const std::string name, const char** loc, + GesturesProp* createStringProperty(const std::string& name, const char** loc, const char* const init); void freeProperty(GesturesProp* prop); diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index af40fed51d..97138c73c0 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -86,6 +86,13 @@ cc_test { ], }, }, + sanitize: { + undefined: true, + all_undefined: true, + diag: { + undefined: true, + }, + }, static_libs: [ "libc++fs", "libgmock", diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp index bb8a30e5ba..30c1719c35 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp +++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp @@ -205,6 +205,10 @@ void FakeInputReaderPolicy::setStylusButtonMotionEventsEnabled(bool enabled) { 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 9ec3217d3e..28ac505284 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.h +++ b/services/inputflinger/tests/FakeInputReaderPolicy.h @@ -76,6 +76,7 @@ public: 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 ab7879f1aa..28dad95d47 100644 --- a/services/inputflinger/tests/FakePointerController.cpp +++ b/services/inputflinger/tests/FakePointerController.cpp @@ -65,6 +65,10 @@ void FakePointerController::assertPosition(float x, float y) { 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 @@ void FakePointerController::move(float deltaX, float deltaY) { 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 d10cbcd68b..dd56e65c01 100644 --- a/services/inputflinger/tests/FakePointerController.h +++ b/services/inputflinger/tests/FakePointerController.h @@ -39,12 +39,13 @@ public: 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 @@ private: 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/HardwareStateConverter_test.cpp b/services/inputflinger/tests/HardwareStateConverter_test.cpp index 79218816bd..36b9bab14e 100644 --- a/services/inputflinger/tests/HardwareStateConverter_test.cpp +++ b/services/inputflinger/tests/HardwareStateConverter_test.cpp @@ -191,6 +191,68 @@ TEST_F(HardwareStateConverterTest, TwoFingers) { EXPECT_EQ(0u, finger2.flags); } +TEST_F(HardwareStateConverterTest, OnePalm) { + const nsecs_t time = ARBITRARY_TIME; + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + HardwareStateConverter conv(deviceContext); + + processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, time, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); + processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123); + processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100); + + processAxis(conv, time, EV_KEY, BTN_TOUCH, 1); + std::optional<SelfContainedHardwareState> schs = processSync(conv, time); + ASSERT_TRUE(schs.has_value()); + EXPECT_EQ(0, schs->state.finger_cnt); +} + +TEST_F(HardwareStateConverterTest, OneFingerTurningIntoAPalm) { + const nsecs_t time = ARBITRARY_TIME; + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + HardwareStateConverter conv(deviceContext); + + processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, time, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123); + processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100); + + processAxis(conv, time, EV_KEY, BTN_TOUCH, 1); + + std::optional<SelfContainedHardwareState> schs = processSync(conv, time); + ASSERT_TRUE(schs.has_value()); + EXPECT_EQ(1, schs->state.finger_cnt); + + processAxis(conv, time, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); + processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 51); + processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 99); + + schs = processSync(conv, time); + ASSERT_TRUE(schs.has_value()); + ASSERT_EQ(1, schs->state.finger_cnt); + EXPECT_EQ(-1, schs->state.fingers[0].tracking_id); + + processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 53); + processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 97); + + schs = processSync(conv, time); + ASSERT_TRUE(schs.has_value()); + EXPECT_EQ(0, schs->state.finger_cnt); + + processAxis(conv, time, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 55); + processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 95); + schs = processSync(conv, time); + ASSERT_TRUE(schs.has_value()); + ASSERT_EQ(1, schs->state.finger_cnt); + const FingerState& newFinger = schs->state.fingers[0]; + EXPECT_EQ(123, newFinger.tracking_id); + EXPECT_NEAR(55, newFinger.position_x, EPSILON); + EXPECT_NEAR(95, newFinger.position_y, EPSILON); +} + TEST_F(HardwareStateConverterTest, ButtonPressed) { const nsecs_t time = ARBITRARY_TIME; InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index b1b6e058e4..e4ba24153d 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -58,8 +58,23 @@ static constexpr int32_t SECOND_DEVICE_ID = 2; static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; static constexpr int32_t SECOND_DISPLAY_ID = 1; +static constexpr int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN; +static constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE; +static constexpr int32_t ACTION_UP = AMOTION_EVENT_ACTION_UP; +static constexpr int32_t ACTION_HOVER_ENTER = AMOTION_EVENT_ACTION_HOVER_ENTER; +static constexpr int32_t ACTION_HOVER_EXIT = AMOTION_EVENT_ACTION_HOVER_EXIT; static constexpr int32_t ACTION_OUTSIDE = AMOTION_EVENT_ACTION_OUTSIDE; static constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL; +/** + * The POINTER_DOWN(0) is an unusual, but valid, action. It just means that the new pointer in the + * MotionEvent is at the index 0 rather than 1 (or later). That is, the pointer id=0 (which is at + * index 0) is the new pointer going down. The same pointer could have been placed at a different + * index, and the action would become POINTER_1_DOWN, 2, etc..; these would all be valid. In + * general, we try to place pointer id = 0 at the index 0. Of course, this is not possible if + * pointer id=0 leaves but the pointer id=1 remains. + */ +static constexpr int32_t POINTER_0_DOWN = + AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); 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 = @@ -90,6 +105,8 @@ static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 1000ms; static constexpr int expectedWallpaperFlags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; +using ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID; + struct PointF { float x; float y; @@ -145,6 +162,10 @@ MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") { return arg.getDisplayId() == displayId; } +MATCHER_P(WithDeviceId, deviceId, "InputEvent with specified deviceId") { + return arg.getDeviceId() == deviceId; +} + MATCHER_P(WithSource, source, "InputEvent with specified source") { *result_listener << "expected source " << inputEventSourceToString(source) << ", but got " << inputEventSourceToString(arg.getSource()); @@ -163,6 +184,10 @@ MATCHER_P2(WithCoords, x, y, "MotionEvent with specified coordinates") { return arg.getX(/*pointerIndex=*/0) == x && arg.getY(/*pointerIndex=*/0) == y; } +MATCHER_P(WithPointerCount, pointerCount, "MotionEvent with specified number of pointers") { + return arg.getPointerCount() == pointerCount; +} + MATCHER_P(WithPointers, pointers, "MotionEvent with specified pointers") { // Build a map for the received pointers, by pointer id std::map<int32_t /*pointerId*/, PointF> actualPointers; @@ -1567,6 +1592,113 @@ private: 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, @@ -1815,6 +1947,48 @@ TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCance } /** + * Two fingers down on the window, and lift off the first finger. + * Next, cancel the gesture to the window by removing the window. Make sure that the CANCEL event + * contains a single pointer. + */ +TEST_F(InputDispatcherTest, CancelAfterPointer0Up) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + NotifyMotionArgs args; + // First touch pointer down on right window + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .build())); + // Second touch pointer down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(110).y(100)) + .build())); + // First touch pointer lifts. The second one remains down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) + + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(110).y(100)) + .build())); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_0_UP)); + + // Remove the window. The gesture should be canceled + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}}); + const std::map<int32_t, PointF> expectedPointers{{1, PointF{110, 100}}}; + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithPointers(expectedPointers))); +} + +/** * Same test as WhenForegroundWindowDisappears_WallpaperTouchIsCanceled above, * with the following differences: * After ACTION_DOWN, Wallpaper window hangs up its channel, which forces the dispatcher to @@ -1922,8 +2096,17 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {100, 100})) + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN) + .displayId(ADISPLAY_ID_DEFAULT) + .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) + .pointer(PointerBuilder(/* id */ 1, + AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(100) + .y(100)) + .build(), + INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; foregroundWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); @@ -2055,6 +2238,92 @@ TEST_F(InputDispatcherTest, WallpaperWindowWhenSlippery) { } /** + * 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. @@ -2172,6 +2441,557 @@ TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { } /** + * Two windows: a window on the left and a window on the right. + * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains + * down. Then, on the left window, also place second touch pointer down. + * This test tries to reproduce a crash. + * In the buggy implementation, second pointer down on the left window would cause a crash. + */ +TEST_F(InputDispatcherTest, MultiDeviceSplitTouch) { + 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 touchDeviceId = 4; + const int32_t mouseDeviceId = 6; + NotifyMotionArgs args; + + // Start hovering over the left window + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .build())); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + + // Mouse down on left window + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .build())); + + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId))); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .build())); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // First touch pointer down on right window + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .build())); + leftWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL)); + + rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + // Second touch pointer down on left window + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(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( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + // This MOVE event is not necessary (doesn't carry any new information), but it's there in the + // current implementation. + const std::map<int32_t, PointF> expectedPointers{{0, PointF{100, 100}}}; + rightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithPointers(expectedPointers))); + + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + +/** + * On a single window, use two different devices: mouse and touch. + * Touch happens first, with two pointers going down, and then the first pointer leaving. + * Mouse is clicked next, which causes the touch stream to be aborted with ACTION_CANCEL. + * Finally, a second touch pointer goes down again. Ensure the second touch pointer is ignored, + * because the mouse is currently down, and a POINTER_DOWN event from the touchscreen does not + * represent a new gesture. + */ +TEST_F(InputDispatcherTest, MixedTouchAndMouseWithPointerDown) { + 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, 400, 400)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + const int32_t touchDeviceId = 4; + const int32_t mouseDeviceId = 6; + NotifyMotionArgs args; + + // First touch pointer down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .build())); + // Second touch pointer down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(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(350).y(100)) + .build())); + // First touch pointer lifts. The second one remains down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(POINTER_0_UP, 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(350).y(100)) + .build())); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_0_UP)); + + // Mouse down. The touch should be canceled + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(320).y(100)) + .build())); + + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId), + WithPointerCount(1u))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(320).y(100)) + .build())); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // Second touch pointer down. + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(POINTER_0_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(350).y(100)) + .build())); + // The pointer_down event should be ignored + window->assertNoEvents(); +} + +/** + * Inject a touch down and then send a new event via 'notifyMotion'. Ensure the new event cancels + * the injected event. + */ +TEST_F(InputDispatcherTest, UnfinishedInjectedEvent) { + 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, 400, 400)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + const int32_t touchDeviceId = 4; + NotifyMotionArgs args; + // Pretend a test injects an ACTION_DOWN mouse event, but forgets to lift up the touch after + // completion. + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .x(50) + .y(50)) + .build())); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(VIRTUAL_KEYBOARD_ID))); + + // Now a real touch comes. Rather than crashing or dropping the real event, the injected pointer + // should be canceled and the new gesture should take over. + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .build())); + + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(VIRTUAL_KEYBOARD_ID))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); +} + +/** + * 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(); +} + +/** + * A spy window above a window with no input channel. + * Start hovering with a stylus device, and then tap with it. + * Ensure spy window receives the entire sequence. + */ +TEST_F(InputDispatcherTest, StylusHoverAndDownNoInputChannel) { + 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->setNoInputChannel(true); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}}); + + NotifyMotionArgs args; + + // Start hovering with stylus + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + // Stop hovering + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + + // Stylus touches down + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + // Stylus goes up + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_UP)); + + // Again hover + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + // Stop hovering + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + + // No more events + spyWindow->assertNoEvents(); + window->assertNoEvents(); +} + +/** + * Start hovering with a mouse, and then tap with a touch device. Pilfer the touch stream. + * Next, click with the mouse device. Both windows (spy and regular) should receive the new mouse + * ACTION_DOWN event because that's a new gesture, and pilfering should no longer be active. + * While the mouse is down, new move events from the touch device should be ignored. + */ +TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { + 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 mouseDeviceId = 7; + const int32_t touchDeviceId = 4; + NotifyMotionArgs args; + + // Hover a bit with mouse first + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .build())); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + + // Start touching + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(55).y(55)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + window->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + + // Pilfer the stream + EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken())); + window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL)); + + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(60).y(60)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + + // Mouse down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .build())); + + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // Mouse move! + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(110).y(110)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + window->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + + // Touch move! + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(65).y(65)) + .build())); + + // No more events + spyWindow->assertNoEvents(); + 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. @@ -2280,8 +3100,8 @@ TEST_F(InputDispatcherTest, SplitTouchesSendCorrectActionDownTime) { window1->assertNoEvents(); // Now move the pointer on the first window - mDispatcher->notifyMotion( - &(args = generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{51, 51}, {151, 51}}))); + mDispatcher->notifyMotion(&( + args = generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{51, 51}, {151, 51}, {150, 50}}))); mDispatcher->waitForIdle(); window1->consumeMotionEvent(WithDownTime(downTimeForWindow1)); @@ -2336,7 +3156,8 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .x(300) .y(400)) .build())); - windowLeft->consumeMotionDown(ADISPLAY_ID_DEFAULT); + windowLeft->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + windowLeft->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, @@ -2381,7 +3202,6 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .x(900) .y(400)) .build())); - windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); // No more events @@ -2389,6 +3209,70 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { windowRight->assertNoEvents(); } +/** + * Put two fingers down (and don't release them) and click the mouse button. + * The clicking of mouse is a new ACTION_DOWN event. Since it's from a different device, the + * currently active gesture should be canceled, and the new one should proceed. + */ +TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) { + 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, 600, 800)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + const int32_t touchDeviceId = 4; + const int32_t mouseDeviceId = 6; + NotifyMotionArgs args; + + // Two pointers down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .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) + .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())); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + + // Inject a series of mouse events for a mouse click + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(300).y(400)) + .build())); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId), + WithPointerCount(2u))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(300).y(400)) + .build())); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // Try to send more touch events while the mouse is down. Since it's a continuation of an + // already canceled gesture, it should be ignored. + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(101).y(101)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(121).y(121)) + .build())); + window->assertNoEvents(); +} + TEST_F(InputDispatcherTest, HoverWithSpyWindows) { std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); @@ -2571,7 +3455,8 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { .x(300) .y(400)) .build())); - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, @@ -2615,7 +3500,7 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { .x(300) .y(400)) .build())); - window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); + window->assertNoEvents(); } /** @@ -2648,6 +3533,42 @@ TEST_F(InputDispatcherTest, HoverExitIsSentToRemovedWindow) { } /** + * If mouse is hovering when the touch goes down, the hovering should be stopped via HOVER_EXIT. + */ +TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 100, 100)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + const int32_t mouseDeviceId = 7; + const int32_t touchDeviceId = 4; + NotifyMotionArgs args; + + // Start hovering with the mouse + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(10).y(10)) + .build())); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + + // Touch goes down + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .build())); + + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); +} + +/** * Inject a mouse hover event followed by a tap from touchscreen. * The tap causes a HOVER_EXIT event to be generated because the current event * stream's source has been switched. @@ -4534,6 +5455,7 @@ TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_StopsKeyRepeatAfterDisableInp } TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseEventIdFromInputDispatcher) { + GTEST_SKIP() << "Flaky test (b/270393106)"; sendAndConsumeKeyDown(/*deviceId=*/1); for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) { InputEvent* repeatEvent = mWindow->consume(); @@ -4544,6 +5466,7 @@ TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseEventIdFrom } TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseUniqueEventId) { + GTEST_SKIP() << "Flaky test (b/270393106)"; sendAndConsumeKeyDown(/*deviceId=*/1); std::unordered_set<int32_t> idSet; @@ -4673,6 +5596,13 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorMotionEvent_MultiDisplay) { windowInSecondary->consumeMotionDown(SECOND_DISPLAY_ID); monitorInSecondary.consumeMotionDown(SECOND_DISPLAY_ID); + // Lift up the touch from the second display + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID)) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + windowInSecondary->consumeMotionUp(SECOND_DISPLAY_ID); + monitorInSecondary.consumeMotionUp(SECOND_DISPLAY_ID); + // Test inject a non-pointer motion event. // If specific a display, it will dispatch to the focused window of particular display, // or it will dispatch to the focused window of focused display. diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp index a02ef0548e..ae300066d2 100644 --- a/services/inputflinger/tests/InputMapperTest.cpp +++ b/services/inputflinger/tests/InputMapperTest.cpp @@ -54,7 +54,8 @@ std::list<NotifyArgs> InputMapperTest::configureDevice(uint32_t changes) { if (!changes || (changes & (InputReaderConfiguration::CHANGE_DISPLAY_INFO | - InputReaderConfiguration::CHANGE_POINTER_CAPTURE))) { + InputReaderConfiguration::CHANGE_POINTER_CAPTURE | + InputReaderConfiguration::CHANGE_DEVICE_TYPE))) { mReader->requestRefreshConfiguration(changes); mReader->loopOnce(); } diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index e1c54e9914..0855683ba2 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -6678,6 +6678,75 @@ TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsSetToTouchNavigation_setsCorr 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()); +} + +TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsChangedToTouchNavigation_updatesDeviceType) { + // Initialize the device without setting device source to touch navigation. + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>(); + + // Ensure that the device is created as a touchscreen, not touch navigation. + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources()); + + // Add device type association after the device was created. + mFakePolicy->addDeviceTypeAssociation(DEVICE_LOCATION, "touchNavigation"); + + // Send update to the mapper. + std::list<NotifyArgs> unused2 = + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::CHANGE_DEVICE_TYPE /*changes*/); + + // Check whether device type update was successful. + ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mDevice->getSources()); +} + // --- TouchDisplayProjectionTest --- class TouchDisplayProjectionTest : public SingleTouchInputMapperTest { @@ -6685,28 +6754,30 @@ public: // The values inside DisplayViewport are expected to be pre-rotated. This updates the current // DisplayViewport to pre-rotate the values. The viewport's physical display will be set to the // rotated equivalent of the given un-rotated physical display bounds. - void configurePhysicalDisplay(ui::Rotation orientation, Rect naturalPhysicalDisplay) { + void configurePhysicalDisplay(ui::Rotation orientation, Rect naturalPhysicalDisplay, + int32_t naturalDisplayWidth = DISPLAY_WIDTH, + int32_t naturalDisplayHeight = DISPLAY_HEIGHT) { uint32_t inverseRotationFlags; - auto width = DISPLAY_WIDTH; - auto height = DISPLAY_HEIGHT; + auto rotatedWidth = naturalDisplayWidth; + auto rotatedHeight = naturalDisplayHeight; switch (orientation) { case ui::ROTATION_90: inverseRotationFlags = ui::Transform::ROT_270; - std::swap(width, height); + std::swap(rotatedWidth, rotatedHeight); break; case ui::ROTATION_180: inverseRotationFlags = ui::Transform::ROT_180; break; case ui::ROTATION_270: inverseRotationFlags = ui::Transform::ROT_90; - std::swap(width, height); + std::swap(rotatedWidth, rotatedHeight); break; case ui::ROTATION_0: inverseRotationFlags = ui::Transform::ROT_0; break; } - const ui::Transform rotation(inverseRotationFlags, width, height); + const ui::Transform rotation(inverseRotationFlags, rotatedWidth, rotatedHeight); const Rect rotatedPhysicalDisplay = rotation.transform(naturalPhysicalDisplay); std::optional<DisplayViewport> internalViewport = @@ -6725,8 +6796,8 @@ public: v.physicalRight = rotatedPhysicalDisplay.right; v.physicalBottom = rotatedPhysicalDisplay.bottom; - v.deviceWidth = width; - v.deviceHeight = height; + v.deviceWidth = rotatedWidth; + v.deviceHeight = rotatedHeight; v.isActive = true; v.uniqueId = UNIQUE_ID; @@ -6840,6 +6911,197 @@ TEST_F(TouchDisplayProjectionTest, EmitsTouchDownAfterEnteringPhysicalDisplay) { } } +// --- TouchscreenPrecisionTests --- + +// This test suite is used to ensure that touchscreen devices are scaled and configured correctly +// in various orientations and with different display rotations. We configure the touchscreen to +// have a higher resolution than that of the display by an integer scale factor in each axis so that +// we can enforce that coordinates match precisely as expected. +class TouchscreenPrecisionTestsFixture : public TouchDisplayProjectionTest, + public ::testing::WithParamInterface<ui::Rotation> { +public: + void SetUp() override { + SingleTouchInputMapperTest::SetUp(); + + // Prepare the raw axes to have twice the resolution of the display in the X axis and + // four times the resolution of the display in the Y axis. + prepareButtons(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_X, PRECISION_RAW_X_MIN, PRECISION_RAW_X_MAX, + 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Y, PRECISION_RAW_Y_MIN, PRECISION_RAW_Y_MAX, + 0, 0); + } + + static const int32_t PRECISION_RAW_X_MIN = TouchInputMapperTest::RAW_X_MIN; + static const int32_t PRECISION_RAW_X_MAX = PRECISION_RAW_X_MIN + DISPLAY_WIDTH * 2 - 1; + static const int32_t PRECISION_RAW_Y_MIN = TouchInputMapperTest::RAW_Y_MIN; + static const int32_t PRECISION_RAW_Y_MAX = PRECISION_RAW_Y_MIN + DISPLAY_HEIGHT * 4 - 1; + + static const std::array<Point, 4> kRawCorners; +}; + +const std::array<Point, 4> TouchscreenPrecisionTestsFixture::kRawCorners = {{ + {PRECISION_RAW_X_MIN, PRECISION_RAW_Y_MIN}, // left-top + {PRECISION_RAW_X_MAX, PRECISION_RAW_Y_MIN}, // right-top + {PRECISION_RAW_X_MAX, PRECISION_RAW_Y_MAX}, // right-bottom + {PRECISION_RAW_X_MIN, PRECISION_RAW_Y_MAX}, // left-bottom +}}; + +// Tests for how the touchscreen is oriented relative to the natural orientation of the display. +// For example, if a touchscreen is configured with an orientation of 90 degrees, it is a portrait +// touchscreen panel that is used on a device whose natural display orientation is in landscape. +TEST_P(TouchscreenPrecisionTestsFixture, OrientationPrecision) { + enum class Orientation { + ORIENTATION_0 = ui::toRotationInt(ui::ROTATION_0), + ORIENTATION_90 = ui::toRotationInt(ui::ROTATION_90), + ORIENTATION_180 = ui::toRotationInt(ui::ROTATION_180), + ORIENTATION_270 = ui::toRotationInt(ui::ROTATION_270), + ftl_last = ORIENTATION_270, + }; + using Orientation::ORIENTATION_0, Orientation::ORIENTATION_90, Orientation::ORIENTATION_180, + Orientation::ORIENTATION_270; + static const std::map<Orientation, std::array<vec2, 4> /*mappedCorners*/> kMappedCorners = { + {ORIENTATION_0, {{{0, 0}, {479.5, 0}, {479.5, 799.75}, {0, 799.75}}}}, + {ORIENTATION_90, {{{0, 479.5}, {0, 0}, {799.75, 0}, {799.75, 479.5}}}}, + {ORIENTATION_180, {{{479.5, 799.75}, {0, 799.75}, {0, 0}, {479.5, 0}}}}, + {ORIENTATION_270, {{{799.75, 0}, {799.75, 479.5}, {0, 479.5}, {0, 0}}}}, + }; + + const auto touchscreenOrientation = static_cast<Orientation>(ui::toRotationInt(GetParam())); + + // Configure the touchscreen as being installed in the one of the four different orientations + // relative to the display. + addConfigurationProperty("touch.deviceType", "touchScreen"); + addConfigurationProperty("touch.orientation", ftl::enum_string(touchscreenOrientation).c_str()); + prepareDisplay(ui::ROTATION_0); + + SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>(); + + // If the touchscreen is installed in a rotated orientation relative to the display (i.e. in + // orientations of either 90 or 270) this means the display's natural resolution will be + // flipped. + const bool displayRotated = + touchscreenOrientation == ORIENTATION_90 || touchscreenOrientation == ORIENTATION_270; + const int32_t width = displayRotated ? DISPLAY_HEIGHT : DISPLAY_WIDTH; + const int32_t height = displayRotated ? DISPLAY_WIDTH : DISPLAY_HEIGHT; + const Rect physicalFrame{0, 0, width, height}; + configurePhysicalDisplay(ui::ROTATION_0, physicalFrame, width, height); + + const auto& expectedPoints = kMappedCorners.at(touchscreenOrientation); + const float expectedPrecisionX = displayRotated ? 4 : 2; + const float expectedPrecisionY = displayRotated ? 2 : 4; + + // Test all four corners. + for (int i = 0; i < 4; i++) { + const auto& raw = kRawCorners[i]; + processDown(mapper, raw.x, raw.y); + processSync(mapper); + const auto& expected = expectedPoints[i]; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(expected.x, expected.y), + WithPrecision(expectedPrecisionX, expectedPrecisionY)))) + << "Failed to process raw point (" << raw.x << ", " << raw.y << ") " + << "with touchscreen orientation " + << ftl::enum_string(touchscreenOrientation).c_str() << ", expected point (" + << expected.x << ", " << expected.y << ")."; + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(expected.x, expected.y)))); + } +} + +TEST_P(TouchscreenPrecisionTestsFixture, RotationPrecisionWhenOrientationAware) { + static const std::map<ui::Rotation /*rotation*/, std::array<vec2, 4> /*mappedCorners*/> + kMappedCorners = { + {ui::ROTATION_0, {{{0, 0}, {479.5, 0}, {479.5, 799.75}, {0, 799.75}}}}, + {ui::ROTATION_90, {{{0.5, 0}, {480, 0}, {480, 799.75}, {0.5, 799.75}}}}, + {ui::ROTATION_180, {{{0.5, 0.25}, {480, 0.25}, {480, 800}, {0.5, 800}}}}, + {ui::ROTATION_270, {{{0, 0.25}, {479.5, 0.25}, {479.5, 800}, {0, 800}}}}, + }; + + const ui::Rotation displayRotation = GetParam(); + + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(displayRotation); + + SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>(); + + const auto& expectedPoints = kMappedCorners.at(displayRotation); + + // Test all four corners. + for (int i = 0; i < 4; i++) { + const auto& expected = expectedPoints[i]; + const auto& raw = kRawCorners[i]; + processDown(mapper, raw.x, raw.y); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(expected.x, expected.y), WithPrecision(2, 4)))) + << "Failed to process raw point (" << raw.x << ", " << raw.y << ") " + << "with display rotation " << ui::toCString(displayRotation) + << ", expected point (" << expected.x << ", " << expected.y << ")."; + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(expected.x, expected.y)))); + } +} + +TEST_P(TouchscreenPrecisionTestsFixture, RotationPrecisionOrientationAwareInOri270) { + static const std::map<ui::Rotation /*orientation*/, std::array<vec2, 4> /*mappedCorners*/> + kMappedCorners = { + {ui::ROTATION_0, {{{799.75, 0}, {799.75, 479.5}, {0, 479.5}, {0, 0}}}}, + {ui::ROTATION_90, {{{800, 0}, {800, 479.5}, {0.25, 479.5}, {0.25, 0}}}}, + {ui::ROTATION_180, {{{800, 0.5}, {800, 480}, {0.25, 480}, {0.25, 0.5}}}}, + {ui::ROTATION_270, {{{799.75, 0.5}, {799.75, 480}, {0, 480}, {0, 0.5}}}}, + }; + + const ui::Rotation displayRotation = GetParam(); + + addConfigurationProperty("touch.deviceType", "touchScreen"); + addConfigurationProperty("touch.orientation", "ORIENTATION_270"); + + SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>(); + + // Ori 270, so width and height swapped + const Rect physicalFrame{0, 0, DISPLAY_HEIGHT, DISPLAY_WIDTH}; + prepareDisplay(displayRotation); + configurePhysicalDisplay(displayRotation, physicalFrame, DISPLAY_HEIGHT, DISPLAY_WIDTH); + + const auto& expectedPoints = kMappedCorners.at(displayRotation); + + // Test all four corners. + for (int i = 0; i < 4; i++) { + const auto& expected = expectedPoints[i]; + const auto& raw = kRawCorners[i]; + processDown(mapper, raw.x, raw.y); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(expected.x, expected.y), WithPrecision(4, 2)))) + << "Failed to process raw point (" << raw.x << ", " << raw.y << ") " + << "with display rotation " << ui::toCString(displayRotation) + << ", expected point (" << expected.x << ", " << expected.y << ")."; + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(expected.x, expected.y)))); + } +} + +// Run the precision tests for all rotations. +INSTANTIATE_TEST_SUITE_P(TouchscreenPrecisionTests, TouchscreenPrecisionTestsFixture, + ::testing::Values(ui::ROTATION_0, ui::ROTATION_90, ui::ROTATION_180, + ui::ROTATION_270), + [](const testing::TestParamInfo<ui::Rotation>& testParamInfo) { + return ftl::enum_string(testParamInfo.param); + }); + // --- ExternalStylusFusionTest --- class ExternalStylusFusionTest : public SingleTouchInputMapperTest { @@ -9757,6 +10019,58 @@ TEST_F(MultiTouchInputMapperTest, StylusSourceIsAddedDynamicallyFromToolType) { 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 +10300,26 @@ TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) { 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) { diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h index b9d96076f1..edd14f82e7 100644 --- a/services/inputflinger/tests/TestInputListenerMatchers.h +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -176,4 +176,10 @@ MATCHER_P(WithDownTime, downTime, "InputEvent with specified downTime") { return arg.downTime == downTime; } +MATCHER_P2(WithPrecision, xPrecision, yPrecision, "MotionEvent with specified precision") { + *result_listener << "expected x-precision " << xPrecision << " and y-precision " << yPrecision + << ", but got " << arg.xPrecision << " and " << arg.yPrecision; + return arg.xPrecision == xPrecision && arg.yPrecision == yPrecision; +} + } // namespace android diff --git a/services/memtrackproxy/MemtrackProxy.cpp b/services/memtrackproxy/MemtrackProxy.cpp index 467616724a..9e41a9333e 100644 --- a/services/memtrackproxy/MemtrackProxy.cpp +++ b/services/memtrackproxy/MemtrackProxy.cpp @@ -97,9 +97,14 @@ bool MemtrackProxy::CheckPid(pid_t calling_pid, pid_t request_pid) { return calling_pid == request_pid; } -MemtrackProxy::MemtrackProxy() - : memtrack_hidl_instance_(MemtrackProxy::MemtrackHidlInstance()), - memtrack_aidl_instance_(MemtrackProxy::MemtrackAidlInstance()) {} +MemtrackProxy::MemtrackProxy() { + memtrack_aidl_instance_ = MemtrackProxy::MemtrackAidlInstance(); + + // Only check for a HIDL implementation if we failed to get the AIDL service + if (!memtrack_aidl_instance_) { + memtrack_hidl_instance_ = MemtrackProxy::MemtrackHidlInstance(); + } +} ndk::ScopedAStatus MemtrackProxy::getMemory(int pid, MemtrackType type, std::vector<MemtrackRecord>* _aidl_return) { diff --git a/services/sensorservice/SensorDirectConnection.cpp b/services/sensorservice/SensorDirectConnection.cpp index 4ac9651a7b..4fff8bb1b4 100644 --- a/services/sensorservice/SensorDirectConnection.cpp +++ b/services/sensorservice/SensorDirectConnection.cpp @@ -28,10 +28,10 @@ using util::ProtoOutputStream; SensorService::SensorDirectConnection::SensorDirectConnection(const sp<SensorService>& service, uid_t uid, const sensors_direct_mem_t *mem, int32_t halChannelHandle, - const String16& opPackageName) + const String16& opPackageName, int deviceId) : mService(service), mUid(uid), mMem(*mem), mHalChannelHandle(halChannelHandle), - mOpPackageName(opPackageName), mDestroyed(false) { + mOpPackageName(opPackageName), mDeviceId(deviceId), mDestroyed(false) { mUserId = multiuser_get_user_id(mUid); ALOGD_IF(DEBUG_CONNECTIONS, "Created SensorDirectConnection"); } @@ -180,8 +180,7 @@ int32_t SensorService::SensorDirectConnection::configureChannel(int handle, int }; Mutex::Autolock _l(mConnectionLock); - SensorDevice& dev(SensorDevice::getInstance()); - int ret = dev.configureDirectChannel(handle, getHalChannelHandle(), &config); + int ret = configure(handle, &config); if (rateLevel == SENSOR_DIRECT_RATE_STOP) { if (ret == NO_ERROR) { @@ -224,7 +223,6 @@ void SensorService::SensorDirectConnection::capRates() { std::unordered_map<int, int>& existingConnections = (!temporarilyStopped) ? mActivated : mActivatedBackup; - SensorDevice& dev(SensorDevice::getInstance()); for (auto &i : existingConnections) { int handle = i.first; int rateLevel = i.second; @@ -239,8 +237,8 @@ void SensorService::SensorDirectConnection::capRates() { // Only reconfigure the channel if it's ongoing if (!temporarilyStopped) { // Stopping before reconfiguring is the well-tested path in CTS - dev.configureDirectChannel(handle, getHalChannelHandle(), &stopConfig); - dev.configureDirectChannel(handle, getHalChannelHandle(), &capConfig); + configure(handle, &stopConfig); + configure(handle, &capConfig); } } } @@ -258,7 +256,6 @@ void SensorService::SensorDirectConnection::uncapRates() { const struct sensors_direct_cfg_t stopConfig = { .rate_level = SENSOR_DIRECT_RATE_STOP }; - SensorDevice& dev(SensorDevice::getInstance()); for (auto &i : mMicRateBackup) { int handle = i.first; int rateLevel = i.second; @@ -273,13 +270,23 @@ void SensorService::SensorDirectConnection::uncapRates() { // Only reconfigure the channel if it's ongoing if (!temporarilyStopped) { // Stopping before reconfiguring is the well-tested path in CTS - dev.configureDirectChannel(handle, getHalChannelHandle(), &stopConfig); - dev.configureDirectChannel(handle, getHalChannelHandle(), &config); + configure(handle, &stopConfig); + configure(handle, &config); } } mMicRateBackup.clear(); } +int SensorService::SensorDirectConnection::configure( + int handle, const sensors_direct_cfg_t* config) { + if (mDeviceId == RuntimeSensor::DEFAULT_DEVICE_ID) { + SensorDevice& dev(SensorDevice::getInstance()); + return dev.configureDirectChannel(handle, getHalChannelHandle(), config); + } else { + return mService->configureRuntimeSensorDirectChannel(handle, this, config); + } +} + void SensorService::SensorDirectConnection::stopAll(bool backupRecord) { Mutex::Autolock _l(mConnectionLock); stopAllLocked(backupRecord); @@ -290,9 +297,8 @@ void SensorService::SensorDirectConnection::stopAllLocked(bool backupRecord) { .rate_level = SENSOR_DIRECT_RATE_STOP }; - SensorDevice& dev(SensorDevice::getInstance()); for (auto &i : mActivated) { - dev.configureDirectChannel(i.first, getHalChannelHandle(), &config); + configure(i.first, &config); } if (backupRecord && mActivatedBackup.empty()) { @@ -306,8 +312,6 @@ void SensorService::SensorDirectConnection::recoverAll() { if (!mActivatedBackup.empty()) { stopAllLocked(false); - SensorDevice& dev(SensorDevice::getInstance()); - // recover list of report from backup ALOG_ASSERT(mActivated.empty(), "mActivated must be empty if mActivatedBackup was non-empty"); @@ -319,7 +323,7 @@ void SensorService::SensorDirectConnection::recoverAll() { struct sensors_direct_cfg_t config = { .rate_level = i.second }; - dev.configureDirectChannel(i.first, getHalChannelHandle(), &config); + configure(i.first, &config); } } } diff --git a/services/sensorservice/SensorDirectConnection.h b/services/sensorservice/SensorDirectConnection.h index d39a073f98..bfaf811330 100644 --- a/services/sensorservice/SensorDirectConnection.h +++ b/services/sensorservice/SensorDirectConnection.h @@ -39,7 +39,7 @@ class SensorService::SensorDirectConnection: public BnSensorEventConnection { public: SensorDirectConnection(const sp<SensorService>& service, uid_t uid, const sensors_direct_mem_t *mem, int32_t halChannelHandle, - const String16& opPackageName); + const String16& opPackageName, int deviceId); void dump(String8& result) const; void dump(util::ProtoOutputStream* proto) const; uid_t getUid() const { return mUid; } @@ -53,6 +53,7 @@ public: void onSensorAccessChanged(bool hasAccess); void onMicSensorAccessChanged(bool isMicToggleOn); userid_t getUserId() const { return mUserId; } + int getDeviceId() const { return mDeviceId; } protected: virtual ~SensorDirectConnection(); @@ -68,6 +69,9 @@ protected: private: bool hasSensorAccess() const; + // Sends the configuration to the relevant sensor device. + int configure(int handle, const sensors_direct_cfg_t* config); + // Stops all active sensor direct report requests. // // If backupRecord is true, stopped requests can be recovered @@ -95,6 +99,7 @@ private: const sensors_direct_mem_t mMem; const int32_t mHalChannelHandle; const String16 mOpPackageName; + const int mDeviceId; mutable Mutex mConnectionLock; std::unordered_map<int, int> mActivated; diff --git a/services/sensorservice/SensorInterface.cpp b/services/sensorservice/SensorInterface.cpp index 398cdf9a0e..e9c8335963 100644 --- a/services/sensorservice/SensorInterface.cpp +++ b/services/sensorservice/SensorInterface.cpp @@ -87,14 +87,15 @@ VirtualSensor::VirtualSensor() : // --------------------------------------------------------------------------- -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 @@ status_t RuntimeSensor::batch(void*, int, int, int64_t samplingPeriodNs, 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 @@ status_t RuntimeSensor::setDelay(void*, int, int64_t ns) { 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 5ee5e1224a..c446d61f89 100644 --- a/services/sensorservice/SensorInterface.h +++ b/services/sensorservice/SensorInterface.h @@ -108,12 +108,12 @@ class RuntimeSensor : public BaseSensor { 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 @@ private: 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 5c98614f1a..0fb3cadc63 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -116,16 +116,17 @@ int32_t nextRuntimeSensorHandle() { 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 @@ SensorService::SensorService() } 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,8 +180,8 @@ int SensorService::registerRuntimeSensor( ALOGI("Registering runtime sensor handle 0x%x, type %d, name %s", handle, sensor.type, sensor.name); - sp<RuntimeSensor::StateChangeCallback> runtimeSensorCallback( - new RuntimeSensorCallbackProxy(std::move(callback))); + sp<RuntimeSensor::SensorCallback> runtimeSensorCallback( + new RuntimeSensorCallbackProxy(callback)); sensor_t runtimeSensor = sensor; // force the handle to be consistent runtimeSensor.handle = handle; @@ -192,11 +193,15 @@ int SensorService::registerRuntimeSensor( return mSensors.getNonSensor().getHandle(); } + if (mRuntimeSensorCallbacks.find(deviceId) == mRuntimeSensorCallbacks.end()) { + mRuntimeSensorCallbacks.emplace(deviceId, callback); + } return handle; } status_t SensorService::unregisterRuntimeSensor(int handle) { ALOGI("Unregistering runtime sensor handle 0x%x disconnected", handle); + int deviceId = getDeviceIdFromHandle(handle); { Mutex::Autolock _l(mLock); if (!unregisterDynamicSensorLocked(handle)) { @@ -209,6 +214,20 @@ status_t SensorService::unregisterRuntimeSensor(int handle) { for (const sp<SensorEventConnection>& connection : connLock.getActiveConnections()) { connection->removeSensor(handle); } + + // If this was the last sensor for this device, remove its callback. + bool deviceHasSensors = false; + mSensors.forEachEntry( + [&deviceId, &deviceHasSensors] (const SensorServiceUtil::SensorList::Entry& e) -> bool { + if (e.deviceId == deviceId) { + deviceHasSensors = true; + return false; // stop iterating + } + return true; + }); + if (!deviceHasSensors) { + mRuntimeSensorCallbacks.erase(deviceId); + } return OK; } @@ -1516,7 +1535,7 @@ int SensorService::isDataInjectionEnabled() { } sp<ISensorEventConnection> SensorService::createSensorDirectConnection( - const String16& opPackageName, uint32_t size, int32_t type, int32_t format, + const String16& opPackageName, int deviceId, uint32_t size, int32_t type, int32_t format, const native_handle *resource) { resetTargetSdkVersionCache(opPackageName); ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock); @@ -1592,14 +1611,25 @@ sp<ISensorEventConnection> SensorService::createSensorDirectConnection( native_handle_set_fdsan_tag(clone); sp<SensorDirectConnection> conn; - SensorDevice& dev(SensorDevice::getInstance()); - int channelHandle = dev.registerDirectChannel(&mem); + int channelHandle = 0; + if (deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) { + SensorDevice& dev(SensorDevice::getInstance()); + channelHandle = dev.registerDirectChannel(&mem); + } else { + auto runtimeSensorCallback = mRuntimeSensorCallbacks.find(deviceId); + if (runtimeSensorCallback == mRuntimeSensorCallbacks.end()) { + ALOGE("Runtime sensor callback for deviceId %d not found", deviceId); + } else { + int fd = dup(clone->data[0]); + channelHandle = runtimeSensorCallback->second->onDirectChannelCreated(fd); + } + } if (channelHandle <= 0) { ALOGE("SensorDevice::registerDirectChannel returns %d", channelHandle); } else { mem.handle = clone; - conn = new SensorDirectConnection(this, uid, &mem, channelHandle, opPackageName); + conn = new SensorDirectConnection(this, uid, &mem, channelHandle, opPackageName, deviceId); } if (conn == nullptr) { @@ -1613,6 +1643,24 @@ sp<ISensorEventConnection> SensorService::createSensorDirectConnection( return conn; } +int SensorService::configureRuntimeSensorDirectChannel( + int sensorHandle, const SensorDirectConnection* c, const sensors_direct_cfg_t* config) { + int deviceId = c->getDeviceId(); + int sensorDeviceId = getDeviceIdFromHandle(sensorHandle); + if (sensorDeviceId != c->getDeviceId()) { + ALOGE("Cannot configure direct channel created for device %d with a sensor that belongs" + "to device %d", c->getDeviceId(), sensorDeviceId); + return BAD_VALUE; + } + auto runtimeSensorCallback = mRuntimeSensorCallbacks.find(deviceId); + if (runtimeSensorCallback == mRuntimeSensorCallbacks.end()) { + ALOGE("Runtime sensor callback for deviceId %d not found", deviceId); + return BAD_VALUE; + } + return runtimeSensorCallback->second->onDirectChannelConfigured( + c->getHalChannelHandle(), sensorHandle, config->rate_level); +} + int SensorService::setOperationParameter( int32_t handle, int32_t type, const Vector<float> &floats, const Vector<int32_t> &ints) { @@ -1768,8 +1816,18 @@ void SensorService::cleanupConnection(SensorEventConnection* c) { void SensorService::cleanupConnection(SensorDirectConnection* c) { Mutex::Autolock _l(mLock); - SensorDevice& dev(SensorDevice::getInstance()); - dev.unregisterDirectChannel(c->getHalChannelHandle()); + int deviceId = c->getDeviceId(); + if (deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) { + SensorDevice& dev(SensorDevice::getInstance()); + dev.unregisterDirectChannel(c->getHalChannelHandle()); + } else { + auto runtimeSensorCallback = mRuntimeSensorCallbacks.find(deviceId); + if (runtimeSensorCallback != mRuntimeSensorCallbacks.end()) { + runtimeSensorCallback->second->onDirectChannelDestroyed(c->getHalChannelHandle()); + } else { + ALOGE("Runtime sensor callback for deviceId %d not found", deviceId); + } + } mConnectionHolder.removeDirectConnection(c); } @@ -1847,6 +1905,19 @@ std::shared_ptr<SensorInterface> SensorService::getSensorInterfaceFromHandle(int return mSensors.getInterface(handle); } +int SensorService::getDeviceIdFromHandle(int handle) const { + int deviceId = RuntimeSensor::DEFAULT_DEVICE_ID; + mSensors.forEachEntry( + [&deviceId, handle] (const SensorServiceUtil::SensorList::Entry& e) -> bool { + if (e.si->getSensor().getHandle() == handle) { + deviceId = e.deviceId; + return false; // stop iterating + } + return true; + }); + return deviceId; +} + status_t SensorService::enable(const sp<SensorEventConnection>& connection, int handle, nsecs_t samplingPeriodNs, nsecs_t maxBatchReportLatencyNs, int reservedFlags, const String16& opPackageName) { diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h index 0798279b6b..fe72a69f15 100644 --- a/services/sensorservice/SensorService.h +++ b/services/sensorservice/SensorService.h @@ -147,12 +147,17 @@ public: 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; + virtual int onDirectChannelCreated(int fd) = 0; + virtual void onDirectChannelDestroyed(int channelHandle) = 0; + virtual int onDirectChannelConfigured(int channelHandle, int sensorHandle, + int rateLevel) = 0; }; static char const* getServiceName() ANDROID_API { return "sensorservice"; } @@ -182,10 +187,13 @@ public: 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; + int configureRuntimeSensorDirectChannel(int sensorHandle, const SensorDirectConnection* c, + const sensors_direct_cfg_t* config); + // Returns true if a sensor should be throttled according to our rate-throttling rules. static bool isSensorInCappedSet(int sensorType); @@ -369,7 +377,8 @@ private: int requestedMode, const String16& opPackageName, const String16& attributionTag); virtual int isDataInjectionEnabled(); virtual sp<ISensorEventConnection> createSensorDirectConnection(const String16& opPackageName, - uint32_t size, int32_t type, int32_t format, const native_handle *resource); + int deviceId, uint32_t size, int32_t type, int32_t format, + const native_handle *resource); virtual int setOperationParameter( int32_t handle, int32_t type, const Vector<float> &floats, const Vector<int32_t> &ints); virtual status_t dump(int fd, const Vector<String16>& args); @@ -379,6 +388,7 @@ private: String8 getSensorStringType(int handle) const; bool isVirtualSensor(int handle) const; std::shared_ptr<SensorInterface> getSensorInterfaceFromHandle(int handle) const; + int getDeviceIdFromHandle(int handle) const; bool isWakeUpSensor(int type) const; void recordLastValueLocked(sensors_event_t const* buffer, size_t count); static void sortEventBuffer(sensors_event_t* buffer, size_t count); @@ -516,6 +526,7 @@ private: std::unordered_map<int, SensorServiceUtil::RecentEventLogger*> mRecentEvent; Mode mCurrentOperatingMode; std::queue<sensors_event_t> mRuntimeSensorEventQueue; + std::unordered_map</*deviceId*/int, sp<RuntimeSensorCallback>> mRuntimeSensorCallbacks; // true if the head tracker sensor type is currently restricted to system usage only // (can only be unrestricted for testing, via shell cmd) diff --git a/services/sensorservice/aidl/DirectReportChannel.cpp b/services/sensorservice/aidl/DirectReportChannel.cpp index cab53c17f5..9ef4ca8d08 100644 --- a/services/sensorservice/aidl/DirectReportChannel.cpp +++ b/services/sensorservice/aidl/DirectReportChannel.cpp @@ -32,7 +32,7 @@ ndk::ScopedAStatus DirectReportChannel::configure( int32_t sensorHandle, ::aidl::android::hardware::sensors::ISensors::RateLevel rate, int32_t* _aidl_return) { int token = mManager.configureDirectChannel(mId, sensorHandle, static_cast<int>(rate)); - if (token <= 0) { + if (token < 0) { return ndk::ScopedAStatus::fromServiceSpecificError(token); } *_aidl_return = token; diff --git a/services/sensorservice/aidl/SensorManager.cpp b/services/sensorservice/aidl/SensorManager.cpp index b7aecdf1bd..08e00b4401 100644 --- a/services/sensorservice/aidl/SensorManager.cpp +++ b/services/sensorservice/aidl/SensorManager.cpp @@ -59,6 +59,9 @@ SensorManagerAidl::~SensorManagerAidl() { if (mPollThread.joinable()) { mPollThread.join(); } + + ::android::SensorManager::removeInstanceForPackage( + String16(ISensorManager::descriptor)); } ndk::ScopedAStatus createDirectChannel(::android::SensorManager& manager, size_t size, int type, diff --git a/services/sensorservice/aidl/fuzzer/fuzzer.cpp b/services/sensorservice/aidl/fuzzer/fuzzer.cpp index 1b63d76953..ee8ceb354c 100644 --- a/services/sensorservice/aidl/fuzzer/fuzzer.cpp +++ b/services/sensorservice/aidl/fuzzer/fuzzer.cpp @@ -16,7 +16,7 @@ #include <fuzzbinder/libbinder_ndk_driver.h> #include <fuzzer/FuzzedDataProvider.h> -#include <ServiceManager.h> +#include <fakeservicemanager/FakeServiceManager.h> #include <android-base/logging.h> #include <android/binder_interface_utils.h> #include <fuzzbinder/random_binder.h> @@ -29,7 +29,7 @@ using ndk::SharedRefBase; [[clang::no_destroy]] static std::once_flag gSmOnce; extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - static android::sp<android::ServiceManager> fakeServiceManager = new android::ServiceManager(); + static android::sp<android::FakeServiceManager> fakeServiceManager = new android::FakeServiceManager(); std::call_once(gSmOnce, [&] { setDefaultServiceManager(fakeServiceManager); }); fakeServiceManager->clear(); diff --git a/services/sensorservice/hidl/SensorManager.cpp b/services/sensorservice/hidl/SensorManager.cpp index f04712c534..3d148e103a 100644 --- a/services/sensorservice/hidl/SensorManager.cpp +++ b/services/sensorservice/hidl/SensorManager.cpp @@ -60,6 +60,9 @@ SensorManager::~SensorManager() { if (mPollThread.joinable()) { mPollThread.join(); } + + ::android::SensorManager::removeInstanceForPackage( + String16(ISensorManager::descriptor)); } // Methods from ::android::frameworks::sensorservice::V1_0::ISensorManager follow. diff --git a/services/stats/.clang-format b/services/stats/.clang-format new file mode 100644 index 0000000000..cead3a0794 --- /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 410a5af421..0f01507c9f 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" +#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 "StatsAidl.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 @@ ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) { 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 @@ ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) { 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 d27d989c81..19176d9aaf 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 @@ hardware::Return<void> StatsHal::reportChargeCycles(const ChargeCycles& chargeCy 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::reportSlowIo(const SlowIo& slowIo) { 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 @@ hardware::Return<void> StatsHal::reportUsbPortOverheatEvent( 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 219e71e7b0..340b53955d 100644 --- a/services/stats/include/stats/StatsAidl.h +++ b/services/stats/include/stats/StatsAidl.h @@ -28,8 +28,7 @@ public: /** * 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 071e54f07c..864ad1457f 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 @@ namespace implementation { using android::hardware::Return; /** -* Implements the Stats HAL -*/ + * Implements the Stats HAL + */ class StatsHal : public IStats { public: StatsHal(); @@ -50,12 +49,12 @@ public: * 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 @@ public: /** * 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/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h index 4777f13598..a8322d8572 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h @@ -97,6 +97,8 @@ struct CompositionRefreshArgs { std::optional<std::chrono::steady_clock::time_point> scheduledFrameTime; std::vector<BorderRenderInfo> borderInfoList; + + bool hasTrustedPresentationListener = false; }; } // namespace android::compositionengine diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h index 5bb249719e..eae5871477 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h @@ -18,6 +18,7 @@ #include <cstdint> +#include <android/gui/CachingHint.h> #include <gui/HdrMetadata.h> #include <math/mat4.h> #include <ui/BlurRegion.h> @@ -212,6 +213,7 @@ struct LayerFECompositionState { float currentSdrHdrRatio = 1.f; float desiredSdrHdrRatio = 1.f; + gui::CachingHint cachingHint = gui::CachingHint::Enabled; virtual ~LayerFECompositionState(); // Debugging diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h index 52ebd9ebec..a3d86398cf 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h @@ -153,6 +153,10 @@ public: Region aboveOpaqueLayers; // The region of the output which should be considered dirty Region dirtyRegion; + // The region of the output which is covered by layers, excluding display overlays. This + // only has a value if there's something needing it, like when a TrustedPresentationListener + // is set + std::optional<Region> aboveCoveredLayersExcludingOverlays; }; virtual ~Output(); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h index b86782f417..7b0af3a248 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h @@ -63,9 +63,15 @@ struct OutputLayerCompositionState { // The portion of the layer that is not obscured and is also opaque Region visibleNonTransparentRegion; - // The portion of the layer that is obscured by opaque layers on top + // The portion of the layer that is obscured by all layers on top. This includes transparent and + // opaque. Region coveredRegion; + // The portion of the layer that is obscured by all layers on top excluding display overlays. + // This only has a value if there's something needing it, like when a + // TrustedPresentationListener is set. + std::optional<Region> coveredRegionExcludingDisplayOverlays; + // The visibleRegion transformed to output space Region outputSpaceVisibleRegion; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h index 24a7744542..d26ca9dd00 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h @@ -149,6 +149,9 @@ public: bool hasSolidColorLayers() const; + // True if any layer in this cached set has CachingHint::Disabled + bool cachingHintExcludesLayers() const; + private: const NonBufferHash mFingerprint; std::chrono::steady_clock::time_point mLastUpdate = std::chrono::steady_clock::now(); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h index e309442220..d5c488e40e 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h @@ -73,6 +73,7 @@ enum class LayerStateField : uint32_t { BackgroundBlurRadius = 1u << 17, BlurRegions = 1u << 18, HasProtectedContent = 1u << 19, + CachingHint = 1u << 20, }; // clang-format on @@ -250,6 +251,8 @@ public: bool isProtected() const { return mIsProtected.get(); } + gui::CachingHint getCachingHint() const { return mCachingHint.get(); } + bool hasSolidColorCompositionType() const { return getOutputLayer()->getLayerFE().getCompositionState()->compositionType == aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR; @@ -487,7 +490,15 @@ private: return layer->getLayerFE().getCompositionState()->hasProtectedContent; }}; - static const constexpr size_t kNumNonUniqueFields = 18; + OutputLayerState<gui::CachingHint, LayerStateField::CachingHint> + mCachingHint{[](auto layer) { + return layer->getLayerFE().getCompositionState()->cachingHint; + }, + [](const gui::CachingHint& cachingHint) { + return std::vector<std::string>{toString(cachingHint)}; + }}; + + static const constexpr size_t kNumNonUniqueFields = 19; std::array<StateInterface*, kNumNonUniqueFields> getNonUniqueFields() { std::array<const StateInterface*, kNumNonUniqueFields> constFields = @@ -501,13 +512,11 @@ private: } std::array<const StateInterface*, kNumNonUniqueFields> getNonUniqueFields() const { - return { - &mDisplayFrame, &mSourceCrop, &mBufferTransform, &mBlendMode, + return {&mDisplayFrame, &mSourceCrop, &mBufferTransform, &mBlendMode, &mAlpha, &mLayerMetadata, &mVisibleRegion, &mOutputDataspace, &mPixelFormat, &mColorTransform, &mCompositionType, &mSidebandStream, &mBuffer, &mSolidColor, &mBackgroundBlurRadius, &mBlurRegions, - &mFrameNumber, &mIsProtected, - }; + &mFrameNumber, &mIsProtected, &mCachingHint}; } }; diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp index 531b65963d..615d04b460 100644 --- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp +++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp @@ -126,6 +126,7 @@ void LayerFECompositionState::dump(std::string& out) const { dumpVal(out, "desired sdr/hdr ratio", desiredSdrHdrRatio); } dumpVal(out, "colorTransform", colorTransform); + dumpVal(out, "caching hint", toString(cachingHint)); out.append("\n"); } diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 403385ea8c..175dd1d825 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -31,6 +31,7 @@ #include <ftl/future.h> #include <gui/TraceUtils.h> +#include <optional> #include <thread> #include "renderengine/ExternalTexture.h" @@ -480,6 +481,9 @@ void Output::rebuildLayerStacks(const compositionengine::CompositionRefreshArgs& // Process the layers to determine visibility and coverage compositionengine::Output::CoverageState coverage{layerFESet}; + coverage.aboveCoveredLayersExcludingOverlays = refreshArgs.hasTrustedPresentationListener + ? std::make_optional<Region>() + : std::nullopt; collectVisibleLayers(refreshArgs, coverage); // Compute the resulting coverage for this output, and store it for later @@ -534,6 +538,9 @@ void Output::ensureOutputLayerIfVisible(sp<compositionengine::LayerFE>& layerFE, return; } + bool computeAboveCoveredExcludingOverlays = coverage.aboveCoveredLayersExcludingOverlays && + !layerFEState->outputFilter.toInternalDisplay; + /* * opaqueRegion: area of a surface that is fully opaque. */ @@ -575,6 +582,11 @@ void Output::ensureOutputLayerIfVisible(sp<compositionengine::LayerFE>& layerFE, */ Region shadowRegion; + /** + * covered region above excluding internal display overlay layers + */ + std::optional<Region> coveredRegionExcludingDisplayOverlays = std::nullopt; + const ui::Transform& tr = layerFEState->geomLayerTransform; // Get the visible region @@ -647,6 +659,12 @@ void Output::ensureOutputLayerIfVisible(sp<compositionengine::LayerFE>& layerFE, // Update accumAboveCoveredLayers for next (lower) layer coverage.aboveCoveredLayers.orSelf(visibleRegion); + if (CC_UNLIKELY(computeAboveCoveredExcludingOverlays)) { + coveredRegionExcludingDisplayOverlays = + coverage.aboveCoveredLayersExcludingOverlays->intersect(visibleRegion); + coverage.aboveCoveredLayersExcludingOverlays->orSelf(visibleRegion); + } + // subtract the opaque region covered by the layers above us visibleRegion.subtractSelf(coverage.aboveOpaqueLayers); @@ -733,6 +751,10 @@ void Output::ensureOutputLayerIfVisible(sp<compositionengine::LayerFE>& layerFE, ? outputState.transform.transform( transparentRegion.intersect(outputState.layerStackSpace.getContent())) : Region(); + if (CC_UNLIKELY(computeAboveCoveredExcludingOverlays)) { + outputLayerState.coveredRegionExcludingDisplayOverlays = + std::move(coveredRegionExcludingDisplayOverlays); + } } void Output::setReleasedLayers(const compositionengine::CompositionRefreshArgs&) { diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp index 6b69ce7941..1b86cd376a 100644 --- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp +++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp @@ -585,6 +585,7 @@ void OutputLayer::writeOutputIndependentPerFrameStateToHWC( case Composition::CURSOR: case Composition::DEVICE: case Composition::DISPLAY_DECORATION: + case Composition::REFRESH_RATE_INDICATOR: writeBufferStateToHWC(hwcLayer, outputIndependentState, skipLayer); break; case Composition::INVALID: @@ -780,6 +781,7 @@ void OutputLayer::detectDisallowedCompositionTypeChange(Composition from, Compos 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/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp index ed9a88d3c4..a00ce57e29 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp @@ -393,6 +393,18 @@ bool CachedSet::hasSolidColorLayers() const { }); } +bool CachedSet::cachingHintExcludesLayers() const { + const bool shouldExcludeLayers = + std::any_of(mLayers.cbegin(), mLayers.cend(), [](const Layer& layer) { + return layer.getState()->getCachingHint() == gui::CachingHint::Disabled; + }); + + LOG_ALWAYS_FATAL_IF(shouldExcludeLayers && getLayerCount() > 1, + "CachedSet is invalid: should be excluded but contains %zu layers", + getLayerCount()); + return shouldExcludeLayers; +} + void CachedSet::dump(std::string& result) const { const auto now = std::chrono::steady_clock::now(); diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp index 9175dd01a1..13b6307aea 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp @@ -413,6 +413,7 @@ std::vector<Flattener::Run> Flattener::findCandidateRuns(time_point now) const { for (auto currentSet = mLayers.cbegin(); currentSet != mLayers.cend(); ++currentSet) { bool layerIsInactive = now - currentSet->getLastUpdate() > mTunables.mActiveLayerTimeout; const bool layerHasBlur = currentSet->hasBlurBehind(); + const bool layerDeniedFromCaching = currentSet->cachingHintExcludesLayers(); // Layers should also be considered inactive whenever their framerate is lower than 1fps. if (!layerIsInactive && currentSet->getLayerCount() == kNumLayersFpsConsideration) { @@ -424,7 +425,8 @@ std::vector<Flattener::Run> Flattener::findCandidateRuns(time_point now) const { } } - if (layerIsInactive && (firstLayer || runHasFirstLayer || !layerHasBlur) && + if (!layerDeniedFromCaching && layerIsInactive && + (firstLayer || runHasFirstLayer || !layerHasBlur) && !currentSet->hasUnsupportedDataspace()) { if (isPartOfRun) { builder.increment(); diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp index 2fc029fdc6..6064126099 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp @@ -151,6 +151,10 @@ std::string to_string(const Plan& plan) { // 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 6199a5ae40..1a56ab751f 100644 --- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h +++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h @@ -93,7 +93,7 @@ public: 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)); @@ -115,8 +115,9 @@ public: MOCK_CONST_METHOD0( getHdrConversionCapabilities, std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability>()); - MOCK_METHOD1(setHdrConversionStrategy, - status_t(aidl::android::hardware::graphics::common::HdrConversionStrategy)); + MOCK_METHOD2(setHdrConversionStrategy, + status_t(aidl::android::hardware::graphics::common::HdrConversionStrategy, + aidl::android::hardware::graphics::common::Hdr*)); MOCK_METHOD2(setAutoLowLatencyMode, status_t(PhysicalDisplayId, bool)); MOCK_METHOD(status_t, getSupportedContentTypes, (PhysicalDisplayId, std::vector<hal::ContentType>*), (const, override)); @@ -146,6 +147,7 @@ public: MOCK_METHOD(bool, getValidateSkipped, (HalDisplayId), (const, override)); MOCK_METHOD(const aidl::android::hardware::graphics::composer3::OverlayProperties&, getOverlaySupport, (), (const, override)); + MOCK_METHOD(status_t, setRefreshRateChangedCallbackDebugEnabled, (PhysicalDisplayId, bool)); }; } // namespace mock diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp index 9ad2edb983..aa83883e95 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp @@ -1312,6 +1312,18 @@ TEST_F(OutputLayerWriteStateToHWCTest, setBlockingRegion) { false); } +TEST_F(OutputLayerWriteStateToHWCTest, setCompositionTypeRefreshRateIndicator) { + mLayerFEState.compositionType = Composition::REFRESH_RATE_INDICATOR; + + expectGeometryCommonCalls(); + expectPerFrameCommonCalls(); + expectSetHdrMetadataAndBufferCalls(); + expectSetCompositionTypeCall(Composition::REFRESH_RATE_INDICATOR); + + mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, + /*zIsOverridden*/ false, /*isPeekingThrough*/ false); +} + /* * OutputLayer::uncacheBuffers */ diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp index d5d688e705..ca5ba69e8e 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp @@ -577,6 +577,20 @@ TEST_F(CachedSetTest, rendersWithOffsetFramebufferContent) { cachedSet.append(CachedSet(layer3)); } +TEST_F(CachedSetTest, cachingHintIncludesLayersByDefault) { + CachedSet cachedSet(*mTestLayers[0]->cachedSetLayer.get()); + EXPECT_FALSE(cachedSet.cachingHintExcludesLayers()); +} + +TEST_F(CachedSetTest, cachingHintExcludesLayersWhenDisabled) { + CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get(); + mTestLayers[0]->layerFECompositionState.cachingHint = gui::CachingHint::Disabled; + mTestLayers[0]->layerState->update(&mTestLayers[0]->outputLayer); + + CachedSet cachedSet(layer1); + EXPECT_TRUE(cachedSet.cachingHintExcludesLayers()); +} + TEST_F(CachedSetTest, holePunch_requiresBuffer) { CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get(); auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState; diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp index 86cfee6f0a..778a0a8c93 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp @@ -1112,6 +1112,55 @@ TEST_F(FlattenerRenderSchedulingTest, flattenLayers_renderCachedSets_defersUpToM true); } +TEST_F(FlattenerTest, flattenLayers_skipsLayersDisabledFromCaching) { + auto& layerState1 = mTestLayers[0]->layerState; + const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; + + auto& layerState2 = mTestLayers[1]->layerState; + const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; + + // The third layer has a CachingHint that prevents caching from running + auto& layerState3 = mTestLayers[2]->layerState; + const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; + mTestLayers[2]->layerFECompositionState.cachingHint = gui::CachingHint::Disabled; + mTestLayers[2]->layerState->update(&mTestLayers[2]->outputLayer); + + const std::vector<const LayerState*> layers = { + layerState1.get(), + layerState2.get(), + layerState3.get(), + }; + + initializeFlattener(layers); + + mTime += 200ms; + initializeOverrideBuffer(layers); + EXPECT_EQ(getNonBufferHash(layers), + mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); + + // This will render a CachedSet. + EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) + .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); + + // We've rendered a CachedSet, but we haven't merged it in. + EXPECT_EQ(nullptr, overrideBuffer1); + EXPECT_EQ(nullptr, overrideBuffer2); + EXPECT_EQ(nullptr, overrideBuffer3); + + // This time we merge the CachedSet in, so we have a new hash, and we should + // only have two sets. + EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0); + initializeOverrideBuffer(layers); + EXPECT_NE(getNonBufferHash(layers), + mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); + + EXPECT_NE(nullptr, overrideBuffer1); + EXPECT_EQ(overrideBuffer1, overrideBuffer2); + EXPECT_EQ(nullptr, overrideBuffer3); +} + TEST_F(FlattenerTest, flattenLayers_skipsBT601_625) { auto& layerState1 = mTestLayers[0]->layerState; const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp index 47b682059f..044917ead9 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp @@ -994,6 +994,45 @@ TEST_F(LayerStateTest, hasBlurBehind_withBlurRegion_returnsTrue) { EXPECT_TRUE(mLayerState->hasBlurBehind()); } +TEST_F(LayerStateTest, updateCachingHint) { + OutputLayerCompositionState outputLayerCompositionState; + LayerFECompositionState layerFECompositionState; + layerFECompositionState.cachingHint = gui::CachingHint::Enabled; + setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, + layerFECompositionState); + mLayerState = std::make_unique<LayerState>(&mOutputLayer); + + mock::OutputLayer newOutputLayer; + sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make(); + LayerFECompositionState layerFECompositionStateTwo; + layerFECompositionStateTwo.cachingHint = gui::CachingHint::Disabled; + setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState, + layerFECompositionStateTwo); + ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer); + EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::CachingHint), updates); +} + +TEST_F(LayerStateTest, compareCachingHint) { + OutputLayerCompositionState outputLayerCompositionState; + LayerFECompositionState layerFECompositionState; + layerFECompositionState.cachingHint = gui::CachingHint::Enabled; + setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, + layerFECompositionState); + mLayerState = std::make_unique<LayerState>(&mOutputLayer); + mock::OutputLayer newOutputLayer; + sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make(); + LayerFECompositionState layerFECompositionStateTwo; + layerFECompositionStateTwo.cachingHint = gui::CachingHint::Disabled; + setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState, + layerFECompositionStateTwo); + auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer); + + verifyNonUniqueDifferingFields(*mLayerState, *otherLayerState, LayerStateField::CachingHint); + + EXPECT_TRUE(mLayerState->compare(*otherLayerState)); + EXPECT_TRUE(otherLayerState->compare(*mLayerState)); +} + TEST_F(LayerStateTest, dumpDoesNotCrash) { OutputLayerCompositionState outputLayerCompositionState; LayerFECompositionState layerFECompositionState; diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 5f73fbc1a6..3cdb3d5c76 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -408,8 +408,8 @@ HdrCapabilities DisplayDevice::getHdrCapabilities() const { capabilities.getDesiredMinLuminance()); } -void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinner, bool showRenderRate, - bool showInMiddle) { +void DisplayDevice::enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner, + bool showRenderRate, bool showInMiddle) { if (!enable) { mRefreshRateOverlay.reset(); return; @@ -428,11 +428,22 @@ void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinner, bool features |= RefreshRateOverlay::Features::ShowInMiddle; } + if (setByHwc) { + features |= RefreshRateOverlay::Features::SetByHwc; + } + const auto fpsRange = mRefreshRateSelector->getSupportedRefreshRateRange(); mRefreshRateOverlay = std::make_unique<RefreshRateOverlay>(fpsRange, features); mRefreshRateOverlay->setLayerStack(getLayerStack()); mRefreshRateOverlay->setViewport(getSize()); - mRefreshRateOverlay->changeRefreshRate(getActiveMode().modePtr->getFps(), getActiveMode().fps); + updateRefreshRateOverlayRate(getActiveMode().modePtr->getFps(), getActiveMode().fps); +} + +void DisplayDevice::updateRefreshRateOverlayRate(Fps displayFps, Fps renderFps, bool setByHwc) { + ATRACE_CALL(); + if (mRefreshRateOverlay && (!mRefreshRateOverlay->isSetByHwc() || setByHwc)) { + mRefreshRateOverlay->changeRefreshRate(displayFps, renderFps); + } } bool DisplayDevice::onKernelTimerChanged(std::optional<DisplayModeId> desiredModeId, @@ -441,7 +452,7 @@ bool DisplayDevice::onKernelTimerChanged(std::optional<DisplayModeId> desiredMod const auto newMode = mRefreshRateSelector->onKernelTimerChanged(desiredModeId, timerExpired); if (newMode) { - mRefreshRateOverlay->changeRefreshRate(newMode->modePtr->getFps(), newMode->fps); + updateRefreshRateOverlayRate(newMode->modePtr->getFps(), newMode->fps); return true; } } @@ -510,21 +521,21 @@ void DisplayDevice::clearDesiredActiveModeState() { mDesiredActiveModeChanged = false; } -void DisplayDevice::adjustRefreshRate(Fps leaderDisplayRefreshRate) { +void DisplayDevice::adjustRefreshRate(Fps pacesetterDisplayRefreshRate) { using fps_approx_ops::operator==; if (mRequestedRefreshRate == 0_Hz) { return; } using fps_approx_ops::operator>; - if (mRequestedRefreshRate > leaderDisplayRefreshRate) { - mAdjustedRefreshRate = leaderDisplayRefreshRate; + if (mRequestedRefreshRate > pacesetterDisplayRefreshRate) { + mAdjustedRefreshRate = pacesetterDisplayRefreshRate; return; } unsigned divisor = static_cast<unsigned>( - std::round(leaderDisplayRefreshRate.getValue() / mRequestedRefreshRate.getValue())); - mAdjustedRefreshRate = leaderDisplayRefreshRate / divisor; + std::round(pacesetterDisplayRefreshRate.getValue() / mRequestedRefreshRate.getValue())); + mAdjustedRefreshRate = pacesetterDisplayRefreshRate / divisor; } std::atomic<int32_t> DisplayDeviceState::sNextSequenceId(1); diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index 6b5d1d7f07..d9c3e1c825 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> @@ -236,8 +237,9 @@ public: } // Enables an overlay to be displayed with the current refresh rate - void enableRefreshRateOverlay(bool enable, bool showSpinner, bool showRenderRate, + void enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner, bool showRenderRate, bool showInMiddle) REQUIRES(kMainThreadContext); + void updateRefreshRateOverlayRate(Fps displayFps, Fps renderFps, bool setByHwc = false); bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; } bool onKernelTimerChanged(std::optional<DisplayModeId>, bool timerExpired); void animateRefreshRateOverlay(); @@ -246,9 +248,9 @@ public: Fps getAdjustedRefreshRate() const { return mAdjustedRefreshRate; } - // Round the requested refresh rate to match a divisor of the leader + // Round the requested refresh rate to match a divisor of the pacesetter // display's refresh rate. Only supported for virtual displays. - void adjustRefreshRate(Fps leaderDisplayRefreshRate); + void adjustRefreshRate(Fps pacesetterDisplayRefreshRate); // release HWC resources (if any) for removable displays void disconnect(); @@ -289,7 +291,7 @@ private: // for virtual displays to match this requested refresh rate. const Fps mRequestedRefreshRate; - // Adjusted refresh rate, rounded to match a divisor of the leader + // Adjusted refresh rate, rounded to match a divisor of the pacesetter // display's refresh rate. Only supported for virtual displays. Fps mAdjustedRefreshRate = 0_Hz; @@ -300,8 +302,8 @@ private: 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); }; diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp index 9470552e41..bd2680fbb5 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp @@ -208,6 +208,12 @@ public: return ::ndk::ScopedAStatus::ok(); } + ::ndk::ScopedAStatus onRefreshRateChangedDebug( + const RefreshRateChangedDebugData& refreshRateChangedDebugData) override { + mCallback.onRefreshRateChangedDebug(refreshRateChangedDebugData); + return ::ndk::ScopedAStatus::ok(); + } + private: HWC2::ComposerCallback& mCallback; }; @@ -1411,8 +1417,10 @@ Error AidlComposer::getHdrConversionCapabilities( return Error::NONE; } -Error AidlComposer::setHdrConversionStrategy(AidlHdrConversionStrategy hdrConversionStrategy) { - const auto status = mAidlComposerClient->setHdrConversionStrategy(hdrConversionStrategy); +Error AidlComposer::setHdrConversionStrategy(AidlHdrConversionStrategy hdrConversionStrategy, + Hdr* outPreferredHdrOutputType) { + const auto status = mAidlComposerClient->setHdrConversionStrategy(hdrConversionStrategy, + outPreferredHdrOutputType); if (!status.isOk()) { ALOGE("setHdrConversionStrategy failed %s", status.getDescription().c_str()); return static_cast<Error>(status.getServiceSpecificError()); @@ -1420,6 +1428,19 @@ Error AidlComposer::setHdrConversionStrategy(AidlHdrConversionStrategy hdrConver return Error::NONE; } +Error AidlComposer::setRefreshRateChangedCallbackDebugEnabled(Display displayId, bool enabled) { + const auto status = + mAidlComposerClient->setRefreshRateChangedCallbackDebugEnabled(translate<int64_t>( + displayId), + enabled); + if (!status.isOk()) { + ALOGE("setRefreshRateChangedCallbackDebugEnabled failed %s", + status.getDescription().c_str()); + return static_cast<Error>(status.getServiceSpecificError()); + } + return Error::NONE; +} + Error AidlComposer::getClientTargetProperty( Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) { Error error = Error::NONE; diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h index a5ddf7457f..8313c09e58 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h @@ -238,7 +238,8 @@ public: void onHotplugConnect(Display) override; void onHotplugDisconnect(Display) override; Error getHdrConversionCapabilities(std::vector<HdrConversionCapability>*) override; - Error setHdrConversionStrategy(HdrConversionStrategy) override; + Error setHdrConversionStrategy(HdrConversionStrategy, Hdr*) override; + Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) override; private: // Many public functions above simply write a command into the command diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h index 82b677e7d4..c65c572920 100644 --- a/services/surfaceflinger/DisplayHardware/ComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h @@ -293,7 +293,8 @@ public: virtual Error getHdrConversionCapabilities( std::vector<::aidl::android::hardware::graphics::common::HdrConversionCapability>*) = 0; virtual Error setHdrConversionStrategy( - ::aidl::android::hardware::graphics::common::HdrConversionStrategy) = 0; + ::aidl::android::hardware::graphics::common::HdrConversionStrategy, Hdr*) = 0; + virtual Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) = 0; }; } // namespace Hwc2 diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h index c1c7070a2d..23dd3e5016 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 @@ class Layer; 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 @@ struct ComposerCallback { 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 7dde6b4e44..28148ac1dc 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 @@ bool HWComposer::updatesDeviceProductInfoOnHotplugReconnect() const { 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 @@ bool HWComposer::onVsync(hal::HWDisplayId hwcDisplayId, nsecs_t timestamp) { // 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 @@ void HWComposer::setVsyncEnabled(PhysicalDisplayId displayId, hal::Vsync enabled 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, @@ -794,10 +796,29 @@ std::vector<HdrConversionCapability> HWComposer::getHdrConversionCapabilities() return mHdrConversionCapabilities; } -status_t HWComposer::setHdrConversionStrategy(HdrConversionStrategy hdrConversionStrategy) { - const auto error = mComposer->setHdrConversionStrategy(hdrConversionStrategy); +status_t HWComposer::setHdrConversionStrategy( + HdrConversionStrategy hdrConversionStrategy, + aidl::android::hardware::graphics::common::Hdr* outPreferredHdrOutputType) { + const auto error = + mComposer->setHdrConversionStrategy(hdrConversionStrategy, outPreferredHdrOutputType); if (error != hal::Error::NONE) { ALOGE("Error in setting HDR conversion strategy %s", to_string(error).c_str()); + return INVALID_OPERATION; + } + return NO_ERROR; +} + +status_t HWComposer::setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId displayId, + bool enabled) { + RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX); + const auto error = + mComposer->setRefreshRateChangedCallbackDebugEnabled(mDisplayData[displayId] + .hwcDisplay->getId(), + enabled); + if (error != hal::Error::NONE) { + ALOGE("Error in setting refresh refresh rate change callback debug enabled %s", + to_string(error).c_str()); + return INVALID_OPERATION; } return NO_ERROR; } diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h index f6155d24ad..7a3f41cc1c 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.h +++ b/services/surfaceflinger/DisplayHardware/HWComposer.h @@ -44,6 +44,7 @@ #include "Hal.h" #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h> +#include <aidl/android/hardware/graphics/common/Hdr.h> #include <aidl/android/hardware/graphics/common/HdrConversionCapability.h> #include <aidl/android/hardware/graphics/common/HdrConversionStrategy.h> #include <aidl/android/hardware/graphics/composer3/Capability.h> @@ -221,7 +222,10 @@ public: // 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; @@ -291,7 +295,9 @@ public: virtual std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability> getHdrConversionCapabilities() const = 0; virtual status_t setHdrConversionStrategy( - aidl::android::hardware::graphics::common::HdrConversionStrategy) = 0; + aidl::android::hardware::graphics::common::HdrConversionStrategy, + aidl::android::hardware::graphics::common::Hdr*) = 0; + virtual status_t setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId, bool enabled) = 0; }; static inline bool operator==(const android::HWComposer::DeviceRequestedChanges& lhs, @@ -402,7 +408,7 @@ public: 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; @@ -446,7 +452,9 @@ public: std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability> getHdrConversionCapabilities() const override; status_t setHdrConversionStrategy( - aidl::android::hardware::graphics::common::HdrConversionStrategy) override; + aidl::android::hardware::graphics::common::HdrConversionStrategy, + aidl::android::hardware::graphics::common::Hdr*) override; + status_t setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId, bool enabled) override; // for debugging ---------------------------------------------------------- void dump(std::string& out) const override; diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h index 537d5450ad..bf3089f040 100644 --- a/services/surfaceflinger/DisplayHardware/Hal.h +++ b/services/surfaceflinger/DisplayHardware/Hal.h @@ -113,6 +113,8 @@ inline std::string to_string( 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/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp index 6fdb2d79f3..23de4fa42e 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp @@ -1354,7 +1354,11 @@ Error HidlComposer::getHdrConversionCapabilities(std::vector<HdrConversionCapabi return Error::UNSUPPORTED; } -Error HidlComposer::setHdrConversionStrategy(HdrConversionStrategy) { +Error HidlComposer::setHdrConversionStrategy(HdrConversionStrategy, Hdr*) { + return Error::UNSUPPORTED; +} + +Error HidlComposer::setRefreshRateChangedCallbackDebugEnabled(Display, bool) { return Error::UNSUPPORTED; } diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h index 8280af271e..d04652bf21 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h @@ -346,8 +346,9 @@ public: Error getHdrConversionCapabilities( std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability>*) override; - Error setHdrConversionStrategy( - aidl::android::hardware::graphics::common::HdrConversionStrategy) override; + Error setHdrConversionStrategy(aidl::android::hardware::graphics::common::HdrConversionStrategy, + Hdr*) override; + Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) override; private: class CommandWriter : public CommandWriterBase { diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp index 6d492c0f4a..66598251cb 100644 --- a/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp +++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp @@ -24,9 +24,13 @@ namespace android::surfaceflinger { std::atomic<uint32_t> LayerCreationArgs::sSequence{1}; +uint32_t LayerCreationArgs::getInternalLayerId(uint32_t id) { + return id | INTERNAL_LAYER_PREFIX; +} + LayerCreationArgs::LayerCreationArgs(SurfaceFlinger* flinger, sp<Client> client, std::string name, uint32_t flags, gui::LayerMetadata metadataArg, - std::optional<uint32_t> id) + std::optional<uint32_t> id, bool internalLayer) : flinger(flinger), client(std::move(client)), name(std::move(name)), @@ -46,10 +50,15 @@ LayerCreationArgs::LayerCreationArgs(SurfaceFlinger* flinger, sp<Client> client, if (id) { sequence = *id; - sSequence = *id + 1; + if (internalLayer) { + sequence = getInternalLayerId(*id); + } else { + sSequence = *id + 1; + } } else { sequence = sSequence++; - if (sequence == UNASSIGNED_LAYER_ID) { + if (sequence >= INTERNAL_LAYER_PREFIX) { + sSequence = 1; ALOGW("Layer sequence id rolled over."); sequence = sSequence++; } diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h index 9d2aaab23d..2cd6b55bc4 100644 --- a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h +++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h @@ -25,6 +25,7 @@ #include <optional> constexpr uint32_t UNASSIGNED_LAYER_ID = std::numeric_limits<uint32_t>::max(); +constexpr uint32_t INTERNAL_LAYER_PREFIX = 1u << 31; namespace android { class SurfaceFlinger; @@ -35,10 +36,11 @@ namespace android::surfaceflinger { struct LayerCreationArgs { static std::atomic<uint32_t> sSequence; + static uint32_t getInternalLayerId(uint32_t id); LayerCreationArgs(android::SurfaceFlinger*, sp<android::Client>, std::string name, - uint32_t flags, gui::LayerMetadata, - std::optional<uint32_t> id = std::nullopt); + uint32_t flags, gui::LayerMetadata, std::optional<uint32_t> id = std::nullopt, + bool internalLayer = false); LayerCreationArgs(const LayerCreationArgs&); android::SurfaceFlinger* flinger; diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp index a4fac1cf5c..c30465fbd4 100644 --- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp @@ -19,6 +19,7 @@ #define LOG_TAG "LayerHierarchy" #include "LayerHierarchy.h" +#include "LayerLog.h" #include "SwapErase.h" namespace android::surfaceflinger::frontend { @@ -129,6 +130,14 @@ const RequestedLayerState* LayerHierarchy::getLayer() const { return mLayer; } +const LayerHierarchy* LayerHierarchy::getRelativeParent() const { + return mRelativeParent; +} + +const LayerHierarchy* LayerHierarchy::getParent() const { + return mParent; +} + std::string LayerHierarchy::getDebugStringShort() const { std::string debug = "LayerHierarchy{"; debug += ((mLayer) ? mLayer->getDebugString() : "root") + " "; @@ -259,6 +268,7 @@ void LayerHierarchyBuilder::onLayerAdded(RequestedLayerState* layer) { } void LayerHierarchyBuilder::onLayerDestroyed(RequestedLayerState* layer) { + LLOGV(layer->id, ""); LayerHierarchy* hierarchy = getHierarchyFromId(layer->id, /*crashOnFailure=*/false); if (!hierarchy) { // Layer was never part of the hierarchy if it was created and destroyed in the same @@ -408,29 +418,32 @@ std::string LayerHierarchy::TraversalPath::toString() const { if (id == UNASSIGNED_LAYER_ID) { return "TraversalPath{ROOT}"; } - std::string debugString = "TraversalPath{.id = " + std::to_string(id); + std::stringstream ss; + ss << "TraversalPath{.id = " << id; - if (!mirrorRootIds.empty()) { - debugString += ", .mirrorRootIds="; - for (auto rootId : mirrorRootIds) { - debugString += std::to_string(rootId) + ","; - } + if (mirrorRootId != UNASSIGNED_LAYER_ID) { + ss << ", .mirrorRootId=" << mirrorRootId; } if (!relativeRootIds.empty()) { - debugString += ", .relativeRootIds="; + ss << ", .relativeRootIds="; for (auto rootId : relativeRootIds) { - debugString += std::to_string(rootId) + ","; + ss << rootId << ","; } } if (hasRelZLoop()) { - debugString += ", hasRelZLoop=true invalidRelativeRootId="; - debugString += std::to_string(invalidRelativeRootId) + ","; + ss << "hasRelZLoop=true invalidRelativeRootId=" << invalidRelativeRootId << ","; } + ss << "}"; + return ss.str(); +} - debugString += "}"; - return debugString; +LayerHierarchy::TraversalPath LayerHierarchy::TraversalPath::getMirrorRoot() const { + LOG_ALWAYS_FATAL_IF(!isClone(), "Cannot get mirror root of a non cloned node"); + TraversalPath mirrorRootPath = *this; + mirrorRootPath.id = mirrorRootId; + return mirrorRootPath; } // Helper class to update a passed in TraversalPath when visiting a child. When the object goes out @@ -438,16 +451,13 @@ std::string LayerHierarchy::TraversalPath::toString() const { LayerHierarchy::ScopedAddToTraversalPath::ScopedAddToTraversalPath(TraversalPath& traversalPath, uint32_t layerId, LayerHierarchy::Variant variant) - : mTraversalPath(traversalPath), - mParentId(traversalPath.id), - mParentVariant(traversalPath.variant), - mParentDetached(traversalPath.detached) { + : mTraversalPath(traversalPath), mParentPath(traversalPath) { // Update the traversal id with the child layer id and variant. Parent id and variant are // stored to reset the id upon destruction. traversalPath.id = layerId; traversalPath.variant = variant; if (variant == LayerHierarchy::Variant::Mirror) { - traversalPath.mirrorRootIds.emplace_back(layerId); + traversalPath.mirrorRootId = mParentPath.id; } else if (variant == LayerHierarchy::Variant::Relative) { if (std::find(traversalPath.relativeRootIds.begin(), traversalPath.relativeRootIds.end(), layerId) != traversalPath.relativeRootIds.end()) { @@ -462,16 +472,16 @@ LayerHierarchy::ScopedAddToTraversalPath::~ScopedAddToTraversalPath() { // Reset the traversal id to its original parent state using the state that was saved in // the constructor. if (mTraversalPath.variant == LayerHierarchy::Variant::Mirror) { - mTraversalPath.mirrorRootIds.pop_back(); + mTraversalPath.mirrorRootId = mParentPath.mirrorRootId; } else if (mTraversalPath.variant == LayerHierarchy::Variant::Relative) { mTraversalPath.relativeRootIds.pop_back(); } if (mTraversalPath.invalidRelativeRootId == mTraversalPath.id) { mTraversalPath.invalidRelativeRootId = UNASSIGNED_LAYER_ID; } - mTraversalPath.id = mParentId; - mTraversalPath.variant = mParentVariant; - mTraversalPath.detached = mParentDetached; + mTraversalPath.id = mParentPath.id; + mTraversalPath.variant = mParentPath.variant; + mTraversalPath.detached = mParentPath.detached; } } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h index ca8d301879..3dd89badff 100644 --- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h @@ -50,12 +50,32 @@ public: ftl_last = Mirror, }; // Represents a unique path to a node. + // The layer hierarchy is represented as a graph. Each node can be visited by multiple parents. + // This allows us to represent mirroring in an efficient way. See the example below: + // root + // ├─ A {Traversal path id = 1} + // ├─ B {Traversal path id = 2} + // │ ├─ C {Traversal path id = 3} + // │ ├─ D {Traversal path id = 4} + // │ └─ E {Traversal path id = 5} + // ├─ F (Mirrors B) {Traversal path id = 6} + // └─ G (Mirrors F) {Traversal path id = 7} + // + // C, D and E can be traversed via B or via F then B or via G then F then B. + // Depending on how the node is reached, its properties such as geometry or visibility might be + // different. And we can uniquely identify the node by keeping track of the nodes leading up to + // it. But to be more efficient we only need to track the nodes id and the top mirror root path. + // So C for example, would have the following unique traversal paths: + // - {Traversal path id = 3} + // - {Traversal path id = 3, mirrorRootId = 6} + // - {Traversal path id = 3, mirrorRootId = 7} + struct TraversalPath { uint32_t id; LayerHierarchy::Variant variant; // Mirrored layers can have a different geometry than their parents so we need to track // the mirror roots in the traversal. - ftl::SmallVector<uint32_t, 5> mirrorRootIds; + uint32_t mirrorRootId = UNASSIGNED_LAYER_ID; // Relative layers can be visited twice, once by their parent and then once again by // their relative parent. We keep track of the roots here to detect any loops in the // hierarchy. If a relative root already exists in the list while building the @@ -73,10 +93,11 @@ public: // Returns true if the node or its parents are not Detached. bool isAttached() const { return !detached; } // Returns true if the node is a clone. - bool isClone() const { return !mirrorRootIds.empty(); } + bool isClone() const { return mirrorRootId != UNASSIGNED_LAYER_ID; } + TraversalPath getMirrorRoot() const; bool operator==(const TraversalPath& other) const { - return id == other.id && mirrorRootIds == other.mirrorRootIds; + return id == other.id && mirrorRootId == other.mirrorRootId; } std::string toString() const; @@ -93,9 +114,7 @@ public: private: TraversalPath& mTraversalPath; - uint32_t mParentId; - LayerHierarchy::Variant mParentVariant; - bool mParentDetached; + TraversalPath mParentPath; }; LayerHierarchy(RequestedLayerState* layer); @@ -109,16 +128,24 @@ public: // Traverse the hierarchy and visit all child variants. void traverse(const Visitor& visitor) const { TraversalPath root = TraversalPath::ROOT; + if (mLayer) { + root.id = mLayer->id; + } traverse(visitor, root); } // Traverse the hierarchy in z-order, skipping children that have relative parents. void traverseInZOrder(const Visitor& visitor) const { TraversalPath root = TraversalPath::ROOT; + if (mLayer) { + root.id = mLayer->id; + } traverseInZOrder(visitor, root); } const RequestedLayerState* getLayer() const; + const LayerHierarchy* getRelativeParent() const; + const LayerHierarchy* getParent() const; std::string getDebugString(const char* prefix = "") const; std::string getDebugStringShort() const; // Traverse the hierarchy and return true if loops are found. The outInvalidRelativeRoot diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp index 547a852b9e..33cc42951f 100644 --- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp +++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp @@ -22,6 +22,7 @@ #include "LayerLifecycleManager.h" #include "Layer.h" // temporarily needed for LayerHandle #include "LayerHandle.h" +#include "LayerLog.h" #include "SwapErase.h" namespace android::surfaceflinger::frontend { @@ -66,6 +67,7 @@ void LayerLifecycleManager::addLayers(std::vector<std::unique_ptr<RequestedLayer if (layer.isRoot()) { updateDisplayMirrorLayers(layer); } + LLOGV(layer.id, "%s", layer.getDebugString().c_str()); mLayers.emplace_back(std::move(newLayer)); } } @@ -79,6 +81,7 @@ void LayerLifecycleManager::onHandlesDestroyed(const std::vector<uint32_t>& dest continue; } RequestedLayerState& layer = it->second.owner; + LLOGV(layer.id, "%s", layer.getDebugString().c_str()); layer.handleAlive = false; if (!layer.canBeDestroyed()) { continue; @@ -146,7 +149,7 @@ void LayerLifecycleManager::onHandlesDestroyed(const std::vector<uint32_t>& dest while (it != mLayers.end()) { RequestedLayerState* layer = it->get(); if (layer->changes.test(RequestedLayerState::Changes::Destroyed)) { - ALOGV("%s destroyed layer %s", __func__, layer->getDebugStringShort().c_str()); + LLOGV(layer->id, "destroyed %s", layer->getDebugStringShort().c_str()); std::iter_swap(it, mLayers.end() - 1); mDestroyedLayers.emplace_back(std::move(mLayers.back())); if (it == mLayers.end() - 1) { @@ -184,41 +187,48 @@ void LayerLifecycleManager::applyTransactions(const std::vector<TransactionState continue; } + if (transaction.flags & ISurfaceComposer::eAnimation) { + layer->changes |= RequestedLayerState::Changes::Animation; + } + uint32_t oldParentId = layer->parentId; uint32_t oldRelativeParentId = layer->relativeParentId; uint32_t oldTouchCropId = layer->touchCropId; layer->merge(resolvedComposerState); if (layer->what & layer_state_t::eBackgroundColorChanged) { - if (layer->bgColorLayerId == UNASSIGNED_LAYER_ID && layer->bgColorAlpha != 0) { + if (layer->bgColorLayerId == UNASSIGNED_LAYER_ID && layer->bgColor.a != 0) { LayerCreationArgs backgroundLayerArgs{nullptr, nullptr, layer->name + "BackgroundColorLayer", ISurfaceComposerClient::eFXSurfaceEffect, - {}}; + {}, + layer->id, + /*internalLayer=*/true}; std::vector<std::unique_ptr<RequestedLayerState>> newLayers; newLayers.emplace_back( std::make_unique<RequestedLayerState>(backgroundLayerArgs)); RequestedLayerState* backgroundLayer = newLayers.back().get(); + backgroundLayer->bgColorLayer = true; backgroundLayer->handleAlive = false; backgroundLayer->parentId = layer->id; backgroundLayer->z = std::numeric_limits<int32_t>::min(); - backgroundLayer->color.rgb = layer->color.rgb; - backgroundLayer->color.a = layer->bgColorAlpha; + backgroundLayer->color = layer->bgColor; backgroundLayer->dataspace = layer->bgColorDataspace; - layer->bgColorLayerId = backgroundLayer->id; addLayers({std::move(newLayers)}); - } else if (layer->bgColorLayerId != UNASSIGNED_LAYER_ID && - layer->bgColorAlpha == 0) { + } else if (layer->bgColorLayerId != UNASSIGNED_LAYER_ID && layer->bgColor.a == 0) { RequestedLayerState* bgColorLayer = getLayerFromId(layer->bgColorLayerId); - bgColorLayer->parentId = UNASSIGNED_LAYER_ID; - onHandlesDestroyed({layer->bgColorLayerId}); + layer->bgColorLayerId = UNASSIGNED_LAYER_ID; + bgColorLayer->parentId = unlinkLayer(bgColorLayer->parentId, bgColorLayer->id); + onHandlesDestroyed({bgColorLayer->id}); } else if (layer->bgColorLayerId != UNASSIGNED_LAYER_ID) { RequestedLayerState* bgColorLayer = getLayerFromId(layer->bgColorLayerId); - bgColorLayer->color.rgb = layer->color.rgb; - bgColorLayer->color.a = layer->bgColorAlpha; + bgColorLayer->color = layer->bgColor; bgColorLayer->dataspace = layer->bgColorDataspace; + bgColorLayer->what |= layer_state_t::eColorChanged | + layer_state_t::eDataspaceChanged | layer_state_t::eAlphaChanged; + bgColorLayer->changes |= RequestedLayerState::Changes::Content; mGlobalChanges |= RequestedLayerState::Changes::Content; } } @@ -254,8 +264,7 @@ void LayerLifecycleManager::commitChanges() { listener->onLayerAdded(*layer); } } - layer->what = 0; - layer->changes.clear(); + layer->clearChanges(); } for (auto& destroyedLayer : mDestroyedLayers) { diff --git a/services/surfaceflinger/FrontEnd/LayerLog.h b/services/surfaceflinger/FrontEnd/LayerLog.h index 47e1e30c48..4943483e62 100644 --- a/services/surfaceflinger/FrontEnd/LayerLog.h +++ b/services/surfaceflinger/FrontEnd/LayerLog.h @@ -25,3 +25,5 @@ #else #define LLOGV(LAYER_ID, x, ...) ALOGV("[%d] %s " x, (LAYER_ID), __func__, ##__VA_ARGS__); #endif + +#define LLOGD(LAYER_ID, x, ...) ALOGD("[%d] %s " x, (LAYER_ID), __func__, ##__VA_ARGS__); diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp index dbb7fbf462..8a450933f0 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp @@ -27,14 +27,22 @@ using namespace ftl::flag_operators; LayerSnapshot::LayerSnapshot(const RequestedLayerState& state, const LayerHierarchy::TraversalPath& path) : path(path) { + static uint32_t sUniqueSequenceId = 0; + // Provide a unique id for clones otherwise keeping using the sequence id. + // The seq id can still be useful for debugging if its available. + uniqueSequence = (path.isClone()) ? sUniqueSequenceId++ : state.id; sequence = static_cast<int32_t>(state.id); name = state.name; textureName = state.textureName; premultipliedAlpha = state.premultipliedAlpha; inputInfo.name = state.name; - inputInfo.id = static_cast<int32_t>(state.id); + inputInfo.id = static_cast<int32_t>(uniqueSequence); inputInfo.ownerUid = static_cast<int32_t>(state.ownerUid); inputInfo.ownerPid = state.ownerPid; + changes = RequestedLayerState::Changes::Created; + mirrorRootPath = path.variant == LayerHierarchy::Variant::Mirror + ? path + : LayerHierarchy::TraversalPath::ROOT; } // As documented in libhardware header, formats in the range @@ -139,7 +147,8 @@ std::string LayerSnapshot::getIsVisibleReason() const { // visible std::stringstream reason; if (sidebandStream != nullptr) reason << " sidebandStream"; - if (externalTexture != nullptr) reason << " buffer"; + if (externalTexture != nullptr) + reason << " buffer:" << externalTexture->getId() << " frame:" << frameNumber; if (fillsColor() || color.a > 0.0f) reason << " color{" << color << "}"; if (drawShadows()) reason << " shadowSettings.length=" << shadowSettings.length; if (backgroundBlurRadius > 0) reason << " backgroundBlurRadius=" << backgroundBlurRadius; @@ -164,7 +173,8 @@ bool LayerSnapshot::hasInputInfo() const { std::string LayerSnapshot::getDebugString() const { std::stringstream debug; debug << "Snapshot{" << path.toString() << name << " isVisible=" << isVisible << " {" - << getIsVisibleReason() << "} changes=" << changes.string() << "}"; + << getIsVisibleReason() << "} changes=" << changes.string() + << " layerStack=" << outputFilter.layerStack.id << "}"; return debug.str(); } diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h index 5d74203af8..6fb2f57d96 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h @@ -57,6 +57,12 @@ struct LayerSnapshot : public compositionengine::LayerFECompositionState { bool isHiddenByPolicyFromParent = false; bool isHiddenByPolicyFromRelativeParent = false; ftl::Flags<RequestedLayerState::Changes> changes; + // Some consumers of this snapshot (input, layer traces) rely on each snapshot to be unique. + // For mirrored layers, snapshots will have the same sequence so this unique id provides + // an alternative identifier when needed. + uint32_t uniqueSequence; + // Layer id used to create this snapshot. Multiple snapshots will have the same sequence if they + // generated from the same layer, for example when mirroring. int32_t sequence; std::string name; uint32_t textureName; @@ -84,6 +90,9 @@ struct LayerSnapshot : public compositionengine::LayerFECompositionState { scheduler::LayerInfo::FrameRate frameRate; ui::Transform::RotationFlags fixedTransformHint; bool handleSkipScreenshotFlag = false; + int32_t frameRateSelectionPriority; + LayerHierarchy::TraversalPath mirrorRootPath; + bool unreachable = true; ChildState childState; static bool isOpaqueFormat(PixelFormat format); diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index 6490476396..a16de1bcfb 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -259,8 +259,8 @@ void updateSurfaceDamage(const RequestedLayerState& requested, bool hasReadyFram } } -void updateVisibility(LayerSnapshot& snapshot) { - snapshot.isVisible = snapshot.getIsVisible(); +void updateVisibility(LayerSnapshot& snapshot, bool visible) { + snapshot.isVisible = visible; // TODO(b/238781169) we are ignoring this compat for now, since we will have // to remove any optimization based on visibility. @@ -273,9 +273,9 @@ void updateVisibility(LayerSnapshot& snapshot) { // We are just using these layers for occlusion detection in // InputDispatcher, and obviously if they aren't visible they can't occlude // anything. - const bool visible = - (snapshot.inputInfo.token != nullptr) ? snapshot.canReceiveInput() : snapshot.isVisible; - snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE, !visible); + const bool visibleForInput = + snapshot.hasInputInfo() ? snapshot.canReceiveInput() : snapshot.isVisible; + snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE, !visibleForInput); } bool needsInputInfo(const LayerSnapshot& snapshot, const RequestedLayerState& requested) { @@ -296,6 +296,26 @@ bool needsInputInfo(const LayerSnapshot& snapshot, const RequestedLayerState& re gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL); } +void updateMetadata(LayerSnapshot& snapshot, const RequestedLayerState& requested, + const LayerSnapshotBuilder::Args& args) { + snapshot.metadata.clear(); + for (const auto& [key, mandatory] : args.supportedLayerGenericMetadata) { + auto compatIter = args.genericLayerMetadataKeyMap.find(key); + if (compatIter == std::end(args.genericLayerMetadataKeyMap)) { + continue; + } + const uint32_t id = compatIter->second; + auto it = requested.metadata.mMap.find(id); + if (it == std::end(requested.metadata.mMap)) { + continue; + } + + snapshot.metadata.emplace(key, + compositionengine::GenericLayerMetadataEntry{mandatory, + it->second}); + } +} + void clearChanges(LayerSnapshot& snapshot) { snapshot.changes.clear(); snapshot.contentDirty = false; @@ -308,6 +328,7 @@ void clearChanges(LayerSnapshot& snapshot) { LayerSnapshot LayerSnapshotBuilder::getRootSnapshot() { LayerSnapshot snapshot; + snapshot.path = LayerHierarchy::TraversalPath::ROOT; snapshot.changes = ftl::Flags<RequestedLayerState::Changes>(); snapshot.isHiddenByPolicyFromParent = false; snapshot.isHiddenByPolicyFromRelativeParent = false; @@ -337,21 +358,17 @@ LayerSnapshot LayerSnapshotBuilder::getRootSnapshot() { LayerSnapshotBuilder::LayerSnapshotBuilder() : mRootSnapshot(getRootSnapshot()) {} LayerSnapshotBuilder::LayerSnapshotBuilder(Args args) : LayerSnapshotBuilder() { - args.forceUpdate = true; + args.forceUpdate = ForceUpdateFlags::ALL; updateSnapshots(args); } bool LayerSnapshotBuilder::tryFastUpdate(const Args& args) { - if (args.forceUpdate || args.displayChanges) { + if (args.forceUpdate != ForceUpdateFlags::NONE || args.displayChanges) { // force update requested, or we have display changes, so skip the fast path return false; } if (args.layerLifecycleManager.getGlobalChanges().get() == 0) { - // there are no changes, so just clear the change flags from before. - for (auto& snapshot : mSnapshots) { - clearChanges(*snapshot); - } return true; } @@ -376,14 +393,12 @@ bool LayerSnapshotBuilder::tryFastUpdate(const Args& args) { // Walk through the snapshots, clearing previous change flags and updating the snapshots // if needed. for (auto& snapshot : mSnapshots) { - clearChanges(*snapshot); auto it = layersWithChanges.find(snapshot->path.id); if (it != layersWithChanges.end()) { ALOGV("%s fast path snapshot changes = %s", __func__, mRootSnapshot.changes.string().c_str()); LayerHierarchy::TraversalPath root = LayerHierarchy::TraversalPath::ROOT; - updateSnapshot(*snapshot, args, *it->second, mRootSnapshot, root, - /*newSnapshot=*/false); + updateSnapshot(*snapshot, args, *it->second, mRootSnapshot, root); } } return true; @@ -391,37 +406,47 @@ bool LayerSnapshotBuilder::tryFastUpdate(const Args& args) { 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 == ForceUpdateFlags::ALL || args.displayChanges) { mRootSnapshot.geomLayerBounds = getMaxDisplayBounds(args.displays); } if (args.displayChanges) { mRootSnapshot.changes = RequestedLayerState::Changes::AffectsChildren | RequestedLayerState::Changes::Geometry; } + if (args.forceUpdate == ForceUpdateFlags::HIERARCHY) { + mRootSnapshot.changes |= + RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Visibility; + } LayerHierarchy::TraversalPath root = LayerHierarchy::TraversalPath::ROOT; - for (auto& [childHierarchy, variant] : args.root.mChildren) { - LayerHierarchy::ScopedAddToTraversalPath addChildToPath(root, - childHierarchy->getLayer()->id, - variant); - updateSnapshotsInHierarchy(args, *childHierarchy, root, mRootSnapshot); + if (args.root.getLayer()) { + // The hierarchy can have a root layer when used for screenshots otherwise, it will have + // multiple children. + LayerHierarchy::ScopedAddToTraversalPath addChildToPath(root, args.root.getLayer()->id, + LayerHierarchy::Variant::Attached); + updateSnapshotsInHierarchy(args, args.root, root, mRootSnapshot); + } else { + for (auto& [childHierarchy, variant] : args.root.mChildren) { + LayerHierarchy::ScopedAddToTraversalPath addChildToPath(root, + childHierarchy->getLayer()->id, + variant); + updateSnapshotsInHierarchy(args, *childHierarchy, root, mRootSnapshot); + } } - sortSnapshotsByZ(args); + const bool hasUnreachableSnapshots = sortSnapshotsByZ(args); clearChanges(mRootSnapshot); // Destroy unreachable snapshots - if (args.layerLifecycleManager.getDestroyedLayers().empty()) { + if (!hasUnreachableSnapshots) { return; } - std::unordered_set<uint32_t> destroyedLayerIds; - for (auto& destroyedLayer : args.layerLifecycleManager.getDestroyedLayers()) { - destroyedLayerIds.emplace(destroyedLayer->id); - } auto it = mSnapshots.begin(); while (it < mSnapshots.end()) { auto& traversalPath = it->get()->path; - if (destroyedLayerIds.find(traversalPath.id) == destroyedLayerIds.end()) { + if (!it->get()->unreachable) { it++; continue; } @@ -434,6 +459,10 @@ void LayerSnapshotBuilder::updateSnapshots(const Args& args) { } void LayerSnapshotBuilder::update(const Args& args) { + for (auto& snapshot : mSnapshots) { + clearChanges(*snapshot); + } + if (tryFastUpdate(args)) { return; } @@ -447,8 +476,9 @@ const LayerSnapshot& LayerSnapshotBuilder::updateSnapshotsInHierarchy( LayerSnapshot* snapshot = getSnapshot(traversalPath); const bool newSnapshot = snapshot == nullptr; if (newSnapshot) { - snapshot = createSnapshot(traversalPath, *layer); + snapshot = createSnapshot(traversalPath, *layer, parentSnapshot); } + scheduler::LayerInfo::FrameRate oldFrameRate = snapshot->frameRate; if (traversalPath.isRelative()) { bool parentIsRelative = traversalPath.variant == LayerHierarchy::Variant::Relative; updateRelativeState(*snapshot, parentSnapshot, parentIsRelative, args); @@ -456,7 +486,7 @@ const LayerSnapshot& LayerSnapshotBuilder::updateSnapshotsInHierarchy( if (traversalPath.isAttached()) { resetRelativeState(*snapshot); } - updateSnapshot(*snapshot, args, *layer, parentSnapshot, traversalPath, newSnapshot); + updateSnapshot(*snapshot, args, *layer, parentSnapshot, traversalPath); } for (auto& [childHierarchy, variant] : hierarchy.mChildren) { @@ -467,6 +497,10 @@ const LayerSnapshot& LayerSnapshotBuilder::updateSnapshotsInHierarchy( updateSnapshotsInHierarchy(args, *childHierarchy, traversalPath, *snapshot); updateChildState(*snapshot, childSnapshot, args); } + + if (oldFrameRate == snapshot->frameRate) { + snapshot->changes.clear(RequestedLayerState::Changes::FrameRate); + } return *snapshot; } @@ -483,27 +517,34 @@ LayerSnapshot* LayerSnapshotBuilder::getSnapshot(const LayerHierarchy::Traversal return it == mIdToSnapshot.end() ? nullptr : it->second; } -LayerSnapshot* LayerSnapshotBuilder::createSnapshot(const LayerHierarchy::TraversalPath& id, - const RequestedLayerState& layer) { - mSnapshots.emplace_back(std::make_unique<LayerSnapshot>(layer, id)); +LayerSnapshot* LayerSnapshotBuilder::createSnapshot(const LayerHierarchy::TraversalPath& path, + const RequestedLayerState& layer, + const LayerSnapshot& parentSnapshot) { + mSnapshots.emplace_back(std::make_unique<LayerSnapshot>(layer, path)); LayerSnapshot* snapshot = mSnapshots.back().get(); snapshot->globalZ = static_cast<size_t>(mSnapshots.size()) - 1; - mIdToSnapshot[id] = snapshot; + if (path.isClone() && path.variant != LayerHierarchy::Variant::Mirror) { + snapshot->mirrorRootPath = parentSnapshot.mirrorRootPath; + } + mIdToSnapshot[path] = snapshot; return snapshot; } -void LayerSnapshotBuilder::sortSnapshotsByZ(const Args& args) { - if (!mResortSnapshots && !args.forceUpdate && +bool LayerSnapshotBuilder::sortSnapshotsByZ(const Args& args) { + if (!mResortSnapshots && args.forceUpdate == ForceUpdateFlags::NONE && !args.layerLifecycleManager.getGlobalChanges().any( RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Visibility)) { // We are not force updating and there are no hierarchy or visibility changes. Avoid sorting // the snapshots. - return; + return false; } - mResortSnapshots = false; + for (auto& snapshot : mSnapshots) { + snapshot->unreachable = true; + } + size_t globalZ = 0; args.root.traverseInZOrder( [this, &globalZ](const LayerHierarchy&, @@ -513,13 +554,9 @@ void LayerSnapshotBuilder::sortSnapshotsByZ(const Args& args) { return false; } - if (snapshot->isHiddenByPolicy() && - !snapshot->changes.test(RequestedLayerState::Changes::Visibility)) { - return false; - } - + snapshot->unreachable = false; if (snapshot->getIsVisible() || snapshot->hasInputInfo()) { - updateVisibility(*snapshot); + updateVisibility(*snapshot, snapshot->getIsVisible()); size_t oldZ = snapshot->globalZ; size_t newZ = globalZ++; snapshot->globalZ = newZ; @@ -535,11 +572,17 @@ void LayerSnapshotBuilder::sortSnapshotsByZ(const Args& args) { return true; }); mNumInterestingSnapshots = (int)globalZ; + bool hasUnreachableSnapshots = false; while (globalZ < mSnapshots.size()) { mSnapshots[globalZ]->globalZ = globalZ; - updateVisibility(*mSnapshots[globalZ]); + /* mark unreachable snapshots as explicitly invisible */ + updateVisibility(*mSnapshots[globalZ], false); + if (mSnapshots[globalZ]->unreachable) { + hasUnreachableSnapshots = true; + } globalZ++; } + return hasUnreachableSnapshots; } void LayerSnapshotBuilder::updateRelativeState(LayerSnapshot& snapshot, @@ -566,7 +609,8 @@ void LayerSnapshotBuilder::updateChildState(LayerSnapshot& snapshot, if (snapshot.childState.hasValidFrameRate) { return; } - if (args.forceUpdate || childSnapshot.changes.test(RequestedLayerState::Changes::FrameRate)) { + if (args.forceUpdate == ForceUpdateFlags::ALL || + childSnapshot.changes.test(RequestedLayerState::Changes::FrameRate)) { // We return whether this layer ot its children has a vote. We ignore ExactOrMultiple votes // for the same reason we are allowing touch boost for those layers. See // RefreshRateSelector::rankFrameRates for details. @@ -609,33 +653,35 @@ uint32_t getDisplayRotationFlags( void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& args, const RequestedLayerState& requested, const LayerSnapshot& parentSnapshot, - const LayerHierarchy::TraversalPath& path, - bool newSnapshot) { + const LayerHierarchy::TraversalPath& path) { // Always update flags and visibility ftl::Flags<RequestedLayerState::Changes> parentChanges = parentSnapshot.changes & (RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Geometry | RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Metadata | - RequestedLayerState::Changes::AffectsChildren); - snapshot.changes = parentChanges | requested.changes; + RequestedLayerState::Changes::AffectsChildren | + RequestedLayerState::Changes::FrameRate); + 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 = - (snapshot.contentDirty || requested.autoRefresh) && (requested.externalTexture); - // TODO(b/238781169) how is this used? ag/15523870 - snapshot.sidebandStreamHasFrame = false; + snapshot.hasReadyFrame = requested.hasReadyFrame(); + snapshot.sidebandStreamHasFrame = requested.hasSidebandStreamFrame(); updateSurfaceDamage(requested, snapshot.hasReadyFrame, args.forceFullDamage, snapshot.surfaceDamage); + snapshot.outputFilter.layerStack = parentSnapshot.path == LayerHierarchy::TraversalPath::ROOT + ? requested.layerStack + : parentSnapshot.outputFilter.layerStack; - const bool forceUpdate = newSnapshot || args.forceUpdate || - snapshot.changes.any(RequestedLayerState::Changes::Visibility | - RequestedLayerState::Changes::Created); uint32_t displayRotationFlags = getDisplayRotationFlags(args.displays, snapshot.outputFilter.layerStack); + const bool forceUpdate = args.forceUpdate == ForceUpdateFlags::ALL || + snapshot.changes.any(RequestedLayerState::Changes::Visibility | + RequestedLayerState::Changes::Created); // always update the buffer regardless of visibility - if (forceUpdate || requested.what & layer_state_t::BUFFER_CHANGES) { + if (forceUpdate || requested.what & layer_state_t::BUFFER_CHANGES || args.displayChanges) { snapshot.acquireFence = (requested.externalTexture && requested.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged)) @@ -665,7 +711,8 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a snapshot.desiredSdrHdrRatio = requested.desiredSdrHdrRatio; } - if (snapshot.isHiddenByPolicyFromParent && !newSnapshot) { + if (snapshot.isHiddenByPolicyFromParent && + !snapshot.changes.test(RequestedLayerState::Changes::Created)) { if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Geometry | @@ -682,9 +729,6 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a snapshot.isSecure = parentSnapshot.isSecure || (requested.flags & layer_state_t::eLayerSecure); snapshot.isTrustedOverlay = parentSnapshot.isTrustedOverlay || requested.isTrustedOverlay; - snapshot.outputFilter.layerStack = requested.parentId != UNASSIGNED_LAYER_ID - ? parentSnapshot.outputFilter.layerStack - : requested.layerStack; snapshot.outputFilter.toInternalDisplay = parentSnapshot.outputFilter.toInternalDisplay || (requested.flags & layer_state_t::eLayerSkipScreenshot); snapshot.stretchEffect = (requested.stretchEffect.hasEffect()) @@ -700,11 +744,6 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE) ? requested.gameMode : parentSnapshot.gameMode; - snapshot.frameRate = (requested.requestedFrameRate.rate.isValid() || - (requested.requestedFrameRate.type == - scheduler::LayerInfo::FrameRateCompatibility::NoVote)) - ? requested.requestedFrameRate - : parentSnapshot.frameRate; snapshot.fixedTransformHint = requested.fixedTransformHint != ui::Transform::ROT_INVALID ? requested.fixedTransformHint : parentSnapshot.fixedTransformHint; @@ -714,19 +753,39 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a (requested.layerStackToMirror != ui::INVALID_LAYER_STACK); } + if (forceUpdate || + snapshot.changes.any(RequestedLayerState::Changes::FrameRate | + RequestedLayerState::Changes::Hierarchy)) { + snapshot.frameRate = (requested.requestedFrameRate.rate.isValid() || + (requested.requestedFrameRate.type == + scheduler::LayerInfo::FrameRateCompatibility::NoVote)) + ? requested.requestedFrameRate + : parentSnapshot.frameRate; + } + + if (forceUpdate || requested.what & layer_state_t::eMetadataChanged) { + updateMetadata(snapshot, requested, args); + } + if (forceUpdate || requested.changes.get() != 0) { snapshot.compositionType = requested.getCompositionType(); snapshot.dimmingEnabled = requested.dimmingEnabled; snapshot.layerOpaqueFlagSet = (requested.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque; + snapshot.cachingHint = requested.cachingHint; + snapshot.frameRateSelectionPriority = requested.frameRateSelectionPriority; } if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Content)) { snapshot.color.rgb = requested.getColor().rgb; snapshot.isColorspaceAgnostic = requested.colorSpaceAgnostic; - snapshot.backgroundBlurRadius = - args.supportsBlur ? static_cast<int>(requested.backgroundBlurRadius) : 0; + snapshot.backgroundBlurRadius = args.supportsBlur + ? static_cast<int>(parentSnapshot.color.a * (float)requested.backgroundBlurRadius) + : 0; snapshot.blurRegions = requested.blurRegions; + for (auto& region : snapshot.blurRegions) { + region.alpha = region.alpha * snapshot.color.a; + } snapshot.hdrMetadata = requested.hdrMetadata; } @@ -752,18 +811,16 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a } snapshot.forceClientComposition = snapshot.isHdrY410 || snapshot.shadowSettings.length > 0 || requested.blurRegions.size() > 0 || snapshot.stretchEffect.hasEffect(); - snapshot.isOpaque = snapshot.isContentOpaque() && !snapshot.roundedCorner.hasRoundedCorners() && + snapshot.contentOpaque = snapshot.isContentOpaque(); + snapshot.isOpaque = snapshot.contentOpaque && !snapshot.roundedCorner.hasRoundedCorners() && snapshot.color.a == 1.f; snapshot.blendMode = getBlendMode(snapshot, requested); - // TODO(b/238781169) pass this from flinger - // snapshot.fps; - // snapshot.metadata; LLOGV(snapshot.sequence, - "%supdated [%d]%s changes parent:%s global:%s local:%s requested:%s %s from parent %s", - args.forceUpdate ? "Force " : "", requested.id, requested.name.c_str(), - parentSnapshot.changes.string().c_str(), snapshot.changes.string().c_str(), - requested.changes.string().c_str(), std::to_string(requested.what).c_str(), - snapshot.getDebugString().c_str(), parentSnapshot.getDebugString().c_str()); + "%supdated %s changes:%s parent:%s requested:%s requested:%s from parent %s", + args.forceUpdate == ForceUpdateFlags::ALL ? "Force " : "", + snapshot.getDebugString().c_str(), snapshot.changes.string().c_str(), + parentSnapshot.changes.string().c_str(), requested.changes.string().c_str(), + std::to_string(requested.what).c_str(), parentSnapshot.getDebugString().c_str()); } void LayerSnapshotBuilder::updateRoundedCorner(LayerSnapshot& snapshot, @@ -898,9 +955,14 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, snapshot.inputInfo = *requested.windowInfoHandle->getInfo(); } else { snapshot.inputInfo = {}; + // b/271132344 revisit this and see if we can always use the layers uid/pid + snapshot.inputInfo.name = requested.name; + snapshot.inputInfo.ownerUid = static_cast<int32_t>(requested.ownerUid); + snapshot.inputInfo.ownerPid = requested.ownerPid; } - snapshot.inputInfo.displayId = static_cast<int32_t>(snapshot.outputFilter.layerStack.id); + snapshot.inputInfo.id = static_cast<int32_t>(snapshot.uniqueSequence); + snapshot.inputInfo.displayId = static_cast<int32_t>(snapshot.outputFilter.layerStack.id); if (!needsInputInfo(snapshot, requested)) { return; } @@ -923,7 +985,9 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, } snapshot.inputInfo.alpha = snapshot.color.a; - snapshot.inputInfo.touchOcclusionMode = parentSnapshot.inputInfo.touchOcclusionMode; + snapshot.inputInfo.touchOcclusionMode = requested.hasInputInfo() + ? requested.windowInfoHandle->getInfo()->touchOcclusionMode + : parentSnapshot.inputInfo.touchOcclusionMode; if (requested.dropInputMode == gui::DropInputMode::ALL || parentSnapshot.dropInputMode == gui::DropInputMode::ALL) { snapshot.dropInputMode = gui::DropInputMode::ALL; @@ -962,7 +1026,7 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, // touches from going outside the cloned area. if (path.isClone()) { snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::CLONE; - auto clonedRootSnapshot = getSnapshot(path.mirrorRootIds.back()); + auto clonedRootSnapshot = getSnapshot(snapshot.mirrorRootPath); if (clonedRootSnapshot) { const Rect rect = displayInfo.transform.transform(Rect{clonedRootSnapshot->transformedBounds}); @@ -983,6 +1047,20 @@ void LayerSnapshotBuilder::forEachVisibleSnapshot(const ConstVisitor& visitor) c } } +// 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 abb7e668c3..3997a0ad83 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h @@ -35,10 +35,15 @@ namespace android::surfaceflinger::frontend { // snapshots when there are only buffer updates. class LayerSnapshotBuilder { public: + enum class ForceUpdateFlags { + NONE, + ALL, + HIERARCHY, + }; struct Args { - const LayerHierarchy& root; + LayerHierarchy root; const LayerLifecycleManager& layerLifecycleManager; - bool forceUpdate = false; + ForceUpdateFlags forceUpdate = ForceUpdateFlags::NONE; bool includeMetadata = false; const display::DisplayMap<ui::LayerStack, frontend::DisplayInfo>& displays; // Set to true if there were display changes since last update. @@ -46,6 +51,10 @@ public: const renderengine::ShadowSettings& globalShadowSettings; bool supportsBlur = true; bool forceFullDamage = false; + std::optional<FloatRect> parentCrop = std::nullopt; + std::unordered_set<uint32_t> excludeLayerIds; + const std::unordered_map<std::string, bool>& supportedLayerGenericMetadata; + const std::unordered_map<std::string, uint32_t>& genericLayerMetadataKeyMap; }; LayerSnapshotBuilder(); @@ -65,6 +74,9 @@ public: // 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); @@ -87,8 +99,7 @@ private: LayerHierarchy::TraversalPath& traversalPath, const LayerSnapshot& parentSnapshot); void updateSnapshot(LayerSnapshot&, const Args&, const RequestedLayerState&, - const LayerSnapshot& parentSnapshot, const LayerHierarchy::TraversalPath&, - bool newSnapshot); + const LayerSnapshot& parentSnapshot, const LayerHierarchy::TraversalPath&); static void updateRelativeState(LayerSnapshot& snapshot, const LayerSnapshot& parentSnapshot, bool parentIsRelative, const Args& args); static void resetRelativeState(LayerSnapshot& snapshot); @@ -101,17 +112,19 @@ private: void updateInput(LayerSnapshot& snapshot, const RequestedLayerState& requested, const LayerSnapshot& parentSnapshot, const LayerHierarchy::TraversalPath& path, const Args& args); - void sortSnapshotsByZ(const Args& args); + // Return true if there are unreachable snapshots + bool sortSnapshotsByZ(const Args& args); LayerSnapshot* createSnapshot(const LayerHierarchy::TraversalPath& id, - const RequestedLayerState& layer); + const RequestedLayerState& layer, + const LayerSnapshot& parentSnapshot); void updateChildState(LayerSnapshot& snapshot, const LayerSnapshot& childSnapshot, const Args& args); struct TraversalPathHash { std::size_t operator()(const LayerHierarchy::TraversalPath& key) const { uint32_t hashCode = key.id * 31; - for (auto mirrorRoot : key.mirrorRootIds) { - hashCode += mirrorRoot * 31; + if (key.mirrorRootId != UNASSIGNED_LAYER_ID) { + hashCode += key.mirrorRootId * 31; } return std::hash<size_t>{}(hashCode); } diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index d63b126452..e2cbe28893 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -137,6 +137,7 @@ RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args) dataspace = ui::Dataspace::V0_SRGB; gameMode = gui::GameMode::Unsupported; requestedFrameRate = {}; + cachingHint = gui::CachingHint::Enabled; } void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerState) { @@ -144,7 +145,7 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta const half oldAlpha = color.a; const bool hadBufferOrSideStream = hasValidBuffer() || sidebandStream != nullptr; const layer_state_t& clientState = resolvedComposerState.state; - + const bool hadBlur = hasBlur(); uint64_t clientChanges = what | layer_state_t::diff(clientState); layer_state_t::merge(clientState); what = clientChanges; @@ -164,8 +165,13 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta if (hadBufferOrSideStream != hasBufferOrSideStream) { changes |= RequestedLayerState::Changes::Geometry | RequestedLayerState::Changes::VisibleRegion | - RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Input | - RequestedLayerState::Changes::Buffer; + RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Input; + } + if (clientState.what & layer_state_t::eBufferChanged) { + changes |= RequestedLayerState::Changes::Buffer; + } + if (clientState.what & layer_state_t::eSidebandStreamChanged) { + changes |= RequestedLayerState::Changes::SidebandStream; } } if (what & (layer_state_t::eAlphaChanged)) { @@ -174,6 +180,12 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta RequestedLayerState::Changes::VisibleRegion; } } + if (what & (layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBlurRegionsChanged)) { + if (hadBlur != hasBlur()) { + changes |= RequestedLayerState::Changes::Visibility | + RequestedLayerState::Changes::VisibleRegion; + } + } if (clientChanges & layer_state_t::HIERARCHY_CHANGES) changes |= RequestedLayerState::Changes::Hierarchy; if (clientChanges & layer_state_t::CONTENT_CHANGES) @@ -190,7 +202,9 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta static const mat4 identityMatrix = mat4(); hasColorTransform = colorTransform != identityMatrix; } - if (clientState.what & (layer_state_t::eLayerChanged | layer_state_t::eRelativeLayerChanged)) { + if (clientState.what & + (layer_state_t::eLayerChanged | layer_state_t::eRelativeLayerChanged | + layer_state_t::eLayerStackChanged)) { changes |= RequestedLayerState::Changes::Z; } if (clientState.what & layer_state_t::eReparent) { @@ -319,11 +333,11 @@ ui::Transform RequestedLayerState::getTransform(uint32_t displayRotationFlags) c } std::string RequestedLayerState::getDebugString() const { - return "[" + std::to_string(id) + "]" + name + ",parent=" + layerIdToString(parentId) + - ",relativeParent=" + layerIdToString(relativeParentId) + - ",isRelativeOf=" + std::to_string(isRelativeOf) + - ",mirrorIds=" + layerIdsToString(mirrorIds) + - ",handleAlive=" + std::to_string(handleAlive) + ",z=" + std::to_string(z); + std::stringstream debug; + debug << "RequestedLayerState{" << name << " parent=" << layerIdToString(parentId) + << " relativeParent=" << layerIdToString(relativeParentId) + << " mirrorId=" << layerIdsToString(mirrorIds) << " handle=" << handleAlive << " z=" << z; + return debug.str(); } std::string RequestedLayerState::getDebugStringShort() const { @@ -442,4 +456,26 @@ bool RequestedLayerState::hasInputInfo() const { windowInfo->inputConfig.test(gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL); } +bool RequestedLayerState::hasBlur() const { + return backgroundBlurRadius > 0 || blurRegions.size() > 0; +} + +bool RequestedLayerState::hasFrameUpdate() const { + return what & layer_state_t::CONTENT_DIRTY && + (externalTexture || bgColorLayerId != UNASSIGNED_LAYER_ID); +} + +bool RequestedLayerState::hasReadyFrame() const { + return hasFrameUpdate() || changes.test(Changes::SidebandStream) || autoRefresh; +} + +bool RequestedLayerState::hasSidebandStreamFrame() const { + return hasFrameUpdate() && sidebandStream.get(); +} + +void RequestedLayerState::clearChanges() { + what = 0; + changes.clear(); +} + } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h index 6317b95709..6f5485d4e1 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h @@ -52,10 +52,14 @@ struct RequestedLayerState : layer_state_t { FrameRate = 1u << 13, VisibleRegion = 1u << 14, Buffer = 1u << 15, + SidebandStream = 1u << 16, + Animation = 1u << 17, }; static Rect reduce(const Rect& win, const Region& exclude); RequestedLayerState(const LayerCreationArgs&); void merge(const ResolvedComposerState&); + void clearChanges(); + // Currently we only care about the primary display ui::Transform getTransform(uint32_t displayRotationFlags) const; ui::Size getUnrotatedBufferSize(uint32_t displayRotationFlags) const; @@ -71,6 +75,10 @@ struct RequestedLayerState : layer_state_t { aidl::android::hardware::graphics::composer3::Composition getCompositionType() const; bool hasValidRelativeParent() const; bool hasInputInfo() const; + bool hasBlur() const; + bool hasFrameUpdate() const; + bool hasReadyFrame() const; + bool hasSidebandStreamFrame() const; // Layer serial number. This gives layers an explicit ordering, so we // have a stable sort order when their layer stack and Z-order are @@ -109,6 +117,7 @@ struct RequestedLayerState : layer_state_t { uint32_t touchCropId = UNASSIGNED_LAYER_ID; uint32_t bgColorLayerId = UNASSIGNED_LAYER_ID; ftl::Flags<RequestedLayerState::Changes> changes; + bool bgColorLayer = false; }; } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h index a06b870549..7fc825eba3 100644 --- a/services/surfaceflinger/FrontEnd/TransactionHandler.h +++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h @@ -34,7 +34,7 @@ namespace surfaceflinger::frontend { 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/Layer.cpp b/services/surfaceflinger/Layer.cpp index b6b9965407..0f2af2f809 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -146,7 +146,7 @@ Layer::Layer(const LayerCreationArgs& args) 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; @@ -196,7 +196,8 @@ Layer::Layer(const LayerCreationArgs& args) mDrawingState.color.b = -1.0_hf; } - mFrameTracker.setDisplayRefreshPeriod(args.flinger->mScheduler->getLeaderVsyncPeriod().ns()); + mFrameTracker.setDisplayRefreshPeriod( + args.flinger->mScheduler->getPacesetterVsyncPeriod().ns()); mOwnerUid = args.ownerUid; mOwnerPid = args.ownerPid; @@ -228,9 +229,7 @@ Layer::~Layer() { 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 @@ -395,14 +394,22 @@ void Layer::updateTrustedPresentationState(const DisplayDevice* display, if (!leaveState) { const auto outputLayer = findOutputLayerForDisplay(display); - if (outputLayer != nullptr && snapshot != nullptr) { - mLastComputedTrustedPresentationState = - computeTrustedPresentationState(snapshot->geomLayerBounds, - snapshot->sourceBounds(), - outputLayer->getState().coveredRegion, - snapshot->transformedBounds, snapshot->alpha, - snapshot->geomLayerTransform, - mTrustedPresentationThresholds); + if (outputLayer != nullptr) { + if (outputLayer->getState().coveredRegionExcludingDisplayOverlays) { + Region coveredRegion = + *outputLayer->getState().coveredRegionExcludingDisplayOverlays; + mLastComputedTrustedPresentationState = + computeTrustedPresentationState(snapshot->geomLayerBounds, + snapshot->sourceBounds(), coveredRegion, + snapshot->transformedBounds, + snapshot->alpha, + snapshot->geomLayerTransform, + mTrustedPresentationThresholds); + } else { + ALOGE("CoveredRegionExcludingDisplayOverlays was not set for %s. Don't compute " + "TrustedPresentationState", + getDebugName()); + } } } const bool newState = mLastComputedTrustedPresentationState; @@ -459,10 +466,15 @@ bool Layer::computeTrustedPresentationState(const FloatRect& bounds, const Float float boundsOverSourceH = bounds.getHeight() / (float)sourceBounds.getHeight(); fractionRendered *= boundsOverSourceW * boundsOverSourceH; - Rect coveredBounds = coveredRegion.bounds(); - fractionRendered *= (1 - - ((coveredBounds.width() / (float)screenBounds.getWidth()) * - coveredBounds.height() / (float)screenBounds.getHeight())); + Region tJunctionFreeRegion = Region::createTJunctionFreeRegion(coveredRegion); + // Compute the size of all the rects since they may be disconnected. + float coveredSize = 0; + for (auto rect = tJunctionFreeRegion.begin(); rect < tJunctionFreeRegion.end(); rect++) { + float size = rect->width() * rect->height(); + coveredSize += size; + } + + fractionRendered *= (1 - (coveredSize / (screenBounds.getWidth() * screenBounds.getHeight()))); if (fractionRendered < thresholds.minFractionRendered) { return false; @@ -555,6 +567,7 @@ void Layer::prepareBasicGeometryCompositionState() { : Hwc2::IComposerClient::BlendMode::COVERAGE; } + // Please keep in sync with LayerSnapshotBuilder auto* snapshot = editLayerSnapshot(); snapshot->outputFilter = getOutputFilter(); snapshot->isVisible = isVisible(); @@ -581,6 +594,7 @@ void Layer::prepareGeometryCompositionState() { const auto& drawingState{getDrawingState()}; auto* snapshot = editLayerSnapshot(); + // Please keep in sync with LayerSnapshotBuilder snapshot->geomBufferSize = getBufferSize(drawingState); snapshot->geomContentCrop = getBufferCrop(); snapshot->geomCrop = getCrop(drawingState); @@ -613,6 +627,7 @@ void Layer::prepareGeometryCompositionState() { void Layer::preparePerFrameCompositionState() { const auto& drawingState{getDrawingState()}; + // Please keep in sync with LayerSnapshotBuilder auto* snapshot = editLayerSnapshot(); snapshot->forceClientComposition = false; @@ -626,6 +641,7 @@ void Layer::preparePerFrameCompositionState() { snapshot->dimmingEnabled = isDimmingEnabled(); snapshot->currentSdrHdrRatio = getCurrentSdrHdrRatio(); snapshot->desiredSdrHdrRatio = getDesiredSdrHdrRatio(); + snapshot->cachingHint = getCachingHint(); const bool usesRoundedCorners = hasRoundedCorners(); @@ -655,8 +671,9 @@ void Layer::preparePerFrameCompositionState() { } void Layer::preparePerFrameBufferCompositionState() { - // Sideband layers + // Please keep in sync with LayerSnapshotBuilder auto* snapshot = editLayerSnapshot(); + // Sideband layers if (snapshot->sidebandStream.get() && !snapshot->sidebandStreamHasFrame) { snapshot->compositionType = aidl::android::hardware::graphics::composer3::Composition::SIDEBAND; @@ -664,6 +681,9 @@ void Layer::preparePerFrameBufferCompositionState() { } else if ((mDrawingState.flags & layer_state_t::eLayerIsDisplayDecoration) != 0) { snapshot->compositionType = aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION; + } else if ((mDrawingState.flags & layer_state_t::eLayerIsRefreshRateIndicator) != 0) { + snapshot->compositionType = + aidl::android::hardware::graphics::composer3::Composition::REFRESH_RATE_INDICATOR; } else { // Normal buffer layers snapshot->hdrMetadata = mBufferInfo.mHdrMetadata; @@ -679,6 +699,7 @@ void Layer::preparePerFrameBufferCompositionState() { } void Layer::preparePerFrameEffectsCompositionState() { + // Please keep in sync with LayerSnapshotBuilder auto* snapshot = editLayerSnapshot(); snapshot->color = getColor(); snapshot->compositionType = @@ -687,6 +708,7 @@ void Layer::preparePerFrameEffectsCompositionState() { void Layer::prepareCursorCompositionState() { const State& drawingState{getDrawingState()}; + // Please keep in sync with LayerSnapshotBuilder auto* snapshot = editLayerSnapshot(); // Apply the layer's transform, followed by the display's global transform @@ -734,6 +756,40 @@ bool Layer::isSecure() const { 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 // ---------------------------------------------------------------------------- @@ -778,7 +834,7 @@ void Layer::commitTransaction(State&) { if (surfaceFrame->getPresentState() != PresentState::Presented) { // With applyPendingStates, we could end up having presented surfaceframes from previous // states - surfaceFrame->setPresentState(PresentState::Presented); + surfaceFrame->setPresentState(PresentState::Presented, mLastLatchTime); mFlinger->mFrameTimeline->addSurfaceFrame(surfaceFrame); } } @@ -1201,7 +1257,7 @@ bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* tran return parentFrameRate; }(); - *transactionNeeded |= setFrameRateForLayerTree(frameRate); + *transactionNeeded |= setFrameRateForLayerTreeLegacy(frameRate); // The frame rate is propagated to the children bool childrenHaveFrameRate = false; @@ -1215,7 +1271,7 @@ bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* tran if (!frameRate.rate.isValid() && frameRate.type != FrameRateCompatibility::NoVote && childrenHaveFrameRate) { *transactionNeeded |= - setFrameRateForLayerTree(FrameRate(Fps(), FrameRateCompatibility::NoVote)); + setFrameRateForLayerTreeLegacy(FrameRate(Fps(), FrameRateCompatibility::NoVote)); } // We return whether this layer ot its children has a vote. We ignore ExactOrMultiple votes for @@ -1328,7 +1384,7 @@ void Layer::addSurfaceFramePresentedForBuffer( surfaceFrame->setAcquireFenceTime(acquireFenceTime); surfaceFrame->setPresentState(PresentState::Presented, mLastLatchTime); mFlinger->mFrameTimeline->addSurfaceFrame(surfaceFrame); - mLastLatchTime = currentLatchTime; + updateLastLatchTime(currentLatchTime); } std::shared_ptr<frametimeline::SurfaceFrame> Layer::createSurfaceFrameForTransaction( @@ -1367,7 +1423,7 @@ std::shared_ptr<frametimeline::SurfaceFrame> Layer::createSurfaceFrameForBuffer( return surfaceFrame; } -bool Layer::setFrameRateForLayerTree(FrameRate frameRate) { +bool Layer::setFrameRateForLayerTreeLegacy(FrameRate frameRate) { if (mDrawingState.frameRateForLayerTree == frameRate) { return false; } @@ -1380,9 +1436,21 @@ bool Layer::setFrameRateForLayerTree(FrameRate frameRate) { mDrawingState.modified = true; setTransactionFlags(eTransactionNeeded); - using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType; - mFlinger->mScheduler->recordLayerHistory(this, systemTime(), LayerUpdateType::SetFrameRate); + mFlinger->mScheduler + ->recordLayerHistory(sequence, getLayerProps(), systemTime(), + scheduler::LayerHistory::LayerUpdateType::SetFrameRate); + return true; +} + +bool Layer::setFrameRateForLayerTree(FrameRate frameRate, const scheduler::LayerProps& layerProps) { + if (mDrawingState.frameRateForLayerTree == frameRate) { + return false; + } + mDrawingState.frameRateForLayerTree = frameRate; + mFlinger->mScheduler + ->recordLayerHistory(sequence, layerProps, systemTime(), + scheduler::LayerHistory::LayerUpdateType::SetFrameRate); return true; } @@ -2044,15 +2112,7 @@ LayerProto* Layer::writeToProto(LayersProto& layersProto, uint32_t traceFlags) { writeToProtoCommonState(layerProto, LayerVector::StateSet::Drawing, traceFlags); if (traceFlags & LayerTracing::TRACE_COMPOSITION) { - ftl::FakeGuard guard(mFlinger->mStateLock); // Called from the main thread. - - // Only populate for the primary display. - if (const auto display = mFlinger->getDefaultDisplayDeviceLocked()) { - const auto compositionType = getCompositionType(*display); - layerProto->set_hwc_composition_type(static_cast<HwcCompositionType>(compositionType)); - LayerProtoHelper::writeToProto(getVisibleRegion(display.get()), - [&]() { return layerProto->mutable_visible_region(); }); - } + writeCompositionStateToProto(layerProto); } for (const sp<Layer>& layer : mDrawingChildren) { @@ -2062,6 +2122,18 @@ LayerProto* Layer::writeToProto(LayersProto& layersProto, uint32_t traceFlags) { return layerProto; } +void Layer::writeCompositionStateToProto(LayerProto* layerProto) { + ftl::FakeGuard guard(mFlinger->mStateLock); // Called from the main thread. + + // Only populate for the primary display. + if (const auto display = mFlinger->getDefaultDisplayDeviceLocked()) { + const auto compositionType = getCompositionType(*display); + layerProto->set_hwc_composition_type(static_cast<HwcCompositionType>(compositionType)); + LayerProtoHelper::writeToProto(getVisibleRegion(display.get()), + [&]() { return layerProto->mutable_visible_region(); }); + } +} + void Layer::writeToProtoDrawingState(LayerProto* layerInfo) { const ui::Transform transform = getTransform(); auto buffer = getExternalTexture(); @@ -2459,7 +2531,20 @@ bool Layer::hasInputInfo() const { compositionengine::OutputLayer* Layer::findOutputLayerForDisplay( const DisplayDevice* display) const { if (!display) return nullptr; - return display->getCompositionDisplay()->getOutputLayerForLayer(getCompositionEngineLayerFE()); + if (!mFlinger->mLayerLifecycleManagerEnabled) { + return display->getCompositionDisplay()->getOutputLayerForLayer( + getCompositionEngineLayerFE()); + } + sp<LayerFE> layerFE; + frontend::LayerHierarchy::TraversalPath path{.id = static_cast<uint32_t>(sequence)}; + for (auto& [p, layer] : mLayerFEs) { + if (p == path) { + layerFE = layer; + } + } + + if (!layerFE) return nullptr; + return display->getCompositionDisplay()->getOutputLayerForLayer(layerFE); } Region Layer::getVisibleRegion(const DisplayDevice* display) const { @@ -2698,12 +2783,13 @@ void Layer::cloneDrawingState(const Layer* from) { 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); @@ -2798,16 +2884,7 @@ void Layer::releasePendingBuffer(nsecs_t dequeueReadyTime) { } 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 = {}; @@ -2963,9 +3040,7 @@ bool Layer::setBuffer(std::shared_ptr<renderengine::ExternalTexture>& buffer, // 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) { @@ -2975,13 +3050,16 @@ bool Layer::setBuffer(std::shared_ptr<renderengine::ExternalTexture>& buffer, } else if (EARLY_RELEASE_ENABLED && mLastClientCompositionFence != nullptr) { callReleaseBufferCallback(mDrawingState.releaseBufferListener, mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber, - mLastClientCompositionFence, - mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate( - mOwnerUid)); + mLastClientCompositionFence); mLastClientCompositionFence = nullptr; } + } else { + // if we are latching a buffer for the first time then clear the mLastLatchTime since + // we don't want to incorrectly classify a frame if we miss the desired present time. + updateLastLatchTime(0); } + mDrawingState.producerId = bufferData.producerId; mDrawingState.frameNumber = frameNumber; mDrawingState.releaseBufferListener = bufferData.releaseBufferListener; mDrawingState.buffer = std::move(buffer); @@ -2998,7 +3076,7 @@ bool Layer::setBuffer(std::shared_ptr<renderengine::ExternalTexture>& buffer, } else { mCallbackHandleAcquireTimeOrFence = mDrawingState.acquireFenceTime->getSignalTime(); } - + mDrawingState.latchedVsyncId = info.vsyncId; mDrawingState.modified = true; setTransactionFlags(eTransactionNeeded); @@ -3008,18 +3086,9 @@ bool Layer::setBuffer(std::shared_ptr<renderengine::ExternalTexture>& buffer, mDrawingState.desiredPresentTime = desiredPresentTime; mDrawingState.isAutoTimestamp = isAutoTimestamp; - const nsecs_t presentTime = [&] { - if (!isAutoTimestamp) return desiredPresentTime; - - const auto prediction = - mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken(info.vsyncId); - if (prediction.has_value()) return prediction->presentTime; - - return static_cast<nsecs_t>(0); - }(); - - using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType; - mFlinger->mScheduler->recordLayerHistory(this, presentTime, LayerUpdateType::Buffer); + if (mFlinger->mLegacyFrontEndEnabled) { + recordLayerHistoryBufferUpdate(getLayerProps()); + } setFrameTimelineVsyncForBufferTransaction(info, postTime); @@ -3036,6 +3105,32 @@ bool Layer::setBuffer(std::shared_ptr<renderengine::ExternalTexture>& buffer, return true; } +void Layer::setDesiredPresentTime(nsecs_t desiredPresentTime, bool isAutoTimestamp) { + mDrawingState.desiredPresentTime = desiredPresentTime; + mDrawingState.isAutoTimestamp = isAutoTimestamp; +} + +void Layer::recordLayerHistoryBufferUpdate(const scheduler::LayerProps& layerProps) { + const nsecs_t presentTime = [&] { + if (!mDrawingState.isAutoTimestamp) return mDrawingState.desiredPresentTime; + + const auto prediction = mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken( + mDrawingState.latchedVsyncId); + if (prediction.has_value()) return prediction->presentTime; + + return static_cast<nsecs_t>(0); + }(); + mFlinger->mScheduler->recordLayerHistory(sequence, layerProps, presentTime, + scheduler::LayerHistory::LayerUpdateType::Buffer); +} + +void Layer::recordLayerHistoryAnimationTx(const scheduler::LayerProps& layerProps) { + const nsecs_t presentTime = + mDrawingState.isAutoTimestamp ? 0 : mDrawingState.desiredPresentTime; + mFlinger->mScheduler->recordLayerHistory(sequence, layerProps, presentTime, + scheduler::LayerHistory::LayerUpdateType::AnimationTX); +} + bool Layer::setDataspace(ui::Dataspace dataspace) { mDrawingState.dataspaceRequested = true; if (mDrawingState.dataspace == dataspace) return false; @@ -3056,6 +3151,14 @@ bool Layer::setExtendedRangeBrightness(float currentBufferRatio, float desiredRa return true; } +bool Layer::setCachingHint(gui::CachingHint cachingHint) { + if (mDrawingState.cachingHint == cachingHint) return false; + mDrawingState.cachingHint = cachingHint; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + bool Layer::setHdrMetadata(const HdrMetadata& hdrMetadata) { if (mDrawingState.hdrMetadata == hdrMetadata) return false; mDrawingState.hdrMetadata = hdrMetadata; @@ -3098,15 +3201,15 @@ bool Layer::setSidebandStream(const sp<NativeHandle>& sidebandStream) { 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; @@ -3121,11 +3224,19 @@ bool Layer::setTransactionCompletedListeners(const std::vector<sp<CallbackHandle 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; @@ -3180,11 +3291,10 @@ bool Layer::fenceHasSignaled() const { 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) { @@ -3218,11 +3328,11 @@ bool Layer::hasFrameUpdate() const { (c.buffer != nullptr || c.bgColorLayer != nullptr); } -void Layer::updateTexImage(nsecs_t latchTime) { +void Layer::updateTexImage(nsecs_t latchTime, bool bgColorOnly) { const State& s(getDrawingState()); if (!s.buffer) { - if (s.bgColorLayer) { + if (bgColorOnly) { for (auto& handle : mDrawingState.callbackHandles) { handle->latchTime = latchTime; } @@ -3419,7 +3529,7 @@ bool Layer::simpleBufferUpdate(const layer_state_t& s) const { } if (s.what & layer_state_t::eBackgroundColorChanged) { - if (mDrawingState.bgColorLayer || s.bgColorAlpha != 0) { + if (mDrawingState.bgColorLayer || s.bgColor.a != 0) { ALOGV("%s: false [eBackgroundColorChanged changed]", __func__); return false; } @@ -3570,7 +3680,7 @@ bool Layer::isHdrY410() const { 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 { @@ -3581,16 +3691,36 @@ LayerSnapshot* Layer::editLayerSnapshot() { 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; @@ -3723,6 +3853,11 @@ void Layer::onPostComposition(const DisplayDevice* display, } bool Layer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime) { + const bool bgColorOnly = mDrawingState.bgColorLayer != nullptr; + return latchBufferImpl(recomputeVisibleRegions, latchTime, bgColorOnly); +} + +bool Layer::latchBufferImpl(bool& recomputeVisibleRegions, nsecs_t latchTime, bool bgColorOnly) { ATRACE_FORMAT_INSTANT("latchBuffer %s - %" PRIu64, getDebugName(), getDrawingState().frameNumber); @@ -3739,8 +3874,7 @@ bool Layer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime) { mFlinger->onLayerUpdate(); return false; } - - updateTexImage(latchTime); + updateTexImage(latchTime, bgColorOnly); if (mDrawingState.buffer == nullptr) { return false; } @@ -3986,29 +4120,7 @@ void Layer::updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMet } } -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, +bool Layer::setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds, TrustedPresentationListener const& listener) { bool hadTrustedPresentationListener = hasTrustedPresentationListener(); mTrustedPresentationListener = listener; @@ -4019,6 +4131,20 @@ void Layer::setTrustedPresentationInfo(TrustedPresentationThresholds const& thre } else if (hadTrustedPresentationListener && !haveTrustedPresentationListener) { mFlinger->mNumTrustedPresentationListeners--; } + + // Reset trusted presentation states to ensure we start the time again. + mEnteredTrustedPresentationStateTime = -1; + mLastReportedTrustedPresentationState = false; + mLastComputedTrustedPresentationState = false; + + // If there's a new trusted presentation listener, the code needs to go through the composite + // path to ensure it recomutes the current state and invokes the TrustedPresentationListener if + // we're already in the requested state. + return haveTrustedPresentationListener; +} + +void Layer::updateLastLatchTime(nsecs_t latchTime) { + mLastLatchTime = latchTime; } // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index f858224281..2fb122cac3 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -141,6 +141,8 @@ public: uint64_t frameNumber; ui::Transform transform; + + uint32_t producerId = 0; uint32_t bufferTransform; bool transformToDisplayInverse; Region transparentRegionHint; @@ -225,6 +227,8 @@ public: bool dimmingEnabled = true; float currentSdrHdrRatio = 1.f; float desiredSdrHdrRatio = 1.f; + gui::CachingHint cachingHint = gui::CachingHint::Enabled; + int64_t latchedVsyncId = 0; }; explicit Layer(const LayerCreationArgs& args); @@ -294,6 +298,7 @@ public: virtual bool isDimmingEnabled() const { return getDrawingState().dimmingEnabled; } float getDesiredSdrHdrRatio() const { return getDrawingState().desiredSdrHdrRatio; } float getCurrentSdrHdrRatio() const { return getDrawingState().currentSdrHdrRatio; } + gui::CachingHint getCachingHint() const { return getDrawingState().cachingHint; } bool setTransform(uint32_t /*transform*/); bool setTransformToDisplayInverse(bool /*transformToDisplayInverse*/); @@ -301,13 +306,16 @@ public: const BufferData& /* bufferData */, nsecs_t /* postTime */, nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/, std::optional<nsecs_t> /* dequeueTime */, const FrameTimelineInfo& /*info*/); + void setDesiredPresentTime(nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/); bool setDataspace(ui::Dataspace /*dataspace*/); bool setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio); + bool setCachingHint(gui::CachingHint cachingHint); 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); @@ -328,9 +336,12 @@ public: 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 @@ -338,6 +349,7 @@ public: void useSurfaceDamage(); void useEmptyDamage(); Region getVisibleRegion(const DisplayDevice*) const; + void updateLastLatchTime(nsecs_t latchtime); /* * isOpaque - true if this surface is opaque @@ -418,6 +430,9 @@ public: */ bool latchBuffer(bool& /*recomputeVisibleRegions*/, nsecs_t /*latchTime*/); + bool latchBufferImpl(bool& /*recomputeVisibleRegions*/, nsecs_t /*latchTime*/, + bool bgColorOnly); + /* * Calls latchBuffer if the buffer has a frame queued and then releases the buffer. * This is used if the buffer is just latched and releases to free up the buffer @@ -512,7 +527,7 @@ public: // 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) { @@ -595,6 +610,7 @@ public: bool isRemovedFromCurrentState() const; LayerProto* writeToProto(LayersProto& layersProto, uint32_t traceFlags); + void writeCompositionStateToProto(LayerProto* layerProto); // Write states that are modified by the main thread. This includes drawing // state as well as buffer data. This should be called in the main or tracing @@ -748,7 +764,7 @@ public: std::shared_ptr<frametimeline::SurfaceFrame> createSurfaceFrameForBuffer( const FrameTimelineInfo& info, nsecs_t queueTime, std::string debugName); - void setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds, + bool setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds, TrustedPresentationListener const& listener); // Creates a new handle each time, so we only expect @@ -832,6 +848,25 @@ public: 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); + bool setFrameRateForLayerTreeLegacy(FrameRate); + bool setFrameRateForLayerTree(FrameRate, const scheduler::LayerProps&); + void recordLayerHistoryBufferUpdate(const scheduler::LayerProps&); + void recordLayerHistoryAnimationTx(const scheduler::LayerProps&); + auto getLayerProps() const { + return scheduler::LayerProps{ + .visible = isVisible(), + .bounds = getBounds(), + .transform = getTransform(), + .setFrameRateVote = getFrameRateForLayerTree(), + .frameRateSelectionPriority = getFrameRateSelectionPriority(), + }; + }; + bool hasBuffer() const { return mBufferInfo.mBuffer != nullptr; } protected: // For unit tests @@ -1004,7 +1039,6 @@ private: void updateTreeHasFrameRateVote(); bool propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* transactionNeeded); - bool setFrameRateForLayerTree(FrameRate); void setZOrderRelativeOf(const wp<Layer>& relativeOf); bool isTrustedOverlay() const; gui::DropInputMode getDropInputMode() const; @@ -1032,18 +1066,20 @@ private: bool hasFrameUpdate() const; - void updateTexImage(nsecs_t latchTime); + void updateTexImage(nsecs_t latchTime, bool bgColorOnly = false); // Crop that applies to the buffer Rect computeBufferCrop(const State& s); - bool willPresentCurrentTransaction() 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. @@ -1061,6 +1097,11 @@ private: 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. @@ -1146,34 +1187,10 @@ private: // 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.h b/services/surfaceflinger/LayerFE.h index 01da0199a2..c23bd31d1a 100644 --- a/services/surfaceflinger/LayerFE.h +++ b/services/surfaceflinger/LayerFE.h @@ -16,6 +16,7 @@ #pragma once +#include <android/gui/CachingHint.h> #include <gui/LayerMetadata.h> #include "FrontEnd/LayerSnapshot.h" #include "compositionengine/LayerFE.h" diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp index 0506c47555..55281fa962 100644 --- a/services/surfaceflinger/LayerProtoHelper.cpp +++ b/services/surfaceflinger/LayerProtoHelper.cpp @@ -247,6 +247,153 @@ void LayerProtoHelper::readFromProto(const BlurRegion& proto, android::BlurRegio outRegion.right = proto.right(); outRegion.bottom = proto.bottom(); } + +void LayerProtoHelper::writeHierarchyToProto( + LayersProto& outLayersProto, const frontend::LayerHierarchy& root, + const frontend::LayerSnapshotBuilder& snapshotBuilder, + const std::unordered_map<uint32_t, sp<Layer>>& legacyLayers, uint32_t traceFlags) { + using Variant = frontend::LayerHierarchy::Variant; + frontend::LayerSnapshot defaultSnapshot; + + LayerProto* layerProto = outLayersProto.add_layers(); + const frontend::RequestedLayerState& layer = *root.getLayer(); + frontend::LayerSnapshot* snapshot = snapshotBuilder.getSnapshot(layer.id); + + if (!snapshot) { + defaultSnapshot.uniqueSequence = layer.id; + snapshot = &defaultSnapshot; + } + writeSnapshotToProto(layerProto, layer, *snapshot, traceFlags); + for (const auto& [child, variant] : root.mChildren) { + if (variant == Variant::Attached || variant == Variant::Detached) { + layerProto->add_children(child->getLayer()->id); + } else if (variant == Variant::Relative) { + layerProto->add_relatives(child->getLayer()->id); + } + } + + auto parent = root.getParent(); + if (parent && parent->getLayer()) { + layerProto->set_parent(parent->getLayer()->id); + } else { + layerProto->set_parent(-1); + } + + auto relativeParent = root.getRelativeParent(); + if (relativeParent && relativeParent->getLayer()) { + layerProto->set_z_order_relative_of(relativeParent->getLayer()->id); + } else { + layerProto->set_z_order_relative_of(-1); + } + + if (traceFlags & LayerTracing::TRACE_COMPOSITION) { + auto it = legacyLayers.find(layer.id); + if (it != legacyLayers.end()) { + it->second->writeCompositionStateToProto(layerProto); + } + } + + for (const auto& [child, variant] : root.mChildren) { + // avoid visiting relative layers twice + if (variant == Variant::Detached) { + continue; + } + writeHierarchyToProto(outLayersProto, *child, snapshotBuilder, legacyLayers, traceFlags); + } +} + +void LayerProtoHelper::writeSnapshotToProto(LayerProto* layerInfo, + const frontend::RequestedLayerState& requestedState, + const frontend::LayerSnapshot& snapshot, + uint32_t traceFlags) { + const ui::Transform transform = snapshot.geomLayerTransform; + auto buffer = requestedState.externalTexture; + if (buffer != nullptr) { + LayerProtoHelper::writeToProto(*buffer, + [&]() { return layerInfo->mutable_active_buffer(); }); + LayerProtoHelper::writeToProtoDeprecated(ui::Transform(requestedState.bufferTransform), + layerInfo->mutable_buffer_transform()); + } + layerInfo->set_invalidate(snapshot.contentDirty); + layerInfo->set_is_protected(snapshot.hasProtectedContent); + layerInfo->set_dataspace(dataspaceDetails(static_cast<android_dataspace>(snapshot.dataspace))); + layerInfo->set_curr_frame(requestedState.bufferData->frameNumber); + layerInfo->set_requested_corner_radius(requestedState.cornerRadius); + layerInfo->set_corner_radius( + (snapshot.roundedCorner.radius.x + snapshot.roundedCorner.radius.y) / 2.0); + layerInfo->set_background_blur_radius(snapshot.backgroundBlurRadius); + layerInfo->set_is_trusted_overlay(snapshot.isTrustedOverlay); + LayerProtoHelper::writeToProtoDeprecated(transform, layerInfo->mutable_transform()); + LayerProtoHelper::writePositionToProto(transform.tx(), transform.ty(), + [&]() { return layerInfo->mutable_position(); }); + LayerProtoHelper::writeToProto(snapshot.geomLayerBounds, + [&]() { return layerInfo->mutable_bounds(); }); + LayerProtoHelper::writeToProto(snapshot.surfaceDamage, + [&]() { return layerInfo->mutable_damage_region(); }); + + if (requestedState.hasColorTransform) { + LayerProtoHelper::writeToProto(snapshot.colorTransform, + layerInfo->mutable_color_transform()); + } + + LayerProtoHelper::writeToProto(snapshot.croppedBufferSize.toFloatRect(), + [&]() { return layerInfo->mutable_source_bounds(); }); + LayerProtoHelper::writeToProto(snapshot.transformedBounds, + [&]() { return layerInfo->mutable_screen_bounds(); }); + LayerProtoHelper::writeToProto(snapshot.roundedCorner.cropRect, + [&]() { return layerInfo->mutable_corner_radius_crop(); }); + layerInfo->set_shadow_radius(snapshot.shadowRadius); + + layerInfo->set_id(snapshot.uniqueSequence); + layerInfo->set_name(requestedState.name); + layerInfo->set_type("Layer"); + + LayerProtoHelper::writeToProto(requestedState.transparentRegion, + [&]() { return layerInfo->mutable_transparent_region(); }); + + layerInfo->set_layer_stack(snapshot.outputFilter.layerStack.id); + layerInfo->set_z(requestedState.z); + + ui::Transform requestedTransform = requestedState.getTransform(0); + LayerProtoHelper::writePositionToProto(requestedTransform.tx(), requestedTransform.ty(), [&]() { + return layerInfo->mutable_requested_position(); + }); + + LayerProtoHelper::writeToProto(requestedState.crop, + [&]() { return layerInfo->mutable_crop(); }); + + layerInfo->set_is_opaque(snapshot.contentOpaque); + if (requestedState.externalTexture) + layerInfo->set_pixel_format( + decodePixelFormat(requestedState.externalTexture->getPixelFormat())); + LayerProtoHelper::writeToProto(snapshot.color, [&]() { return layerInfo->mutable_color(); }); + LayerProtoHelper::writeToProto(requestedState.color, + [&]() { return layerInfo->mutable_requested_color(); }); + layerInfo->set_flags(requestedState.flags); + + LayerProtoHelper::writeToProtoDeprecated(requestedTransform, + layerInfo->mutable_requested_transform()); + + layerInfo->set_is_relative_of(requestedState.isRelativeOf); + + layerInfo->set_owner_uid(requestedState.ownerUid); + + if ((traceFlags & LayerTracing::TRACE_INPUT) && snapshot.hasInputInfo()) { + LayerProtoHelper::writeToProto(snapshot.inputInfo, {}, + [&]() { return layerInfo->mutable_input_window_info(); }); + } + + if (traceFlags & LayerTracing::TRACE_EXTRA) { + auto protoMap = layerInfo->mutable_metadata(); + for (const auto& entry : requestedState.metadata.mMap) { + (*protoMap)[entry.first] = std::string(entry.second.cbegin(), entry.second.cend()); + } + } + + LayerProtoHelper::writeToProto(requestedState.destinationFrame, + [&]() { return layerInfo->mutable_destination_frame(); }); +} + } // namespace surfaceflinger } // namespace android diff --git a/services/surfaceflinger/LayerProtoHelper.h b/services/surfaceflinger/LayerProtoHelper.h index 6ade1435e5..de4bd01f1a 100644 --- a/services/surfaceflinger/LayerProtoHelper.h +++ b/services/surfaceflinger/LayerProtoHelper.h @@ -58,6 +58,15 @@ public: static void readFromProto(const ColorTransformProto& colorTransformProto, mat4& matrix); static void writeToProto(const android::BlurRegion region, BlurRegion*); static void readFromProto(const BlurRegion& proto, android::BlurRegion& outRegion); + static void writeHierarchyToProto(LayersProto& layersProto, + const frontend::LayerHierarchy& root, + const frontend::LayerSnapshotBuilder& snapshotBuilder, + const std::unordered_map<uint32_t, sp<Layer>>& mLegacyLayers, + uint32_t traceFlags); + + static void writeSnapshotToProto(LayerProto* outProto, + const frontend::RequestedLayerState& requestedState, + const frontend::LayerSnapshot& snapshot, uint32_t traceFlags); }; } // namespace surfaceflinger diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp index 2b4375b0fa..03a7f226ed 100644 --- a/services/surfaceflinger/LayerRenderArea.cpp +++ b/services/surfaceflinger/LayerRenderArea.cpp @@ -69,6 +69,14 @@ Rect LayerRenderArea::getSourceCrop() const { void LayerRenderArea::render(std::function<void()> drawLayers) { using namespace std::string_literals; + 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, @@ -78,7 +86,6 @@ void LayerRenderArea::render(std::function<void()> drawLayers) { } 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()) { diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp index 0ade4679a3..9a4261d087 100644 --- a/services/surfaceflinger/RefreshRateOverlay.cpp +++ b/services/surfaceflinger/RefreshRateOverlay.cpp @@ -29,7 +29,6 @@ #include <SkBlendMode.h> #include <SkRect.h> #include <SkSurface.h> -#include <gui/SurfaceComposerClient.h> #include <gui/SurfaceControl.h> #undef LOG_TAG @@ -46,15 +45,6 @@ constexpr int kMaxDigits = /*displayFps*/ 3 + /*renderFps*/ 3 + /*spinner*/ 1; constexpr int kBufferWidth = kMaxDigits * kDigitWidth + (kMaxDigits - 1) * kDigitSpace; constexpr int kBufferHeight = kDigitHeight; -SurfaceComposerClient::Transaction createTransaction(const sp<SurfaceControl>& surface) { - constexpr float kFrameRate = 0.f; - constexpr int8_t kCompatibility = ANATIVEWINDOW_FRAME_RATE_NO_VOTE; - constexpr int8_t kSeamlessness = ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS; - - return SurfaceComposerClient::Transaction().setFrameRate(surface, kFrameRate, kCompatibility, - kSeamlessness); -} - } // namespace SurfaceControlHolder::~SurfaceControlHolder() { @@ -242,7 +232,7 @@ RefreshRateOverlay::RefreshRateOverlay(FpsRange fpsRange, ftl::Flags<Features> f return; } - createTransaction(mSurfaceControl->get()) + createTransaction() .setLayer(mSurfaceControl->get(), INT32_MAX - 2) .setTrustedOverlay(mSurfaceControl->get(), true) .apply(); @@ -272,14 +262,14 @@ auto RefreshRateOverlay::getOrCreateBuffers(Fps displayFps, Fps renderFps) -> co } }(); - createTransaction(mSurfaceControl->get()) - .setTransform(mSurfaceControl->get(), transform) - .apply(); + createTransaction().setTransform(mSurfaceControl->get(), transform).apply(); BufferCache::const_iterator it = mBufferCache.find({displayFps.getIntValue(), renderFps.getIntValue(), transformHint}); if (it == mBufferCache.end()) { - const int minFps = mFpsRange.min.getIntValue(); + // HWC minFps is not known by the framework in order + // to consider lower rates we set minFps to 0. + const int minFps = isSetByHwc() ? 0 : mFpsRange.min.getIntValue(); const int maxFps = mFpsRange.max.getIntValue(); // Clamp to the range. The current displayFps may be outside of this range if the display @@ -327,7 +317,7 @@ void RefreshRateOverlay::setViewport(ui::Size viewport) { frame.offsetBy(width >> 1, height >> 4); } - createTransaction(mSurfaceControl->get()) + createTransaction() .setMatrix(mSurfaceControl->get(), frame.getWidth() / static_cast<float>(kBufferWidth), 0, 0, frame.getHeight() / static_cast<float>(kBufferHeight)) .setPosition(mSurfaceControl->get(), frame.left, frame.top) @@ -335,14 +325,14 @@ void RefreshRateOverlay::setViewport(ui::Size viewport) { } void RefreshRateOverlay::setLayerStack(ui::LayerStack stack) { - createTransaction(mSurfaceControl->get()).setLayerStack(mSurfaceControl->get(), stack).apply(); + createTransaction().setLayerStack(mSurfaceControl->get(), stack).apply(); } void RefreshRateOverlay::changeRefreshRate(Fps displayFps, Fps renderFps) { mDisplayFps = displayFps; mRenderFps = renderFps; const auto buffer = getOrCreateBuffers(displayFps, renderFps)[mFrame]; - createTransaction(mSurfaceControl->get()).setBuffer(mSurfaceControl->get(), buffer).apply(); + createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply(); } void RefreshRateOverlay::animate() { @@ -351,7 +341,23 @@ void RefreshRateOverlay::animate() { const auto& buffers = getOrCreateBuffers(*mDisplayFps, *mRenderFps); mFrame = (mFrame + 1) % buffers.size(); const auto buffer = buffers[mFrame]; - createTransaction(mSurfaceControl->get()).setBuffer(mSurfaceControl->get(), buffer).apply(); + createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply(); +} + +SurfaceComposerClient::Transaction RefreshRateOverlay::createTransaction() const { + constexpr float kFrameRate = 0.f; + constexpr int8_t kCompatibility = ANATIVEWINDOW_FRAME_RATE_NO_VOTE; + constexpr int8_t kSeamlessness = ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS; + + const sp<SurfaceControl>& surface = mSurfaceControl->get(); + + SurfaceComposerClient::Transaction transaction; + if (isSetByHwc()) { + transaction.setFlags(surface, layer_state_t::eLayerIsRefreshRateIndicator, + layer_state_t::eLayerIsRefreshRateIndicator); + } + transaction.setFrameRate(surface, kFrameRate, kCompatibility, kSeamlessness); + return transaction; } } // namespace android diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h index b68a88c928..0b89b8e3a1 100644 --- a/services/surfaceflinger/RefreshRateOverlay.h +++ b/services/surfaceflinger/RefreshRateOverlay.h @@ -21,6 +21,7 @@ #include <ftl/flags.h> #include <ftl/small_map.h> +#include <gui/SurfaceComposerClient.h> #include <ui/LayerStack.h> #include <ui/Size.h> #include <ui/Transform.h> @@ -55,6 +56,7 @@ public: Spinner = 1 << 0, RenderRate = 1 << 1, ShowInMiddle = 1 << 2, + SetByHwc = 1 << 3, }; RefreshRateOverlay(FpsRange, ftl::Flags<Features>); @@ -63,6 +65,7 @@ public: void setViewport(ui::Size); void changeRefreshRate(Fps, Fps); void animate(); + bool isSetByHwc() const { return mFeatures.test(RefreshRateOverlay::Features::SetByHwc); } private: using Buffers = std::vector<sp<GraphicBuffer>>; @@ -82,6 +85,8 @@ private: const Buffers& getOrCreateBuffers(Fps, Fps); + SurfaceComposerClient::Transaction createTransaction() const; + struct Key { int displayFps; int renderFps; diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp index 839500f8a0..327ca3f0aa 100644 --- a/services/surfaceflinger/RegionSamplingThread.cpp +++ b/services/surfaceflinger/RegionSamplingThread.cpp @@ -285,47 +285,73 @@ void RegionSamplingThread::captureSample() { std::unordered_set<sp<IRegionSamplingListener>, SpHash<IRegionSamplingListener>> listeners; - auto traverseLayers = [&](const LayerVector::Visitor& visitor) { - bool stopLayerFound = false; - auto filterVisitor = [&](Layer* layer) { - // We don't want to capture any layers beyond the stop layer - if (stopLayerFound) return; - - // Likewise if we just found a stop layer, set the flag and abort - for (const auto& [area, stopLayerId, listener] : descriptors) { - if (stopLayerId != UNASSIGNED_LAYER_ID && layer->getSequence() == stopLayerId) { - stopLayerFound = true; - return; - } + auto layerFilterFn = [&](const char* layerName, uint32_t layerId, const Rect& bounds, + const ui::Transform transform, bool& outStopTraversal) -> bool { + // Likewise if we just found a stop layer, set the flag and abort + for (const auto& [area, stopLayerId, listener] : descriptors) { + if (stopLayerId != UNASSIGNED_LAYER_ID && layerId == stopLayerId) { + outStopTraversal = true; + return false; } + } - // Compute the layer's position on the screen - const Rect bounds = Rect(layer->getBounds()); - const ui::Transform transform = layer->getTransform(); - constexpr bool roundOutwards = true; - Rect transformed = transform.transform(bounds, roundOutwards); - - // If this layer doesn't intersect with the larger sampledBounds, skip capturing it - Rect ignore; - if (!transformed.intersect(sampledBounds, &ignore)) return; - - // If the layer doesn't intersect a sampling area, skip capturing it - bool intersectsAnyArea = false; - for (const auto& [area, stopLayer, listener] : descriptors) { - if (transformed.intersect(area, &ignore)) { - intersectsAnyArea = true; - listeners.insert(listener); - } + // Compute the layer's position on the screen + constexpr bool roundOutwards = true; + Rect transformed = transform.transform(bounds, roundOutwards); + + // If this layer doesn't intersect with the larger sampledBounds, skip capturing it + Rect ignore; + if (!transformed.intersect(sampledBounds, &ignore)) return false; + + // If the layer doesn't intersect a sampling area, skip capturing it + bool intersectsAnyArea = false; + for (const auto& [area, stopLayer, listener] : descriptors) { + if (transformed.intersect(area, &ignore)) { + intersectsAnyArea = true; + listeners.insert(listener); } - if (!intersectsAnyArea) return; + } + if (!intersectsAnyArea) return false; - ALOGV("Traversing [%s] [%d, %d, %d, %d]", layer->getDebugName(), bounds.left, - bounds.top, bounds.right, bounds.bottom); - visitor(layer); - }; - mFlinger.traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, filterVisitor); + ALOGV("Traversing [%s] [%d, %d, %d, %d]", layerName, bounds.left, bounds.top, bounds.right, + bounds.bottom); + + return true; }; + std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> getLayerSnapshots; + if (mFlinger.mLayerLifecycleManagerEnabled) { + auto filterFn = [&](const frontend::LayerSnapshot& snapshot, + bool& outStopTraversal) -> bool { + const Rect bounds = + frontend::RequestedLayerState::reduce(Rect(snapshot.geomLayerBounds), + snapshot.transparentRegionHint); + const ui::Transform transform = snapshot.geomLayerTransform; + return layerFilterFn(snapshot.name.c_str(), snapshot.path.id, bounds, transform, + outStopTraversal); + }; + getLayerSnapshots = + mFlinger.getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID, + filterFn); + } else { + auto traverseLayers = [&](const LayerVector::Visitor& visitor) { + bool stopLayerFound = false; + auto filterVisitor = [&](Layer* layer) { + // We don't want to capture any layers beyond the stop layer + if (stopLayerFound) return; + + if (!layerFilterFn(layer->getDebugName(), layer->getSequence(), + Rect(layer->getBounds()), layer->getTransform(), + stopLayerFound)) { + return; + } + visitor(layer); + }; + mFlinger.traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, filterVisitor); + }; + getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + } + std::shared_ptr<renderengine::ExternalTexture> buffer = nullptr; if (mCachedBuffer && mCachedBuffer->getBuffer()->getWidth() == sampledBounds.getWidth() && mCachedBuffer->getBuffer()->getHeight() == sampledBounds.getHeight()) { @@ -344,7 +370,6 @@ void RegionSamplingThread::captureSample() { renderengine::impl::ExternalTexture::Usage:: WRITEABLE); } - auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); constexpr bool kRegionSampling = true; constexpr bool kGrayscale = false; diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index a902a8ebde..57661f199a 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -238,7 +238,7 @@ EventThread::~EventThread() = default; namespace impl { -EventThread::EventThread(const char* name, scheduler::VsyncSchedule& vsyncSchedule, +EventThread::EventThread(const char* name, std::shared_ptr<scheduler::VsyncSchedule> vsyncSchedule, android::frametimeline::TokenManager* tokenManager, ThrottleVsyncCallback throttleVsyncCallback, GetVsyncPeriodFunction getVsyncPeriodFunction, @@ -248,13 +248,8 @@ EventThread::EventThread(const char* name, scheduler::VsyncSchedule& vsyncSchedu mVsyncTracer(base::StringPrintf("VSYNC-%s", name), 0), mWorkDuration(base::StringPrintf("VsyncWorkDuration-%s", name), workDuration), mReadyDuration(readyDuration), - mVsyncSchedule(vsyncSchedule), - mVsyncRegistration( - vsyncSchedule.getDispatch(), - [this](nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) { - onVsync(vsyncTime, wakeupTime, readyTime); - }, - name), + mVsyncSchedule(std::move(vsyncSchedule)), + mVsyncRegistration(mVsyncSchedule->getDispatch(), createDispatchCallback(), name), mTokenManager(tokenManager), mThrottleVsyncCallback(std::move(throttleVsyncCallback)), mGetVsyncPeriodFunction(std::move(getVsyncPeriodFunction)) { @@ -375,7 +370,7 @@ VsyncEventData EventThread::getLatestVsyncEventData( vsyncEventData.frameInterval = frameInterval; const auto [presentTime, deadline] = [&]() -> std::pair<nsecs_t, nsecs_t> { std::lock_guard<std::mutex> lock(mMutex); - const auto vsyncTime = mVsyncSchedule.getTracker().nextAnticipatedVSyncTimeFrom( + const auto vsyncTime = mVsyncSchedule->getTracker().nextAnticipatedVSyncTimeFrom( systemTime() + mWorkDuration.get().count() + mReadyDuration.count()); return {vsyncTime, vsyncTime - mReadyDuration.count()}; }(); @@ -384,23 +379,13 @@ VsyncEventData EventThread::getLatestVsyncEventData( return vsyncEventData; } -void EventThread::onScreenReleased() { +void EventThread::enableSyntheticVsync(bool enable) { std::lock_guard<std::mutex> lock(mMutex); - if (!mVSyncState || mVSyncState->synthetic) { + if (!mVSyncState || mVSyncState->synthetic == enable) { return; } - mVSyncState->synthetic = true; - mCondition.notify_all(); -} - -void EventThread::onScreenAcquired() { - std::lock_guard<std::mutex> lock(mMutex); - if (!mVSyncState || !mVSyncState->synthetic) { - return; - } - - mVSyncState->synthetic = false; + mVSyncState->synthetic = enable; mCondition.notify_all(); } @@ -541,6 +526,13 @@ void EventThread::threadMain(std::unique_lock<std::mutex>& lock) { 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); @@ -699,6 +691,26 @@ const char* EventThread::toCString(State state) { } } +void EventThread::onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule> schedule) { + std::lock_guard<std::mutex> lock(mMutex); + const bool reschedule = mVsyncRegistration.cancel() == scheduler::CancelResult::Cancelled; + mVsyncSchedule = std::move(schedule); + mVsyncRegistration = + scheduler::VSyncCallbackRegistration(mVsyncSchedule->getDispatch(), + createDispatchCallback(), mThreadName); + if (reschedule) { + mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(), + .readyDuration = mReadyDuration.count(), + .earliestVsync = mLastVsyncCallbackTime.ns()}); + } +} + +scheduler::VSyncDispatch::Callback EventThread::createDispatchCallback() { + return [this](nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) { + onVsync(vsyncTime, wakeupTime, readyTime); + }; +} + } // namespace impl } // namespace android diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h index ab9085e44a..87e20a0636 100644 --- a/services/surfaceflinger/Scheduler/EventThread.h +++ b/services/surfaceflinger/Scheduler/EventThread.h @@ -87,6 +87,9 @@ public: const uid_t mOwnerUid; const EventRegistrationFlags mEventRegistration; + /** The frame rate set to the attached choreographer. */ + Fps frameRate; + private: virtual void onFirstRef(); EventThread* const mEventThread; @@ -103,11 +106,8 @@ public: virtual sp<EventThreadConnection> createEventConnection( ResyncCallback, EventRegistrationFlags eventRegistration = {}) const = 0; - // called before the screen is turned off from main thread - virtual void onScreenReleased() = 0; - - // called after the screen is turned on from main thread - virtual void onScreenAcquired() = 0; + // Feed clients with fake VSYNC, e.g. while the display is off. + virtual void enableSyntheticVsync(bool) = 0; virtual void onHotplugReceived(PhysicalDisplayId displayId, bool connected) = 0; @@ -133,6 +133,8 @@ public: // Retrieves the number of event connections tracked by this EventThread. virtual size_t getEventThreadConnectionCount() = 0; + + virtual void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) = 0; }; namespace impl { @@ -142,8 +144,8 @@ public: using ThrottleVsyncCallback = std::function<bool(nsecs_t, uid_t)>; using GetVsyncPeriodFunction = std::function<nsecs_t(uid_t)>; - EventThread(const char* name, scheduler::VsyncSchedule&, frametimeline::TokenManager*, - ThrottleVsyncCallback, GetVsyncPeriodFunction, + EventThread(const char* name, std::shared_ptr<scheduler::VsyncSchedule>, + frametimeline::TokenManager*, ThrottleVsyncCallback, GetVsyncPeriodFunction, std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration); ~EventThread(); @@ -156,11 +158,7 @@ public: VsyncEventData getLatestVsyncEventData( const sp<EventThreadConnection>& connection) const override; - // called before the screen is turned off from main thread - void onScreenReleased() override; - - // called after the screen is turned on from main thread - void onScreenAcquired() override; + void enableSyntheticVsync(bool) override; void onHotplugReceived(PhysicalDisplayId displayId, bool connected) override; @@ -176,6 +174,8 @@ public: size_t getEventThreadConnectionCount() override; + void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) override; + private: friend EventThreadTest; @@ -199,11 +199,13 @@ private: nsecs_t timestamp, nsecs_t preferredExpectedPresentationTime, nsecs_t preferredDeadlineTimestamp) const; + scheduler::VSyncDispatch::Callback createDispatchCallback(); + const char* const mThreadName; TracedOrdinal<int> mVsyncTracer; TracedOrdinal<std::chrono::nanoseconds> mWorkDuration GUARDED_BY(mMutex); std::chrono::nanoseconds mReadyDuration GUARDED_BY(mMutex); - scheduler::VsyncSchedule& mVsyncSchedule; + std::shared_ptr<scheduler::VsyncSchedule> mVsyncSchedule; TimePoint mLastVsyncCallbackTime GUARDED_BY(mMutex) = TimePoint::now(); scheduler::VSyncCallbackRegistration mVsyncRegistration GUARDED_BY(mMutex); frametimeline::TokenManager* const mTokenManager; diff --git a/services/surfaceflinger/Scheduler/ISchedulerCallback.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h new file mode 100644 index 0000000000..92c2189244 --- /dev/null +++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.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 <vector> + +#include <ui/DisplayId.h> + +#include "Display/DisplayModeRequest.h" + +namespace android::scheduler { + +struct ISchedulerCallback { + virtual void setVsyncEnabled(PhysicalDisplayId, 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 55fa402fa6..beaf9724a3 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 { @@ -117,29 +118,35 @@ void LayerHistory::deregisterLayer(Layer* layer) { } } -void LayerHistory::record(Layer* layer, nsecs_t presentTime, nsecs_t now, - LayerUpdateType updateType) { +void LayerHistory::record(int32_t id, const LayerProps& layerProps, nsecs_t presentTime, + nsecs_t now, LayerUpdateType updateType) { std::lock_guard lock(mLock); - auto id = layer->getSequence(); - auto [found, layerPair] = findLayer(id); if (found == LayerStatus::NotFound) { // Offscreen layer - ALOGV("%s: %s not registered", __func__, layer->getName().c_str()); + ALOGV("%s: %d not registered", __func__, id); return; } const auto& info = layerPair->second; - const auto layerProps = LayerInfo::LayerProps{ - .visible = layer->isVisible(), - .bounds = layer->getBounds(), - .transform = layer->getTransform(), - .setFrameRateVote = layer->getFrameRateForLayerTree(), - .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(), - }; - 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 = layerProps.setFrameRateVote.rate; + it++; + } else { + it = mAttachedChoreographers.erase(it); + } + } + } + // Activate layer if inactive. if (found == LayerStatus::LayerInInactiveMap) { mActiveLayerInfos.insert( @@ -294,6 +301,12 @@ float LayerHistory::getLayerFramerate(nsecs_t now, int32_t id) const { 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 5022906ff9..69caf9ffd2 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 { @@ -36,6 +38,7 @@ class Layer; namespace scheduler { class LayerInfo; +struct LayerProps; class LayerHistory { public: @@ -61,7 +64,8 @@ public: }; // Marks the layer as active, and records the given state to its history. - void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType); + void record(int32_t id, const LayerProps& props, nsecs_t presentTime, nsecs_t now, + LayerUpdateType updateType); // Updates the default frame rate compatibility which takes effect when the app // does not set a preference for refresh rate. @@ -80,6 +84,9 @@ public: // 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 +124,10 @@ private: 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/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp index 0142ccd93f..5a90d5866e 100644 --- a/services/surfaceflinger/Scheduler/LayerInfo.cpp +++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp @@ -44,14 +44,17 @@ LayerInfo::LayerInfo(const std::string& name, uid_t ownerUid, mOwnerUid(ownerUid), mDefaultVote(defaultVote), mLayerVote({defaultVote, Fps()}), - mRefreshRateHistory(name) {} + mLayerProps(std::make_unique<LayerProps>()), + mRefreshRateHistory(name) { + ; +} void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType, - bool pendingModeChange, LayerProps props) { + bool pendingModeChange, const LayerProps& props) { lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0)); mLastUpdatedTime = std::max(lastPresentTime, now); - mLayerProps = props; + *mLayerProps = props; switch (updateType) { case LayerUpdateType::AnimationTX: mLastAnimationTime = std::max(lastPresentTime, now); @@ -305,6 +308,26 @@ const char* LayerInfo::getTraceTag(LayerHistory::LayerVoteType type) const { return mTraceTags.at(type).c_str(); } +LayerInfo::FrameRate LayerInfo::getSetFrameRateVote() const { + return mLayerProps->setFrameRateVote; +} + +bool LayerInfo::isVisible() const { + return mLayerProps->visible; +} + +int32_t LayerInfo::getFrameRateSelectionPriority() const { + return mLayerProps->frameRateSelectionPriority; +} + +FloatRect LayerInfo::getBounds() const { + return mLayerProps->bounds; +} + +ui::Transform LayerInfo::getTransform() const { + return mLayerProps->transform; +} + LayerInfo::RefreshRateHistory::HeuristicTraceTagData LayerInfo::RefreshRateHistory::makeHeuristicTraceTagData() const { const std::string prefix = "LFPS "; diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h index 93485be0a4..a3523ac25e 100644 --- a/services/surfaceflinger/Scheduler/LayerInfo.h +++ b/services/surfaceflinger/Scheduler/LayerInfo.h @@ -37,7 +37,7 @@ class Layer; namespace scheduler { using namespace std::chrono_literals; - +struct LayerProps; // Maximum period between presents for a layer to be considered active. constexpr std::chrono::nanoseconds MAX_ACTIVE_LAYER_PERIOD_NS = 1200ms; @@ -132,19 +132,11 @@ public: LayerInfo(const LayerInfo&) = delete; LayerInfo& operator=(const LayerInfo&) = delete; - struct LayerProps { - bool visible = false; - FloatRect bounds; - ui::Transform transform; - FrameRate setFrameRateVote; - int32_t frameRateSelectionPriority = -1; - }; - // Records the last requested present time. It also stores information about when // the layer was last updated. If the present time is farther in the future than the // updated time, the updated time is the present time. void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType, - bool pendingModeChange, LayerProps props); + bool pendingModeChange, const LayerProps& props); // Sets an explicit layer vote. This usually comes directly from the application via // ANativeWindow_setFrameRate API @@ -168,13 +160,11 @@ public: // updated time, the updated time is the present time. nsecs_t getLastUpdatedTime() const { return mLastUpdatedTime; } - FrameRate getSetFrameRateVote() const { return mLayerProps.setFrameRateVote; } - bool isVisible() const { return mLayerProps.visible; } - int32_t getFrameRateSelectionPriority() const { return mLayerProps.frameRateSelectionPriority; } - - FloatRect getBounds() const { return mLayerProps.bounds; } - - ui::Transform getTransform() const { return mLayerProps.transform; } + FrameRate getSetFrameRateVote() const; + bool isVisible() const; + int32_t getFrameRateSelectionPriority() const; + FloatRect getBounds() const; + ui::Transform getTransform() const; // Returns a C string for tracing a vote const char* getTraceTag(LayerHistory::LayerVoteType type) const; @@ -294,7 +284,7 @@ private: static constexpr size_t HISTORY_SIZE = RefreshRateHistory::HISTORY_SIZE; static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s; - LayerProps mLayerProps; + std::unique_ptr<LayerProps> mLayerProps; RefreshRateHistory mRefreshRateHistory; @@ -304,5 +294,13 @@ private: static bool sTraceEnabled; }; +struct LayerProps { + bool visible = false; + FloatRect bounds; + ui::Transform transform; + LayerInfo::FrameRate setFrameRateVote; + int32_t frameRateSelectionPriority = -1; +}; + } // namespace scheduler } // namespace android diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp index dec8f59ee9..7457b84011 100644 --- a/services/surfaceflinger/Scheduler/MessageQueue.cpp +++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp @@ -75,19 +75,36 @@ void MessageQueue::vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, ns mHandler->dispatchFrame(vsyncId, expectedVsyncTime); } -void MessageQueue::initVsync(scheduler::VSyncDispatch& dispatch, +void MessageQueue::initVsync(std::shared_ptr<scheduler::VSyncDispatch> dispatch, frametimeline::TokenManager& tokenManager, std::chrono::nanoseconds workDuration) { std::lock_guard lock(mVsync.mutex); mVsync.workDuration = workDuration; mVsync.tokenManager = &tokenManager; + onNewVsyncScheduleLocked(std::move(dispatch)); +} + +void MessageQueue::onNewVsyncSchedule(std::shared_ptr<scheduler::VSyncDispatch> dispatch) { + std::lock_guard lock(mVsync.mutex); + onNewVsyncScheduleLocked(std::move(dispatch)); +} + +void MessageQueue::onNewVsyncScheduleLocked(std::shared_ptr<scheduler::VSyncDispatch> dispatch) { + const bool reschedule = mVsync.registration && + mVsync.registration->cancel() == scheduler::CancelResult::Cancelled; mVsync.registration = std::make_unique< - scheduler::VSyncCallbackRegistration>(dispatch, + scheduler::VSyncCallbackRegistration>(std::move(dispatch), std::bind(&MessageQueue::vsyncCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), "sf"); + if (reschedule) { + mVsync.scheduledFrameTime = + mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(), + .readyDuration = 0, + .earliestVsync = mVsync.lastCallbackTime.ns()}); + } } void MessageQueue::destroyVsync() { diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h index 0d59337950..9c9b2f35c0 100644 --- a/services/surfaceflinger/Scheduler/MessageQueue.h +++ b/services/surfaceflinger/Scheduler/MessageQueue.h @@ -65,7 +65,7 @@ class MessageQueue { public: virtual ~MessageQueue() = default; - virtual void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&, + virtual void initVsync(std::shared_ptr<scheduler::VSyncDispatch>, frametimeline::TokenManager&, std::chrono::nanoseconds workDuration) = 0; virtual void destroyVsync() = 0; virtual void setDuration(std::chrono::nanoseconds workDuration) = 0; @@ -106,6 +106,8 @@ protected: void vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, nsecs_t readyTime); + void onNewVsyncSchedule(std::shared_ptr<scheduler::VSyncDispatch>) EXCLUDES(mVsync.mutex); + private: virtual void onFrameSignal(ICompositor&, VsyncId, TimePoint expectedVsyncTime) = 0; @@ -127,10 +129,12 @@ private: Vsync mVsync; + void onNewVsyncScheduleLocked(std::shared_ptr<scheduler::VSyncDispatch>) REQUIRES(mVsync.mutex); + public: explicit MessageQueue(ICompositor&); - void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&, + void initVsync(std::shared_ptr<scheduler::VSyncDispatch>, frametimeline::TokenManager&, std::chrono::nanoseconds workDuration) override; void destroyVsync() override; void setDuration(std::chrono::nanoseconds workDuration) override; diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.h b/services/surfaceflinger/Scheduler/OneShotTimer.h index f95646c48f..02e8719c08 100644 --- a/services/surfaceflinger/Scheduler/OneShotTimer.h +++ b/services/surfaceflinger/Scheduler/OneShotTimer.h @@ -40,7 +40,7 @@ public: OneShotTimer(std::string name, const Interval& interval, const ResetCallback& resetCallback, const TimeoutCallback& timeoutCallback, - std::unique_ptr<Clock> clock = std::make_unique<SteadyClock>()); + std::unique_ptr<android::Clock> clock = std::make_unique<SteadyClock>()); ~OneShotTimer(); Duration interval() const { return mInterval; } @@ -82,7 +82,7 @@ private: std::thread mThread; // Clock object for the timer. Mocked in unit tests. - std::unique_ptr<Clock> mClock; + std::unique_ptr<android::Clock> mClock; // Semaphore to keep mThread synchronized. sem_t mSemaphore; diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index c5b3e14047..f6fe468dd6 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -238,7 +238,6 @@ struct RefreshRateSelector::RefreshRateScoreComparator { 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; diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h index 4f5842a67a..5052e6e257 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h @@ -32,7 +32,6 @@ #include <scheduler/Seamlessness.h> #include "DisplayHardware/DisplayMode.h" -#include "DisplayHardware/HWComposer.h" #include "Scheduler/OneShotTimer.h" #include "Scheduler/StrongTyping.h" #include "ThreadContext.h" @@ -297,6 +296,8 @@ public: RefreshRateSelector(const RefreshRateSelector&) = delete; RefreshRateSelector& operator=(const RefreshRateSelector&) = delete; + const DisplayModes& displayModes() const { return mDisplayModes; } + // Returns whether switching modes (refresh rate or resolution) is possible. // TODO(b/158780872): Consider HAL support, and skip frame rate detection if the modes only // differ in resolution. Once Config::FrameRateOverride::Enabled becomes the default, diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 33c98ff6d5..f18dfdceb6 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -28,10 +28,10 @@ #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> @@ -47,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" @@ -80,7 +81,7 @@ Scheduler::~Scheduler() { mTouchTimer.reset(); // Stop idle timer and clear callbacks, as the RefreshRateSelector may outlive the Scheduler. - demoteLeaderDisplay(); + demotePacesetterDisplay(); } void Scheduler::startTimers() { @@ -105,34 +106,43 @@ void Scheduler::startTimers() { } } -void Scheduler::setLeaderDisplay(std::optional<PhysicalDisplayId> leaderIdOpt) { - demoteLeaderDisplay(); +void Scheduler::setPacesetterDisplay(std::optional<PhysicalDisplayId> pacesetterIdOpt) { + demotePacesetterDisplay(); std::scoped_lock lock(mDisplayLock); - promoteLeaderDisplay(leaderIdOpt); + promotePacesetterDisplay(pacesetterIdOpt); } void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) { - demoteLeaderDisplay(); + registerDisplayInternal(displayId, std::move(selectorPtr), + std::make_shared<VsyncSchedule>(displayId, mFeatures)); +} + +void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId, + RefreshRateSelectorPtr selectorPtr, + std::shared_ptr<VsyncSchedule> vsyncSchedule) { + demotePacesetterDisplay(); std::scoped_lock lock(mDisplayLock); mRefreshRateSelectors.emplace_or_replace(displayId, std::move(selectorPtr)); + mVsyncSchedules.emplace_or_replace(displayId, std::move(vsyncSchedule)); - promoteLeaderDisplay(); + promotePacesetterDisplay(); } void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { - demoteLeaderDisplay(); + demotePacesetterDisplay(); std::scoped_lock lock(mDisplayLock); mRefreshRateSelectors.erase(displayId); + mVsyncSchedules.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(); + promotePacesetterDisplay(); } void Scheduler::run() { @@ -153,13 +163,9 @@ void Scheduler::onFrameSignal(ICompositor& compositor, VsyncId vsyncId, compositor.sample(); } -void Scheduler::createVsyncSchedule(FeatureFlags features) { - mVsyncSchedule.emplace(features); -} - std::optional<Fps> Scheduler::getFrameRateOverride(uid_t uid) const { const bool supportsFrameRateOverrideByContent = - leaderSelectorPtr()->supportsAppFrameRateOverrideByContent(); + pacesetterSelectorPtr()->supportsAppFrameRateOverrideByContent(); return mFrameRateOverrideMappings .getFrameRateOverrideForUid(uid, supportsFrameRateOverrideByContent); } @@ -170,11 +176,12 @@ bool Scheduler::isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const return true; } - return mVsyncSchedule->getTracker().isVSyncInPhase(expectedVsyncTimestamp.ns(), *frameRate); + ATRACE_FORMAT("%s uid: %d frameRate: %s", __func__, uid, to_string(*frameRate).c_str()); + return getVsyncSchedule()->getTracker().isVSyncInPhase(expectedVsyncTimestamp.ns(), *frameRate); } bool Scheduler::isVsyncInPhase(TimePoint timePoint, const Fps frameRate) const { - return mVsyncSchedule->getTracker().isVSyncInPhase(timePoint.ns(), frameRate); + return getVsyncSchedule()->getTracker().isVSyncInPhase(timePoint.ns(), frameRate); } impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() const { @@ -185,8 +192,9 @@ impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const { return [this](uid_t uid) { - const Fps refreshRate = leaderSelectorPtr()->getActiveMode().fps; - const nsecs_t currentPeriod = mVsyncSchedule->period().ns() ?: refreshRate.getPeriodNsecs(); + const Fps refreshRate = pacesetterSelectorPtr()->getActiveMode().fps; + const nsecs_t currentPeriod = + getVsyncSchedule()->period().ns() ?: refreshRate.getPeriodNsecs(); const auto frameRate = getFrameRateOverride(uid); if (!frameRate.has_value()) { @@ -206,7 +214,7 @@ ConnectionHandle Scheduler::createEventThread(Cycle cycle, std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration) { auto eventThread = std::make_unique<impl::EventThread>(cycle == Cycle::Render ? "app" : "appSf", - *mVsyncSchedule, tokenManager, + getVsyncSchedule(), tokenManager, makeThrottleVsyncCallback(), makeGetVsyncPeriodFunction(), workDuration, readyDuration); @@ -228,15 +236,21 @@ ConnectionHandle Scheduler::createConnection(std::unique_ptr<EventThread> eventT } 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) { @@ -257,31 +271,21 @@ void Scheduler::onHotplugReceived(ConnectionHandle handle, PhysicalDisplayId dis thread->onHotplugReceived(displayId, connected); } -void Scheduler::onScreenAcquired(ConnectionHandle handle) { +void Scheduler::enableSyntheticVsync(bool enable) { + // TODO(b/241285945): Remove connection handles. + const ConnectionHandle handle = mAppConnectionHandle; android::EventThread* thread; { std::lock_guard<std::mutex> lock(mConnectionsLock); RETURN_IF_INVALID_HANDLE(handle); thread = mConnections[handle].thread.get(); } - thread->onScreenAcquired(); - mScreenAcquired = true; -} - -void Scheduler::onScreenReleased(ConnectionHandle handle) { - android::EventThread* thread; - { - std::lock_guard<std::mutex> lock(mConnectionsLock); - RETURN_IF_INVALID_HANDLE(handle); - thread = mConnections[handle].thread.get(); - } - thread->onScreenReleased(); - mScreenAcquired = false; + thread->enableSyntheticVsync(enable); } void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId) { const bool supportsFrameRateOverrideByContent = - leaderSelectorPtr()->supportsAppFrameRateOverrideByContent(); + pacesetterSelectorPtr()->supportsAppFrameRateOverrideByContent(); std::vector<FrameRateOverride> overrides = mFrameRateOverrideMappings.getAllFrameRateOverrides(supportsFrameRateOverrideByContent); @@ -322,7 +326,7 @@ void Scheduler::dispatchCachedReportedMode() { // If the mode is not the current mode, this means that a // mode change is in progress. In that case we shouldn't dispatch an event // as it will be dispatched when the current mode changes. - if (leaderSelectorPtr()->getActiveMode() != mPolicy.modeOpt) { + if (pacesetterSelectorPtr()->getActiveMode() != mPolicy.modeOpt) { return; } @@ -387,57 +391,59 @@ void Scheduler::setVsyncConfig(const VsyncConfig& config, Period vsyncPeriod) { setDuration(config.sfWorkDuration); } -void Scheduler::enableHardwareVsync() { - std::lock_guard<std::mutex> lock(mHWVsyncLock); - if (!mPrimaryHWVsyncEnabled && mHWVsyncAvailable) { - mVsyncSchedule->getTracker().resetModel(); - mSchedulerCallback.setVsyncEnabled(true); - mPrimaryHWVsyncEnabled = true; - } +void Scheduler::enableHardwareVsync(PhysicalDisplayId id) { + auto schedule = getVsyncSchedule(id); + schedule->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(PhysicalDisplayId id, bool disallow) { + auto schedule = getVsyncSchedule(id); + schedule->disableHardwareVsync(mSchedulerCallback, disallow); +} + +void Scheduler::resyncAllToHardwareVsync(bool allowToEnable) { + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + + for (const auto& [id, _] : mRefreshRateSelectors) { + resyncToHardwareVsyncLocked(id, allowToEnable); } } -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::resyncToHardwareVsyncLocked(PhysicalDisplayId id, bool allowToEnable, + std::optional<Fps> refreshRate) { + auto schedule = getVsyncScheduleLocked(id); + if (schedule->isHardwareVsyncAllowed(allowToEnable)) { + if (!refreshRate) { + auto selectorPtr = mRefreshRateSelectors.get(id); + LOG_ALWAYS_FATAL_IF(!selectorPtr); + refreshRate = selectorPtr->get()->getActiveMode().modePtr->getFps(); + } + if (refreshRate->isValid()) { + schedule->startPeriodTransition(mSchedulerCallback, refreshRate->getPeriod(), + false /* force */); } } - - setVsyncPeriod(refreshRate.getPeriodNsecs()); } -void Scheduler::setRenderRate(Fps renderFrameRate) { - const auto mode = leaderSelectorPtr()->getActiveMode(); +void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate) { + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + + auto selectorPtr = mRefreshRateSelectors.get(id); + LOG_ALWAYS_FATAL_IF(!selectorPtr); + const auto mode = selectorPtr->get()->getActiveMode(); using fps_approx_ops::operator!=; LOG_ALWAYS_FATAL_IF(renderFrameRate != mode.fps, - "Mismatch in render frame rates. Selector: %s, Scheduler: %s", - to_string(mode.fps).c_str(), to_string(renderFrameRate).c_str()); + "Mismatch in render frame rates. Selector: %s, Scheduler: %s, Display: " + "%" PRIu64, + to_string(mode.fps).c_str(), to_string(renderFrameRate).c_str(), id.value); ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(), to_string(mode.modePtr->getFps()).c_str()); - const auto divisor = RefreshRateSelector::getFrameRateDivisor(mode.modePtr->getFps(), mode.fps); - LOG_ALWAYS_FATAL_IF(divisor == 0, "%s <> %s -- not divisors", to_string(mode.fps).c_str(), - to_string(mode.fps).c_str()); - - mVsyncSchedule->getTracker().setDivisor(static_cast<unsigned>(divisor)); + getVsyncScheduleLocked(id)->getTracker().setRenderRate(renderFrameRate); } void Scheduler::resync() { @@ -447,49 +453,26 @@ void Scheduler::resync() { const nsecs_t last = mLastResyncTime.exchange(now); if (now - last > kIgnoreDelay) { - const auto refreshRate = leaderSelectorPtr()->getActiveMode().modePtr->getFps(); - resyncToHardwareVsync(false, refreshRate); - } -} - -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; + resyncAllToHardwareVsync(false /* allowToEnable */); } } -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(PhysicalDisplayId id, nsecs_t timestamp, + std::optional<nsecs_t> hwcVsyncPeriodIn) { + const auto hwcVsyncPeriod = ftl::Optional(hwcVsyncPeriodIn).transform([](nsecs_t nanos) { + return Period::fromNs(nanos); + }); + return getVsyncSchedule(id)->addResyncSample(mSchedulerCallback, TimePoint::fromNs(timestamp), + hwcVsyncPeriod); } -void Scheduler::addPresentFence(std::shared_ptr<FenceTime> fence) { - if (mVsyncSchedule->getController().addPresentFence(std::move(fence))) { - enableHardwareVsync(); +void Scheduler::addPresentFence(PhysicalDisplayId id, std::shared_ptr<FenceTime> fence) { + auto schedule = getVsyncSchedule(id); + const bool needMoreSignals = schedule->getController().addPresentFence(std::move(fence)); + if (needMoreSignals) { + schedule->enableHardwareVsync(mSchedulerCallback); } else { - disableHardwareVsync(false); + schedule->disableHardwareVsync(mSchedulerCallback, false /* disallow */); } } @@ -504,10 +487,10 @@ void Scheduler::deregisterLayer(Layer* layer) { mLayerHistory.deregisterLayer(layer); } -void Scheduler::recordLayerHistory(Layer* layer, nsecs_t presentTime, +void Scheduler::recordLayerHistory(int32_t id, const LayerProps& layerProps, nsecs_t presentTime, LayerHistory::LayerUpdateType updateType) { - if (leaderSelectorPtr()->canSwitch()) { - mLayerHistory.record(layer, presentTime, systemTime(), updateType); + if (pacesetterSelectorPtr()->canSwitch()) { + mLayerHistory.record(id, layerProps, presentTime, systemTime(), updateType); } } @@ -521,7 +504,7 @@ void Scheduler::setDefaultFrameRateCompatibility(Layer* layer) { } void Scheduler::chooseRefreshRateForContent() { - const auto selectorPtr = leaderSelectorPtr(); + const auto selectorPtr = pacesetterSelectorPtr(); if (!selectorPtr->canSwitch()) return; ATRACE_CALL(); @@ -531,22 +514,32 @@ void Scheduler::chooseRefreshRateForContent() { } void Scheduler::resetIdleTimer() { - leaderSelectorPtr()->resetIdleTimer(); + pacesetterSelectorPtr()->resetIdleTimer(); } void Scheduler::onTouchHint() { if (mTouchTimer) { mTouchTimer->reset(); - leaderSelectorPtr()->resetKernelIdleTimer(); + pacesetterSelectorPtr()->resetKernelIdleTimer(); } } -void Scheduler::setDisplayPowerMode(hal::PowerMode powerMode) { - { +void Scheduler::setDisplayPowerMode(PhysicalDisplayId id, hal::PowerMode powerMode) { + const bool isPacesetter = [this, id]() REQUIRES(kMainThreadContext) { + ftl::FakeGuard guard(mDisplayLock); + return id == mPacesetterDisplayId; + }(); + if (isPacesetter) { + // TODO (b/255657128): This needs to be handled per display. std::lock_guard<std::mutex> lock(mPolicyLock); mPolicy.displayPowerMode = powerMode; } - mVsyncSchedule->getController().setDisplayPowerMode(powerMode); + { + std::scoped_lock lock(mDisplayLock); + auto vsyncSchedule = getVsyncScheduleLocked(id); + vsyncSchedule->getController().setDisplayPowerMode(powerMode); + } + if (!isPacesetter) return; if (mDisplayPowerTimer) { mDisplayPowerTimer->reset(); @@ -557,12 +550,30 @@ void Scheduler::setDisplayPowerMode(hal::PowerMode powerMode) { mLayerHistory.clear(); } +std::shared_ptr<const VsyncSchedule> Scheduler::getVsyncSchedule( + std::optional<PhysicalDisplayId> idOpt) const { + std::scoped_lock lock(mDisplayLock); + return getVsyncScheduleLocked(idOpt); +} + +std::shared_ptr<const VsyncSchedule> Scheduler::getVsyncScheduleLocked( + std::optional<PhysicalDisplayId> idOpt) const { + ftl::FakeGuard guard(kMainThreadContext); + if (!idOpt) { + LOG_ALWAYS_FATAL_IF(!mPacesetterDisplayId, "Missing a pacesetter!"); + idOpt = mPacesetterDisplayId; + } + auto scheduleOpt = mVsyncSchedules.get(*idOpt); + LOG_ALWAYS_FATAL_IF(!scheduleOpt); + return std::const_pointer_cast<const VsyncSchedule>(scheduleOpt->get()); +} + void Scheduler::kernelIdleTimerCallback(TimerState state) { ATRACE_INT("ExpiredKernelIdleTimer", static_cast<int>(state)); // TODO(145561154): cleanup the kernel idle timer implementation and the refresh rate // magic number - const Fps refreshRate = leaderSelectorPtr()->getActiveMode().modePtr->getFps(); + const Fps refreshRate = pacesetterSelectorPtr()->getActiveMode().modePtr->getFps(); constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER = 65_Hz; using namespace fps_approx_ops; @@ -571,12 +582,17 @@ void Scheduler::kernelIdleTimerCallback(TimerState state) { // If we're not in performance mode then the kernel timer shouldn't do // anything, as the refresh rate during DPU power collapse will be the // same. - resyncToHardwareVsync(true /* makeAvailable */, refreshRate); + resyncAllToHardwareVsync(true /* allowToEnable */); } else if (state == TimerState::Expired && refreshRate <= FPS_THRESHOLD_FOR_KERNEL_TIMER) { // Disable HW VSYNC if the timer expired, as we don't need it enabled if // we're not pushing frames, and if we're in PERFORMANCE mode then we'll // need to update the VsyncController model anyway. - disableHardwareVsync(false /* makeUnavailable */); + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + constexpr bool disallow = false; + for (auto& [_, schedule] : mVsyncSchedules) { + schedule->disableHardwareVsync(mSchedulerCallback, disallow); + } } mSchedulerCallback.kernelTimerChanged(state == TimerState::Expired); @@ -621,7 +637,7 @@ void Scheduler::dump(utils::Dumper& dumper) const { { std::scoped_lock lock(mDisplayLock); ftl::FakeGuard guard(kMainThreadContext); - dumper.dump("leaderDisplayId"sv, mLeaderDisplayId); + dumper.dump("pacesetterDisplayId"sv, mPacesetterDisplayId); } dumper.dump("layerHistory"sv, mLayerHistory.dump()); dumper.dump("touchTimer"sv, mTouchTimer.transform(&OneShotTimer::interval)); @@ -630,58 +646,75 @@ void Scheduler::dump(utils::Dumper& dumper) const { 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 { - mVsyncSchedule->dump(out); + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + if (mPacesetterDisplayId) { + base::StringAppendF(&out, "VsyncSchedule for pacesetter %s:\n", + to_string(*mPacesetterDisplayId).c_str()); + getVsyncScheduleLocked()->dump(out); + } + for (auto& [id, vsyncSchedule] : mVsyncSchedules) { + if (id == mPacesetterDisplayId) { + continue; + } + base::StringAppendF(&out, "VsyncSchedule for follower %s:\n", to_string(id).c_str()); + vsyncSchedule->dump(out); + } } bool Scheduler::updateFrameRateOverrides(GlobalSignals consideredSignals, Fps displayRefreshRate) { if (consideredSignals.idle) return false; const auto frameRateOverrides = - leaderSelectorPtr()->getFrameRateOverrides(mPolicy.contentRequirements, - displayRefreshRate, consideredSignals); + pacesetterSelectorPtr()->getFrameRateOverrides(mPolicy.contentRequirements, + displayRefreshRate, consideredSignals); // Note that RefreshRateSelector::supportsFrameRateOverrideByContent is checked when querying // the FrameRateOverrideMappings rather than here. return mFrameRateOverrideMappings.updateFrameRateOverridesByContent(frameRateOverrides); } -void Scheduler::promoteLeaderDisplay(std::optional<PhysicalDisplayId> leaderIdOpt) { - // TODO(b/241286431): Choose the leader display. - mLeaderDisplayId = leaderIdOpt.value_or(mRefreshRateSelectors.begin()->first); - ALOGI("Display %s is the leader", to_string(*mLeaderDisplayId).c_str()); +void Scheduler::promotePacesetterDisplay(std::optional<PhysicalDisplayId> pacesetterIdOpt) { + // TODO(b/241286431): Choose the pacesetter display. + mPacesetterDisplayId = pacesetterIdOpt.value_or(mRefreshRateSelectors.begin()->first); + ALOGI("Display %s is the pacesetter", to_string(*mPacesetterDisplayId).c_str()); - if (const auto leaderPtr = leaderSelectorPtrLocked()) { - leaderPtr->setIdleTimerCallbacks( + auto vsyncSchedule = getVsyncScheduleLocked(*mPacesetterDisplayId); + if (const auto pacesetterPtr = pacesetterSelectorPtrLocked()) { + pacesetterPtr->setIdleTimerCallbacks( {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); }, .onExpired = [this] { idleTimerCallback(TimerState::Expired); }}, .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); }, .onExpired = [this] { kernelIdleTimerCallback(TimerState::Expired); }}}); - leaderPtr->startIdleTimer(); + pacesetterPtr->startIdleTimer(); + + const Fps refreshRate = pacesetterPtr->getActiveMode().modePtr->getFps(); + vsyncSchedule->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod(), + true /* force */); + } + + onNewVsyncSchedule(vsyncSchedule->getDispatch()); + { + std::lock_guard<std::mutex> lock(mConnectionsLock); + for (auto& [_, connection] : mConnections) { + connection.thread->onNewVsyncSchedule(vsyncSchedule); + } } } -void Scheduler::demoteLeaderDisplay() { +void Scheduler::demotePacesetterDisplay() { // No need to lock for reads on kMainThreadContext. - if (const auto leaderPtr = FTL_FAKE_GUARD(mDisplayLock, leaderSelectorPtrLocked())) { - leaderPtr->stopIdleTimer(); - leaderPtr->clearIdleTimerCallbacks(); + if (const auto pacesetterPtr = FTL_FAKE_GUARD(mDisplayLock, pacesetterSelectorPtrLocked())) { + pacesetterPtr->stopIdleTimer(); + pacesetterPtr->clearIdleTimerCallbacks(); } - // Clear state that depends on the leader's RefreshRateSelector. + // Clear state that depends on the pacesetter's RefreshRateSelector. std::scoped_lock lock(mPolicyLock); mPolicy = {}; } @@ -710,10 +743,11 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals modeChoices = chooseDisplayModes(); - // TODO(b/240743786): The leader display's mode must change for any DisplayModeRequest - // to go through. Fix this by tracking per-display Scheduler::Policy and timers. + // TODO(b/240743786): The pacesetter display's mode must change for any + // DisplayModeRequest to go through. Fix this by tracking per-display Scheduler::Policy + // and timers. std::tie(modeOpt, consideredSignals) = - modeChoices.get(*mLeaderDisplayId) + modeChoices.get(*mPacesetterDisplayId) .transform([](const DisplayModeChoice& choice) { return std::make_pair(choice.mode, choice.consideredSignals); }) @@ -846,7 +880,7 @@ GlobalSignals Scheduler::makeGlobalSignals() const { FrameRateMode Scheduler::getPreferredDisplayMode() { std::lock_guard<std::mutex> lock(mPolicyLock); const auto frameRateMode = - leaderSelectorPtr() + pacesetterSelectorPtr() ->getRankedFrameRates(mPolicy.contentRequirements, makeGlobalSignals()) .ranking.front() .frameRateMode; diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index e8224481c6..62a5fb2ef1 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 @@ namespace scheduler { 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; @@ -111,8 +102,8 @@ public: void startTimers(); - // TODO(b/241285191): Remove this API by promoting leader in onScreen{Acquired,Released}. - void setLeaderDisplay(std::optional<PhysicalDisplayId>) REQUIRES(kMainThreadContext) + // TODO(b/241285191): Remove this API by promoting pacesetter in onScreen{Acquired,Released}. + void setPacesetterDisplay(std::optional<PhysicalDisplayId>) REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); using RefreshRateSelectorPtr = std::shared_ptr<RefreshRateSelector>; @@ -123,8 +114,6 @@ public: void run(); - void createVsyncSchedule(FeatureFlags); - using Impl::initVsync; using Impl::getScheduledFrameTime; @@ -158,15 +147,16 @@ public: std::chrono::nanoseconds readyDuration); sp<IDisplayEventConnection> createDisplayEventConnection( - ConnectionHandle, EventRegistrationFlags eventRegistration = {}); + ConnectionHandle, EventRegistrationFlags eventRegistration = {}, + const sp<IBinder>& layerHandle = nullptr); sp<EventThreadConnection> getEventConnection(ConnectionHandle); void onHotplugReceived(ConnectionHandle, PhysicalDisplayId, bool connected); void onPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&) EXCLUDES(mPolicyLock); void onNonPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&); - void onScreenAcquired(ConnectionHandle); - void onScreenReleased(ConnectionHandle); + + void enableSyntheticVsync(bool = true) REQUIRES(kMainThreadContext); void onFrameRateOverridesChanged(ConnectionHandle, PhysicalDisplayId) EXCLUDES(mConnectionsLock); @@ -177,40 +167,60 @@ public: const VsyncModulator& vsyncModulator() const { return *mVsyncModulator; } + // In some cases, we should only modulate for the pacesetter display. In those + // cases, the caller should pass in the relevant display, and the method + // will no-op if it's not the pacesetter. Other cases are not specific to a + // display. template <typename... Args, typename Handler = std::optional<VsyncConfig> (VsyncModulator::*)(Args...)> - void modulateVsync(Handler handler, Args... args) { + void modulateVsync(std::optional<PhysicalDisplayId> id, Handler handler, Args... args) { + if (id) { + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + if (id != mPacesetterDisplayId) { + return; + } + } + if (const auto config = (*mVsyncModulator.*handler)(args...)) { - setVsyncConfig(*config, getLeaderVsyncPeriod()); + setVsyncConfig(*config, getPacesetterVsyncPeriod()); } } void setVsyncConfigSet(const VsyncConfigSet&, Period vsyncPeriod); // Sets the render rate for the scheduler to run at. - void setRenderRate(Fps); + void setRenderRate(PhysicalDisplayId, Fps); - void enableHardwareVsync(); - void disableHardwareVsync(bool makeUnavailable); + void enableHardwareVsync(PhysicalDisplayId); + void disableHardwareVsync(PhysicalDisplayId, 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); + // If refreshRate is nullopt, use the existing refresh rate of the display. + void resyncToHardwareVsync(PhysicalDisplayId id, bool allowToEnable, + std::optional<Fps> refreshRate = std::nullopt) + EXCLUDES(mDisplayLock) { + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + resyncToHardwareVsyncLocked(id, allowToEnable, 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); - void addPresentFence(std::shared_ptr<FenceTime>); + // Passes a vsync sample to VsyncController. Returns true if + // VsyncController detected that the vsync period changed and false + // otherwise. + bool addResyncSample(PhysicalDisplayId, nsecs_t timestamp, + std::optional<nsecs_t> hwcVsyncPeriod); + void addPresentFence(PhysicalDisplayId, std::shared_ptr<FenceTime>) EXCLUDES(mDisplayLock); // Layers are registered on creation, and unregistered when the weak reference expires. void registerLayer(Layer*); - void recordLayerHistory(Layer*, nsecs_t presentTime, LayerHistory::LayerUpdateType) - EXCLUDES(mDisplayLock); + void recordLayerHistory(int32_t id, const LayerProps& layerProps, nsecs_t presentTime, + LayerHistory::LayerUpdateType) EXCLUDES(mDisplayLock); void setModeChangePending(bool pending); void setDefaultFrameRateCompatibility(Layer*); void deregisterLayer(Layer*); @@ -223,22 +233,28 @@ public: // Indicates that touch interaction is taking place. void onTouchHint(); - void setDisplayPowerMode(hal::PowerMode powerMode); + void setDisplayPowerMode(PhysicalDisplayId, hal::PowerMode powerMode) + REQUIRES(kMainThreadContext); - VsyncSchedule& getVsyncSchedule() { return *mVsyncSchedule; } + std::shared_ptr<const VsyncSchedule> getVsyncSchedule( + std::optional<PhysicalDisplayId> idOpt = std::nullopt) const EXCLUDES(mDisplayLock); + std::shared_ptr<VsyncSchedule> getVsyncSchedule( + std::optional<PhysicalDisplayId> idOpt = std::nullopt) EXCLUDES(mDisplayLock) { + return std::const_pointer_cast<VsyncSchedule>( + static_cast<const Scheduler*>(this)->getVsyncSchedule(idOpt)); + } // Returns true if a given vsync timestamp is considered valid vsync // 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; + bool isVsyncInPhase(TimePoint expectedVsyncTime, Fps frameRate) const; void dump(utils::Dumper&) const; void dump(ConnectionHandle, std::string&) const; - void dumpVsync(std::string&) const; + void dumpVsync(std::string&) const EXCLUDES(mDisplayLock); - // Returns the preferred refresh rate and frame rate for the leader display. + // Returns the preferred refresh rate and frame rate for the pacesetter display. FrameRateMode getPreferredDisplayMode(); // Notifies the scheduler about a refresh rate timeline change. @@ -261,12 +277,12 @@ public: // Retrieves the overridden refresh rate for a given uid. std::optional<Fps> getFrameRateOverride(uid_t) const EXCLUDES(mDisplayLock); - Period getLeaderVsyncPeriod() const EXCLUDES(mDisplayLock) { - return leaderSelectorPtr()->getActiveMode().fps.getPeriod(); + Period getPacesetterVsyncPeriod() const EXCLUDES(mDisplayLock) { + return pacesetterSelectorPtr()->getActiveMode().fps.getPeriod(); } - Fps getLeaderRefreshRate() const EXCLUDES(mDisplayLock) { - return leaderSelectorPtr()->getActiveMode().fps; + Fps getPacesetterRefreshRate() const EXCLUDES(mDisplayLock) { + return pacesetterSelectorPtr()->getActiveMode().fps; } // Returns the framerate of the layer with the given sequence ID @@ -287,7 +303,8 @@ private: // 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); @@ -295,17 +312,24 @@ private: void touchTimerCallback(TimerState); void displayPowerTimerCallback(TimerState); - void setVsyncPeriod(nsecs_t period); + void resyncToHardwareVsyncLocked(PhysicalDisplayId, bool allowToEnable, + std::optional<Fps> refreshRate = std::nullopt) + REQUIRES(kMainThreadContext, mDisplayLock); + void resyncAllToHardwareVsync(bool allowToEnable) EXCLUDES(mDisplayLock); void setVsyncConfig(const VsyncConfig&, Period vsyncPeriod); - // Chooses a leader among the registered displays, unless `leaderIdOpt` is specified. The new - // `mLeaderDisplayId` is never `std::nullopt`. - void promoteLeaderDisplay(std::optional<PhysicalDisplayId> leaderIdOpt = std::nullopt) + // Chooses a pacesetter among the registered displays, unless `pacesetterIdOpt` is specified. + // The new `mPacesetterDisplayId` is never `std::nullopt`. + void promotePacesetterDisplay(std::optional<PhysicalDisplayId> pacesetterIdOpt = std::nullopt) REQUIRES(kMainThreadContext, mDisplayLock); - // Blocks until the leader's idle timer thread exits. `mDisplayLock` must not be locked by the - // caller on the main thread to avoid deadlock, since the timer thread locks it before exit. - void demoteLeaderDisplay() REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock, mPolicyLock); + // Blocks until the pacesetter's idle timer thread exits. `mDisplayLock` must not be locked by + // the caller on the main thread to avoid deadlock, since the timer thread locks it before exit. + void demotePacesetterDisplay() REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock, mPolicyLock); + + void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr, + std::shared_ptr<VsyncSchedule>) REQUIRES(kMainThreadContext) + EXCLUDES(mDisplayLock); struct Policy; @@ -360,14 +384,9 @@ private: 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; // Shifts the VSYNC phase during certain transactions and refresh rate changes. const sp<VsyncModulator> mVsyncModulator; @@ -392,23 +411,35 @@ private: display::PhysicalDisplayMap<PhysicalDisplayId, RefreshRateSelectorPtr> mRefreshRateSelectors GUARDED_BY(mDisplayLock) GUARDED_BY(kMainThreadContext); - ftl::Optional<PhysicalDisplayId> mLeaderDisplayId GUARDED_BY(mDisplayLock) + // TODO (b/266715559): Store in the same map as mRefreshRateSelectors. + display::PhysicalDisplayMap<PhysicalDisplayId, std::shared_ptr<VsyncSchedule>> mVsyncSchedules + GUARDED_BY(mDisplayLock) GUARDED_BY(kMainThreadContext); + + ftl::Optional<PhysicalDisplayId> mPacesetterDisplayId GUARDED_BY(mDisplayLock) GUARDED_BY(kMainThreadContext); - RefreshRateSelectorPtr leaderSelectorPtr() const EXCLUDES(mDisplayLock) { + RefreshRateSelectorPtr pacesetterSelectorPtr() const EXCLUDES(mDisplayLock) { std::scoped_lock lock(mDisplayLock); - return leaderSelectorPtrLocked(); + return pacesetterSelectorPtrLocked(); } - RefreshRateSelectorPtr leaderSelectorPtrLocked() const REQUIRES(mDisplayLock) { + RefreshRateSelectorPtr pacesetterSelectorPtrLocked() const REQUIRES(mDisplayLock) { ftl::FakeGuard guard(kMainThreadContext); - const RefreshRateSelectorPtr noLeader; - return mLeaderDisplayId - .and_then([this](PhysicalDisplayId leaderId) + const RefreshRateSelectorPtr noPacesetter; + return mPacesetterDisplayId + .and_then([this](PhysicalDisplayId pacesetterId) REQUIRES(mDisplayLock, kMainThreadContext) { - return mRefreshRateSelectors.get(leaderId); + return mRefreshRateSelectors.get(pacesetterId); }) - .value_or(std::cref(noLeader)); + .value_or(std::cref(noPacesetter)); + } + + std::shared_ptr<const VsyncSchedule> getVsyncScheduleLocked( + std::optional<PhysicalDisplayId> idOpt = std::nullopt) const REQUIRES(mDisplayLock); + std::shared_ptr<VsyncSchedule> getVsyncScheduleLocked( + std::optional<PhysicalDisplayId> idOpt = std::nullopt) REQUIRES(mDisplayLock) { + return std::const_pointer_cast<VsyncSchedule>( + static_cast<const Scheduler*>(this)->getVsyncScheduleLocked(idOpt)); } struct Policy { @@ -437,9 +468,6 @@ private: 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/VSyncDispatch.h b/services/surfaceflinger/Scheduler/VSyncDispatch.h index 95201314a4..77875e3b4d 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatch.h +++ b/services/surfaceflinger/Scheduler/VSyncDispatch.h @@ -161,7 +161,8 @@ protected: */ class VSyncCallbackRegistration { public: - VSyncCallbackRegistration(VSyncDispatch&, VSyncDispatch::Callback, std::string callbackName); + VSyncCallbackRegistration(std::shared_ptr<VSyncDispatch>, VSyncDispatch::Callback, + std::string callbackName); ~VSyncCallbackRegistration(); VSyncCallbackRegistration(VSyncCallbackRegistration&&); @@ -177,7 +178,7 @@ public: CancelResult cancel(); private: - std::reference_wrapper<VSyncDispatch> mDispatch; + std::shared_ptr<VSyncDispatch> mDispatch; VSyncDispatch::CallbackToken mToken; bool mValidToken; }; diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp index 73d52cf986..26389eb8cc 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp @@ -215,10 +215,10 @@ void VSyncDispatchTimerQueueEntry::dump(std::string& result) const { } VSyncDispatchTimerQueue::VSyncDispatchTimerQueue(std::unique_ptr<TimeKeeper> tk, - VSyncTracker& tracker, nsecs_t timerSlack, - nsecs_t minVsyncDistance) + VsyncSchedule::TrackerPtr tracker, + nsecs_t timerSlack, nsecs_t minVsyncDistance) : mTimeKeeper(std::move(tk)), - mTracker(tracker), + mTracker(std::move(tracker)), mTimerSlack(timerSlack), mMinVsyncDistance(minVsyncDistance) {} @@ -255,7 +255,7 @@ void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( } if (it != skipUpdateIt) { - callback->update(mTracker, now); + callback->update(*mTracker, now); } auto const wakeupTime = *callback->wakeupTime(); if (!min || *min > wakeupTime) { @@ -365,10 +365,10 @@ ScheduleResult VSyncDispatchTimerQueue::scheduleLocked(CallbackToken token, auto const rearmImminent = now > mIntendedWakeupTime; if (CC_UNLIKELY(rearmImminent)) { callback->addPendingWorkloadUpdate(scheduleTiming); - return getExpectedCallbackTime(mTracker, now, scheduleTiming); + return getExpectedCallbackTime(*mTracker, now, scheduleTiming); } - const ScheduleResult result = callback->schedule(scheduleTiming, mTracker, now); + const ScheduleResult result = callback->schedule(scheduleTiming, *mTracker, now); if (!result.has_value()) { return {}; } @@ -434,15 +434,15 @@ void VSyncDispatchTimerQueue::dump(std::string& result) const { } } -VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncDispatch& dispatch, +VSyncCallbackRegistration::VSyncCallbackRegistration(std::shared_ptr<VSyncDispatch> dispatch, VSyncDispatch::Callback callback, std::string callbackName) - : mDispatch(dispatch), - mToken(dispatch.registerCallback(std::move(callback), std::move(callbackName))), + : mDispatch(std::move(dispatch)), + mToken(mDispatch->registerCallback(std::move(callback), std::move(callbackName))), mValidToken(true) {} VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncCallbackRegistration&& other) - : mDispatch(other.mDispatch), + : mDispatch(std::move(other.mDispatch)), mToken(std::move(other.mToken)), mValidToken(std::move(other.mValidToken)) { other.mValidToken = false; @@ -457,28 +457,28 @@ VSyncCallbackRegistration& VSyncCallbackRegistration::operator=(VSyncCallbackReg } VSyncCallbackRegistration::~VSyncCallbackRegistration() { - if (mValidToken) mDispatch.get().unregisterCallback(mToken); + if (mValidToken) mDispatch->unregisterCallback(mToken); } ScheduleResult VSyncCallbackRegistration::schedule(VSyncDispatch::ScheduleTiming scheduleTiming) { if (!mValidToken) { return std::nullopt; } - return mDispatch.get().schedule(mToken, scheduleTiming); + return mDispatch->schedule(mToken, scheduleTiming); } ScheduleResult VSyncCallbackRegistration::update(VSyncDispatch::ScheduleTiming scheduleTiming) { if (!mValidToken) { return std::nullopt; } - return mDispatch.get().update(mToken, scheduleTiming); + return mDispatch->update(mToken, scheduleTiming); } CancelResult VSyncCallbackRegistration::cancel() { if (!mValidToken) { return CancelResult::Error; } - return mDispatch.get().cancel(mToken); + return mDispatch->cancel(mToken); } } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h index c3af136d66..6499d69969 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h @@ -26,11 +26,11 @@ #include <android-base/thread_annotations.h> #include "VSyncDispatch.h" +#include "VsyncSchedule.h" namespace android::scheduler { class TimeKeeper; -class VSyncTracker; // VSyncDispatchTimerQueueEntry is a helper class representing internal state for each entry in // VSyncDispatchTimerQueue hoisted to public for unit testing. @@ -120,8 +120,8 @@ public: // should be grouped into one wakeup. // \param[in] minVsyncDistance The minimum distance between two vsync estimates before the // vsyncs are considered the same vsync event. - VSyncDispatchTimerQueue(std::unique_ptr<TimeKeeper>, VSyncTracker&, nsecs_t timerSlack, - nsecs_t minVsyncDistance); + VSyncDispatchTimerQueue(std::unique_ptr<TimeKeeper>, VsyncSchedule::TrackerPtr, + nsecs_t timerSlack, nsecs_t minVsyncDistance); ~VSyncDispatchTimerQueue(); CallbackToken registerCallback(Callback, std::string callbackName) final; @@ -148,7 +148,7 @@ private: static constexpr nsecs_t kInvalidTime = std::numeric_limits<int64_t>::max(); std::unique_ptr<TimeKeeper> const mTimeKeeper; - VSyncTracker& mTracker; + VsyncSchedule::TrackerPtr mTracker; nsecs_t const mTimerSlack; nsecs_t const mMinVsyncDistance; diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 02e12fd942..e969fdc679 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -31,8 +31,9 @@ #include <android-base/stringprintf.h> #include <cutils/compiler.h> #include <cutils/properties.h> +#include <ftl/concat.h> +#include <gui/TraceUtils.h> #include <utils/Log.h> -#include <utils/Trace.h> #include "RefreshRateSelector.h" #include "VSyncPredictor.h" @@ -45,9 +46,10 @@ static auto constexpr kMaxPercent = 100u; VSyncPredictor::~VSyncPredictor() = default; -VSyncPredictor::VSyncPredictor(nsecs_t idealPeriod, size_t historySize, +VSyncPredictor::VSyncPredictor(PhysicalDisplayId id, nsecs_t idealPeriod, size_t historySize, size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent) - : mTraceOn(property_get_bool("debug.sf.vsp_trace", false)), + : mId(id), + mTraceOn(property_get_bool("debug.sf.vsp_trace", false)), kHistorySize(historySize), kMinimumSamplesForPrediction(minimumSamplesForPrediction), kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)), @@ -57,12 +59,12 @@ VSyncPredictor::VSyncPredictor(nsecs_t idealPeriod, size_t historySize, inline void VSyncPredictor::traceInt64If(const char* name, int64_t value) const { if (CC_UNLIKELY(mTraceOn)) { - ATRACE_INT64(name, value); + traceInt64(name, value); } } inline void VSyncPredictor::traceInt64(const char* name, int64_t value) const { - ATRACE_INT64(name, value); + ATRACE_INT64(ftl::Concat(ftl::truncated<14>(name), " ", mId.value).c_str(), value); } inline size_t VSyncPredictor::next(size_t i) const { @@ -214,11 +216,22 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { it->second = {anticipatedPeriod, intercept}; - ALOGV("model update ts: %" PRId64 " slope: %" PRId64 " intercept: %" PRId64, timestamp, - anticipatedPeriod, intercept); + ALOGV("model update ts %" PRIu64 ": %" PRId64 " slope: %" PRId64 " intercept: %" PRId64, + mId.value, timestamp, anticipatedPeriod, intercept); return true; } +auto VSyncPredictor::getVsyncSequenceLocked(nsecs_t timestamp) const -> VsyncSequence { + const auto vsync = nextAnticipatedVSyncTimeFromLocked(timestamp); + if (!mLastVsyncSequence) return {vsync, 0}; + + const auto [slope, _] = getVSyncPredictionModelLocked(); + const auto [lastVsyncTime, lastVsyncSequence] = *mLastVsyncSequence; + const auto vsyncSequence = lastVsyncSequence + + static_cast<int64_t>(std::round((vsync - lastVsyncTime) / static_cast<float>(slope))); + return {vsync, vsyncSequence}; +} + nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const { auto const [slope, intercept] = getVSyncPredictionModelLocked(); @@ -258,12 +271,30 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) co nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const { std::lock_guard lock(mMutex); - // TODO(b/246164114): This implementation is not efficient at all. Refactor. - nsecs_t nextVsync = nextAnticipatedVSyncTimeFromLocked(timePoint); - while (!isVSyncInPhaseLocked(nextVsync, mDivisor)) { - nextVsync = nextAnticipatedVSyncTimeFromLocked(nextVsync + 1); + // update the mLastVsyncSequence for reference point + mLastVsyncSequence = getVsyncSequenceLocked(timePoint); + + const auto renderRatePhase = [&]() REQUIRES(mMutex) -> int { + if (!mRenderRate) return 0; + + const auto divisor = + RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), + *mRenderRate); + if (divisor <= 1) return 0; + + const int mod = mLastVsyncSequence->seq % divisor; + if (mod == 0) return 0; + + return divisor - mod; + }(); + + if (renderRatePhase == 0) { + return mLastVsyncSequence->vsyncTime; } - return nextVsync; + + auto const [slope, intercept] = getVSyncPredictionModelLocked(); + const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase; + return nextAnticipatedVSyncTimeFromLocked(approximateNextVsync - slope / 2); } /* @@ -282,12 +313,12 @@ bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const { } bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const { - struct VsyncError { - nsecs_t vsyncTimestamp; - float error; - - bool operator<(const VsyncError& other) const { return error < other.error; } + 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); if (divisor <= 1 || timePoint == 0) { return true; @@ -295,41 +326,16 @@ bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) c const nsecs_t period = mRateMap[mIdealPeriod].slope; const nsecs_t justBeforeTimePoint = timePoint - period / 2; - const nsecs_t dividedPeriod = mIdealPeriod / divisor; - - // If this is the first time we have asked about this divisor with the - // current vsync period, it is considered in phase and we store the closest - // vsync timestamp - const auto knownTimestampIter = mRateDivisorKnownTimestampMap.find(dividedPeriod); - if (knownTimestampIter == mRateDivisorKnownTimestampMap.end()) { - const auto vsync = nextAnticipatedVSyncTimeFromLocked(justBeforeTimePoint); - mRateDivisorKnownTimestampMap[dividedPeriod] = vsync; - return true; - } - - // Find the next N vsync timestamp where N is the divisor. - // One of these vsyncs will be in phase. We return the one which is - // the most aligned with the last known in phase vsync - std::vector<VsyncError> vsyncs(static_cast<size_t>(divisor)); - const nsecs_t knownVsync = knownTimestampIter->second; - nsecs_t point = justBeforeTimePoint; - for (size_t i = 0; i < divisor; i++) { - const nsecs_t vsync = nextAnticipatedVSyncTimeFromLocked(point); - const auto numPeriods = static_cast<float>(vsync - knownVsync) / (period * divisor); - const auto error = std::abs(std::round(numPeriods) - numPeriods); - vsyncs[i] = {vsync, error}; - point = vsync + 1; - } - - const auto minVsyncError = std::min_element(vsyncs.begin(), vsyncs.end()); - mRateDivisorKnownTimestampMap[dividedPeriod] = minVsyncError->vsyncTimestamp; - return std::abs(minVsyncError->vsyncTimestamp - timePoint) < period / 2; + const auto vsyncSequence = getVsyncSequenceLocked(justBeforeTimePoint); + ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64, + getTimePointIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq); + return vsyncSequence.seq % divisor == 0; } -void VSyncPredictor::setDivisor(unsigned divisor) { - ALOGV("%s: %d", __func__, divisor); +void VSyncPredictor::setRenderRate(Fps fps) { + ALOGV("%s %s: %s", __func__, to_string(mId).c_str(), to_string(fps).c_str()); std::lock_guard lock(mMutex); - mDivisor = divisor; + mRenderRate = fps; } VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const { @@ -343,7 +349,7 @@ VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const { } void VSyncPredictor::setPeriod(nsecs_t period) { - ATRACE_CALL(); + ATRACE_FORMAT("%s %s", __func__, to_string(mId).c_str()); traceInt64("VSP-setPeriod", period); std::lock_guard lock(mMutex); diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index 305cdb0d56..c01c44dc6b 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -21,6 +21,7 @@ #include <vector> #include <android-base/thread_annotations.h> +#include <ui/DisplayId.h> #include "VSyncTracker.h" @@ -29,14 +30,15 @@ namespace android::scheduler { class VSyncPredictor : public VSyncTracker { public: /* + * \param [in] PhysicalDisplayid The display this corresponds to. * \param [in] idealPeriod The initial ideal period to use. * \param [in] historySize The internal amount of entries to store in the model. * \param [in] minimumSamplesForPrediction The minimum number of samples to collect before * predicting. \param [in] outlierTolerancePercent a number 0 to 100 that will be used to filter * samples that fall outlierTolerancePercent from an anticipated vsync event. */ - VSyncPredictor(nsecs_t idealPeriod, size_t historySize, size_t minimumSamplesForPrediction, - uint32_t outlierTolerancePercent); + VSyncPredictor(PhysicalDisplayId, nsecs_t idealPeriod, size_t historySize, + size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent); ~VSyncPredictor(); bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex); @@ -67,7 +69,7 @@ public: bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex); - void setDivisor(unsigned divisor) final EXCLUDES(mMutex); + void setRenderRate(Fps) final EXCLUDES(mMutex); void dump(std::string& result) const final EXCLUDES(mMutex); @@ -76,37 +78,41 @@ private: VSyncPredictor& operator=(VSyncPredictor const&) = delete; void clearTimestamps() REQUIRES(mMutex); + const PhysicalDisplayId mId; + inline void traceInt64If(const char* name, int64_t value) const; inline void traceInt64(const char* name, int64_t value) const; - bool const mTraceOn; - - size_t const kHistorySize; - size_t const kMinimumSamplesForPrediction; - size_t const kOutlierTolerancePercent; - std::mutex mutable mMutex; size_t next(size_t i) const REQUIRES(mMutex); bool validate(nsecs_t timestamp) const REQUIRES(mMutex); - Model getVSyncPredictionModelLocked() const REQUIRES(mMutex); - nsecs_t nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const REQUIRES(mMutex); - bool isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const REQUIRES(mMutex); + struct VsyncSequence { + nsecs_t vsyncTime; + int64_t seq; + }; + VsyncSequence getVsyncSequenceLocked(nsecs_t timestamp) const REQUIRES(mMutex); + + bool const mTraceOn; + size_t const kHistorySize; + size_t const kMinimumSamplesForPrediction; + size_t const kOutlierTolerancePercent; + std::mutex mutable mMutex; + nsecs_t mIdealPeriod GUARDED_BY(mMutex); std::optional<nsecs_t> mKnownTimestamp GUARDED_BY(mMutex); // Map between ideal vsync period and the calculated model std::unordered_map<nsecs_t, Model> mutable mRateMap GUARDED_BY(mMutex); - // Map between the divided vsync period and the last known vsync timestamp - std::unordered_map<nsecs_t, nsecs_t> mutable mRateDivisorKnownTimestampMap GUARDED_BY(mMutex); - size_t mLastTimestampIndex GUARDED_BY(mMutex) = 0; std::vector<nsecs_t> mTimestamps GUARDED_BY(mMutex); - unsigned mDivisor GUARDED_BY(mMutex) = 1; + std::optional<Fps> mRenderRate GUARDED_BY(mMutex); + + mutable std::optional<VsyncSequence> mLastVsyncSequence GUARDED_BY(mMutex); }; } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp index b5f212e085..2938aa3fb3 100644 --- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp @@ -21,6 +21,8 @@ #include <assert.h> #include <cutils/properties.h> +#include <ftl/concat.h> +#include <gui/TraceUtils.h> #include <log/log.h> #include <utils/Trace.h> @@ -39,12 +41,13 @@ nsecs_t SystemClock::now() const { return systemTime(SYSTEM_TIME_MONOTONIC); } -VSyncReactor::VSyncReactor(std::unique_ptr<Clock> clock, VSyncTracker& tracker, - size_t pendingFenceLimit, bool supportKernelIdleTimer) - : mClock(std::move(clock)), +VSyncReactor::VSyncReactor(PhysicalDisplayId id, std::unique_ptr<Clock> clock, + VSyncTracker& tracker, size_t pendingFenceLimit, + bool supportKernelIdleTimer) + : mId(id), + mClock(std::move(clock)), mTracker(tracker), mPendingLimit(pendingFenceLimit), - // TODO(adyabr): change mSupportKernelIdleTimer when the active display changes mSupportKernelIdleTimer(supportKernelIdleTimer) {} VSyncReactor::~VSyncReactor() = default; @@ -114,7 +117,7 @@ void VSyncReactor::updateIgnorePresentFencesInternal() { } void VSyncReactor::startPeriodTransitionInternal(nsecs_t newPeriod) { - ATRACE_CALL(); + ATRACE_FORMAT("%s %" PRIu64, __func__, mId.value); mPeriodConfirmationInProgress = true; mPeriodTransitioningTo = newPeriod; mMoreSamplesNeeded = true; @@ -122,18 +125,18 @@ void VSyncReactor::startPeriodTransitionInternal(nsecs_t newPeriod) { } void VSyncReactor::endPeriodTransition() { - ATRACE_CALL(); + ATRACE_FORMAT("%s %" PRIu64, __func__, mId.value); mPeriodTransitioningTo.reset(); mPeriodConfirmationInProgress = false; mLastHwVsync.reset(); } -void VSyncReactor::startPeriodTransition(nsecs_t period) { - ATRACE_INT64("VSR-startPeriodTransition", period); +void VSyncReactor::startPeriodTransition(nsecs_t period, bool force) { + ATRACE_INT64(ftl::Concat("VSR-", __func__, " ", mId.value).c_str(), period); std::lock_guard lock(mMutex); mLastHwVsync.reset(); - if (!mSupportKernelIdleTimer && period == mTracker.currentPeriod()) { + if (!mSupportKernelIdleTimer && period == mTracker.currentPeriod() && !force) { endPeriodTransition(); setIgnorePresentFencesInternal(false); mMoreSamplesNeeded = false; @@ -181,7 +184,7 @@ bool VSyncReactor::addHwVsyncTimestamp(nsecs_t timestamp, std::optional<nsecs_t> std::lock_guard lock(mMutex); if (periodConfirmed(timestamp, hwcVsyncPeriod)) { - ATRACE_NAME("VSR: period confirmed"); + ATRACE_FORMAT("VSR %" PRIu64 ": period confirmed", mId.value); if (mPeriodTransitioningTo) { mTracker.setPeriod(*mPeriodTransitioningTo); *periodFlushed = true; @@ -195,12 +198,12 @@ bool VSyncReactor::addHwVsyncTimestamp(nsecs_t timestamp, std::optional<nsecs_t> endPeriodTransition(); mMoreSamplesNeeded = mTracker.needsMoreSamples(); } else if (mPeriodConfirmationInProgress) { - ATRACE_NAME("VSR: still confirming period"); + ATRACE_FORMAT("VSR %" PRIu64 ": still confirming period", mId.value); mLastHwVsync = timestamp; mMoreSamplesNeeded = true; *periodFlushed = false; } else { - ATRACE_NAME("VSR: adding sample"); + ATRACE_FORMAT("VSR %" PRIu64 ": adding sample", mId.value); *periodFlushed = false; mTracker.addVsyncTimestamp(timestamp); mMoreSamplesNeeded = mTracker.needsMoreSamples(); diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.h b/services/surfaceflinger/Scheduler/VSyncReactor.h index 4501487392..f2302422ad 100644 --- a/services/surfaceflinger/Scheduler/VSyncReactor.h +++ b/services/surfaceflinger/Scheduler/VSyncReactor.h @@ -22,6 +22,7 @@ #include <vector> #include <android-base/thread_annotations.h> +#include <ui/DisplayId.h> #include <ui/FenceTime.h> #include <scheduler/TimeKeeper.h> @@ -37,14 +38,14 @@ class VSyncTracker; // TODO (b/145217110): consider renaming. class VSyncReactor : public VsyncController { public: - VSyncReactor(std::unique_ptr<Clock> clock, VSyncTracker& tracker, size_t pendingFenceLimit, - bool supportKernelIdleTimer); + VSyncReactor(PhysicalDisplayId, std::unique_ptr<Clock> clock, VSyncTracker& tracker, + size_t pendingFenceLimit, bool supportKernelIdleTimer); ~VSyncReactor(); bool addPresentFence(std::shared_ptr<FenceTime>) final; void setIgnorePresentFences(bool ignore) final; - void startPeriodTransition(nsecs_t period) final; + void startPeriodTransition(nsecs_t period, bool force) final; bool addHwVsyncTimestamp(nsecs_t timestamp, std::optional<nsecs_t> hwcVsyncPeriod, bool* periodFlushed) final; @@ -61,6 +62,7 @@ private: bool periodConfirmed(nsecs_t vsync_timestamp, std::optional<nsecs_t> hwcVsyncPeriod) REQUIRES(mMutex); + const PhysicalDisplayId mId; std::unique_ptr<Clock> const mClock; VSyncTracker& mTracker; size_t const mPendingLimit; diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h index 8d1629faae..bc0e3bcbb2 100644 --- a/services/surfaceflinger/Scheduler/VSyncTracker.h +++ b/services/surfaceflinger/Scheduler/VSyncTracker.h @@ -80,15 +80,16 @@ public: virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0; /* - * Sets a divisor on the rate (which is a multiplier of the period). + * Sets a render rate on the tracker. If the render rate is not a divisor + * of the period, the render rate is ignored until the period changes. * The tracker will continue to track the vsync timeline and expect it * to match the current period, however, nextAnticipatedVSyncTimeFrom will - * return vsyncs according to the divisor set. Setting a divisor is useful + * return vsyncs according to the render rate set. Setting a render rate is useful * when a display is running at 120Hz but the render frame rate is 60Hz. * - * \param [in] divisor The rate divisor the tracker should operate at. + * \param [in] Fps The render rate the tracker should operate at. */ - virtual void setDivisor(unsigned divisor) = 0; + virtual void setRenderRate(Fps) = 0; virtual void dump(std::string& result) const = 0; diff --git a/services/surfaceflinger/Scheduler/VsyncController.h b/services/surfaceflinger/Scheduler/VsyncController.h index 726a420649..917789934a 100644 --- a/services/surfaceflinger/Scheduler/VsyncController.h +++ b/services/surfaceflinger/Scheduler/VsyncController.h @@ -63,8 +63,9 @@ public: * itself. The controller will end the period transition internally. * * \param [in] period The period that the system is changing into. + * \param [in] force True to recalibrate even if period matches the existing period. */ - virtual void startPeriodTransition(nsecs_t period) = 0; + virtual void startPeriodTransition(nsecs_t period, bool force) = 0; /* * Tells the tracker to stop using present fences to get a vsync signal. diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp index 95bc31f239..84671aea0d 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" @@ -39,8 +42,8 @@ class VsyncSchedule::PredictedVsyncTracer { } public: - explicit PredictedVsyncTracer(VsyncDispatch& dispatch) - : mRegistration(dispatch, makeVsyncCallback(), __func__) { + explicit PredictedVsyncTracer(std::shared_ptr<VsyncDispatch> dispatch) + : mRegistration(std::move(dispatch), makeVsyncCallback(), __func__) { schedule(); } @@ -51,21 +54,22 @@ private: VSyncCallbackRegistration mRegistration; }; -VsyncSchedule::VsyncSchedule(FeatureFlags features) - : mTracker(createTracker()), - mDispatch(createDispatch(*mTracker)), - mController(createController(*mTracker, features)) { - if (features.test(Feature::kTracePredictedVsync)) { - mTracer = std::make_unique<PredictedVsyncTracer>(*mDispatch); - } -} - -VsyncSchedule::VsyncSchedule(TrackerPtr tracker, DispatchPtr dispatch, ControllerPtr controller) - : mTracker(std::move(tracker)), +VsyncSchedule::VsyncSchedule(PhysicalDisplayId id, FeatureFlags features) + : mId(id), + mTracker(createTracker(id)), + mDispatch(createDispatch(mTracker)), + mController(createController(id, *mTracker, features)), + mTracer(features.test(Feature::kTracePredictedVsync) + ? std::make_unique<PredictedVsyncTracer>(mDispatch) + : nullptr) {} + +VsyncSchedule::VsyncSchedule(PhysicalDisplayId id, TrackerPtr tracker, DispatchPtr dispatch, + ControllerPtr controller) + : mId(id), + 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 +81,16 @@ TimePoint VsyncSchedule::vsyncDeadlineAfter(TimePoint timePoint) const { } 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); @@ -84,40 +98,110 @@ void VsyncSchedule::dump(std::string& out) const { mDispatch->dump(out); } -VsyncSchedule::TrackerPtr VsyncSchedule::createTracker() { +VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(PhysicalDisplayId id) { // TODO(b/144707443): Tune constants. constexpr nsecs_t kInitialPeriod = (60_Hz).getPeriodNsecs(); constexpr size_t kHistorySize = 20; constexpr size_t kMinSamplesForPrediction = 6; constexpr uint32_t kDiscardOutlierPercent = 20; - return std::make_unique<VSyncPredictor>(kInitialPeriod, kHistorySize, kMinSamplesForPrediction, - kDiscardOutlierPercent); + return std::make_unique<VSyncPredictor>(id, kInitialPeriod, kHistorySize, + kMinSamplesForPrediction, kDiscardOutlierPercent); } -VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(VsyncTracker& tracker) { +VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(TrackerPtr tracker) { using namespace std::chrono_literals; // TODO(b/144707443): Tune constants. constexpr std::chrono::nanoseconds kGroupDispatchWithin = 500us; constexpr std::chrono::nanoseconds kSnapToSameVsyncWithin = 3ms; - return std::make_unique<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), tracker, + return std::make_unique<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), std::move(tracker), kGroupDispatchWithin.count(), kSnapToSameVsyncWithin.count()); } -VsyncSchedule::ControllerPtr VsyncSchedule::createController(VsyncTracker& tracker, +VsyncSchedule::ControllerPtr VsyncSchedule::createController(PhysicalDisplayId id, + VsyncTracker& tracker, FeatureFlags features) { // TODO(b/144707443): Tune constants. constexpr size_t kMaxPendingFences = 20; const bool hasKernelIdleTimer = features.test(Feature::kKernelIdleTimer); - auto reactor = std::make_unique<VSyncReactor>(std::make_unique<SystemClock>(), tracker, + auto reactor = std::make_unique<VSyncReactor>(id, std::make_unique<SystemClock>(), tracker, kMaxPendingFences, hasKernelIdleTimer); reactor->setIgnorePresentFences(!features.test(Feature::kPresentFences)); return reactor; } +void VsyncSchedule::startPeriodTransition(ISchedulerCallback& callback, Period period, bool force) { + std::lock_guard<std::mutex> lock(mHwVsyncLock); + mController->startPeriodTransition(period.ns(), force); + 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(mId, true); + mHwVsyncState = HwVsyncState::Enabled; + } +} + +void VsyncSchedule::disableHardwareVsync(ISchedulerCallback& callback, bool disallow) { + std::lock_guard<std::mutex> lock(mHwVsyncLock); + switch (mHwVsyncState) { + case HwVsyncState::Enabled: + callback.setVsyncEnabled(mId, false); + [[fallthrough]]; + case HwVsyncState::Disabled: + mHwVsyncState = disallow ? HwVsyncState::Disallowed : HwVsyncState::Disabled; + break; + case HwVsyncState::Disallowed: + break; + } +} + +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 173b1d00cf..0ef1e979b1 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.h +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h @@ -19,11 +19,17 @@ #include <memory> #include <string> +#include <android-base/thread_annotations.h> +#include <ThreadContext.h> +#include <ftl/enum.h> +#include <ftl/optional.h> #include <scheduler/Features.h> #include <scheduler/Time.h> +#include <ui/DisplayId.h> namespace android { class EventThreadTest; +class VsyncScheduleTest; } namespace android::fuzz { @@ -32,6 +38,8 @@ class SchedulerFuzzer; namespace android::scheduler { +struct ISchedulerCallback; + // TODO(b/185535769): Rename classes, and remove aliases. class VSyncDispatch; class VSyncTracker; @@ -43,47 +51,106 @@ using VsyncTracker = VSyncTracker; // Schedule that synchronizes to hardware VSYNC of a physical display. class VsyncSchedule { public: - explicit VsyncSchedule(FeatureFlags); - VsyncSchedule(VsyncSchedule&&); + VsyncSchedule(PhysicalDisplayId, FeatureFlags); ~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. + // \param [in] force True to force a transition even if it is not a + // change. + void startPeriodTransition(ISchedulerCallback&, Period period, bool force); + + // 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; } VsyncController& getController() { return *mController; } + // TODO(b/185535769): Once these are hidden behind the API, they may no + // longer need to be shared_ptrs. + using DispatchPtr = std::shared_ptr<VsyncDispatch>; + using TrackerPtr = std::shared_ptr<VsyncTracker>; + // TODO(b/185535769): Remove once VsyncSchedule owns all registrations. - VsyncDispatch& getDispatch() { return *mDispatch; } + DispatchPtr getDispatch() { return mDispatch; } 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 isHardwareVsyncAllowed is + // called with `makeAllowed` set to true. + 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); + +protected: + using ControllerPtr = std::unique_ptr<VsyncController>; + + // For tests. + VsyncSchedule(PhysicalDisplayId, TrackerPtr, DispatchPtr, ControllerPtr); + private: friend class TestableScheduler; friend class android::EventThreadTest; + friend class android::VsyncScheduleTest; friend class android::fuzz::SchedulerFuzzer; - using TrackerPtr = std::unique_ptr<VsyncTracker>; - using DispatchPtr = std::unique_ptr<VsyncDispatch>; - using ControllerPtr = std::unique_ptr<VsyncController>; + static TrackerPtr createTracker(PhysicalDisplayId); + static DispatchPtr createDispatch(TrackerPtr); + static ControllerPtr createController(PhysicalDisplayId, VsyncTracker&, FeatureFlags); - // For tests. - VsyncSchedule(TrackerPtr, DispatchPtr, ControllerPtr); + 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; - static TrackerPtr createTracker(); - static DispatchPtr createDispatch(VsyncTracker&); - static ControllerPtr createController(VsyncTracker&, FeatureFlags); + // 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 PhysicalDisplayId mId; + 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 bd4e3c27b3..ba1459a562 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/Time.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/Time.h @@ -26,7 +26,7 @@ namespace android { 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/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 43483e475b..50d4dced67 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -173,6 +173,8 @@ using namespace std::string_view_literals; 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; @@ -454,6 +456,7 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI android::hardware::details::setTrebleTestingOverride(true); } + // TODO (b/270966065) Update the HWC based refresh rate overlay to support spinner mRefreshRateOverlaySpinner = property_get_bool("debug.sf.show_refresh_rate_overlay_spinner", 0); mRefreshRateOverlayRenderRate = property_get_bool("debug.sf.show_refresh_rate_overlay_render_rate", 0); @@ -470,6 +473,10 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI 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("persist.debug.sf.enable_layer_lifecycle_manager"s, false); + mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled || + base::GetBoolProperty("persist.debug.sf.enable_legacy_frontend"s, false); } LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() { @@ -865,7 +872,7 @@ void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { // initialize our drawing state mDrawingState = mCurrentState; - // set initial conditions (e.g. unblank default device) + onActiveDisplayChangedLocked(nullptr, *display); initializeDisplays(); mPowerAdvisor->init(); @@ -1124,21 +1131,33 @@ status_t SurfaceFlinger::getDynamicDisplayInfoFromToken(const sp<IBinder>& displ return NO_ERROR; } -status_t SurfaceFlinger::getDisplayStats(const sp<IBinder>&, DisplayStatInfo* outStats) { +status_t SurfaceFlinger::getDisplayStats(const sp<IBinder>& displayToken, + DisplayStatInfo* outStats) { if (!outStats) { return BAD_VALUE; } - const auto& schedule = mScheduler->getVsyncSchedule(); - outStats->vsyncTime = schedule.vsyncDeadlineAfter(TimePoint::now()).ns(); - outStats->vsyncPeriod = schedule.period().ns(); + std::optional<PhysicalDisplayId> displayIdOpt; + { + Mutex::Autolock lock(mStateLock); + displayIdOpt = getPhysicalDisplayIdLocked(displayToken); + } + + if (!displayIdOpt) { + ALOGE("%s: Invalid physical display token %p", __func__, displayToken.get()); + return NAME_NOT_FOUND; + } + const auto schedule = mScheduler->getVsyncSchedule(displayIdOpt); + outStats->vsyncTime = schedule->vsyncDeadlineAfter(TimePoint::now()).ns(); + outStats->vsyncPeriod = schedule->period().ns(); return NO_ERROR; } void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request, bool force) { ATRACE_CALL(); - auto display = getDisplayDeviceLocked(request.mode.modePtr->getPhysicalDisplayId()); + const auto displayId = request.mode.modePtr->getPhysicalDisplayId(); + const auto display = getDisplayDeviceLocked(displayId); if (!display) { ALOGW("%s: display is no longer valid", __func__); return; @@ -1151,23 +1170,25 @@ void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request, force)) { case DisplayDevice::DesiredActiveModeAction::InitiateDisplayModeSwitch: // Set the render rate as setDesiredActiveMode updated it. - mScheduler->setRenderRate(display->refreshRateSelector().getActiveMode().fps); + mScheduler->setRenderRate(displayId, + display->refreshRateSelector().getActiveMode().fps); // Schedule a new frame to initiate the display mode switch. scheduleComposite(FrameHint::kNone); // Start receiving vsync samples now, so that we can detect a period // switch. - mScheduler->resyncToHardwareVsync(true, mode.modePtr->getFps()); + mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, + mode.modePtr->getFps()); + // As we called to set period, we will call to onRefreshRateChangeCompleted once // VsyncController model is locked. - mScheduler->modulateVsync(&VsyncModulator::onRefreshRateChangeInitiated); - + mScheduler->modulateVsync(displayId, &VsyncModulator::onRefreshRateChangeInitiated); updatePhaseConfiguration(mode.fps); mScheduler->setModeChangePending(true); break; case DisplayDevice::DesiredActiveModeAction::InitiateRenderRateSwitch: - mScheduler->setRenderRate(mode.fps); + mScheduler->setRenderRate(displayId, mode.fps); updatePhaseConfiguration(mode.fps); mRefreshRateStats->setRefreshRate(mode.fps); if (display->getPhysicalId() == mActiveDisplayId && emitEvent) { @@ -1283,11 +1304,14 @@ void SurfaceFlinger::clearDesiredActiveModeState(const sp<DisplayDevice>& displa } void SurfaceFlinger::desiredActiveModeChangeDone(const sp<DisplayDevice>& display) { - const auto displayFps = display->getDesiredActiveMode()->modeOpt->modePtr->getFps(); - const auto renderFps = display->getDesiredActiveMode()->modeOpt->fps; + const auto desiredActiveMode = display->getDesiredActiveMode(); + const auto& modeOpt = desiredActiveMode->modeOpt; + const auto displayId = modeOpt->modePtr->getPhysicalDisplayId(); + const auto displayFps = modeOpt->modePtr->getFps(); + const auto renderFps = modeOpt->fps; clearDesiredActiveModeState(display); - mScheduler->resyncToHardwareVsync(true, displayFps); - mScheduler->setRenderRate(renderFps); + mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, displayFps); + mScheduler->setRenderRate(displayId, renderFps); updatePhaseConfiguration(renderFps); } @@ -1574,7 +1598,8 @@ status_t SurfaceFlinger::getHdrConversionCapabilities( } status_t SurfaceFlinger::setHdrConversionStrategy( - const gui::HdrConversionStrategy& hdrConversionStrategy) { + const gui::HdrConversionStrategy& hdrConversionStrategy, + int32_t* outPreferredHdrOutputType) { bool hdrOutputConversionSupport; getHdrOutputConversionSupport(&hdrOutputConversionSupport); if (hdrOutputConversionSupport == false) { @@ -1586,11 +1611,16 @@ status_t SurfaceFlinger::setHdrConversionStrategy( aidl::android::hardware::graphics::common::HdrConversionStrategy; using GuiHdrConversionStrategyTag = gui::HdrConversionStrategy::Tag; AidlHdrConversionStrategy aidlConversionStrategy; + status_t status; + aidl::android::hardware::graphics::common::Hdr aidlPreferredHdrOutputType; switch (hdrConversionStrategy.getTag()) { case GuiHdrConversionStrategyTag::passthrough: { aidlConversionStrategy.set<AidlHdrConversionStrategy::Tag::passthrough>( hdrConversionStrategy.get<GuiHdrConversionStrategyTag::passthrough>()); - return getHwComposer().setHdrConversionStrategy(aidlConversionStrategy); + status = getHwComposer().setHdrConversionStrategy(aidlConversionStrategy, + &aidlPreferredHdrOutputType); + *outPreferredHdrOutputType = static_cast<int32_t>(aidlPreferredHdrOutputType); + return status; } case GuiHdrConversionStrategyTag::autoAllowedHdrTypes: { auto autoHdrTypes = @@ -1603,7 +1633,10 @@ status_t SurfaceFlinger::setHdrConversionStrategy( } aidlConversionStrategy.set<AidlHdrConversionStrategy::Tag::autoAllowedHdrTypes>( aidlAutoHdrTypes); - return getHwComposer().setHdrConversionStrategy(aidlConversionStrategy); + status = getHwComposer().setHdrConversionStrategy(aidlConversionStrategy, + &aidlPreferredHdrOutputType); + *outPreferredHdrOutputType = static_cast<int32_t>(aidlPreferredHdrOutputType); + return status; } case GuiHdrConversionStrategyTag::forceHdrConversion: { auto forceHdrConversion = @@ -1612,7 +1645,10 @@ status_t SurfaceFlinger::setHdrConversionStrategy( aidlConversionStrategy.set<AidlHdrConversionStrategy::Tag::forceHdrConversion>( static_cast<aidl::android::hardware::graphics::common::Hdr>( forceHdrConversion)); - return getHwComposer().setHdrConversionStrategy(aidlConversionStrategy); + status = getHwComposer().setHdrConversionStrategy(aidlConversionStrategy, + &aidlPreferredHdrOutputType); + *outPreferredHdrOutputType = static_cast<int32_t>(aidlPreferredHdrOutputType); + return status; } } }); @@ -1971,13 +2007,14 @@ status_t SurfaceFlinger::getDisplayDecorationSupport( // ---------------------------------------------------------------------------- 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) { @@ -2012,32 +2049,16 @@ nsecs_t SurfaceFlinger::getVsyncPeriodFromHWC() const { void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t timestamp, std::optional<hal::VsyncPeriodNanos> vsyncPeriod) { - const std::string tracePeriod = [vsyncPeriod]() { - if (ATRACE_ENABLED() && vsyncPeriod) { - std::stringstream ss; - ss << "(" << *vsyncPeriod << ")"; - return ss.str(); - } - return std::string(); - }(); - ATRACE_FORMAT("onComposerHalVsync%s", tracePeriod.c_str()); + ATRACE_NAME(vsyncPeriod + ? ftl::Concat(__func__, ' ', hwcDisplayId, ' ', *vsyncPeriod, "ns").c_str() + : ftl::Concat(__func__, ' ', hwcDisplayId).c_str()); Mutex::Autolock lock(mStateLock); - - if (!getHwComposer().onVsync(hwcDisplayId, timestamp)) { - 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); - if (periodFlushed) { - mScheduler->modulateVsync(&VsyncModulator::onRefreshRateChangeCompleted); + if (const auto displayIdOpt = getHwComposer().onVsync(hwcDisplayId, timestamp)) { + if (mScheduler->addResyncSample(*displayIdOpt, timestamp, vsyncPeriod)) { + // period flushed + mScheduler->modulateVsync(displayIdOpt, &VsyncModulator::onRefreshRateChangeCompleted); + } } } @@ -2078,16 +2099,40 @@ void SurfaceFlinger::onComposerHalVsyncIdle(hal::HWDisplayId) { mScheduler->forceNextResync(); } -void SurfaceFlinger::setVsyncEnabled(bool enabled) { +void SurfaceFlinger::onRefreshRateChangedDebug(const RefreshRateChangedDebugData& data) { ATRACE_CALL(); + if (const auto displayId = getHwComposer().toPhysicalDisplayId(data.display); displayId) { + const Fps fps = Fps::fromPeriodNsecs(data.vsyncPeriodNanos); + ATRACE_FORMAT("%s Fps %d", __func__, fps.getIntValue()); + static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) { + { + { + const auto display = getDisplayDeviceLocked(*displayId); + FTL_FAKE_GUARD(kMainThreadContext, + display->updateRefreshRateOverlayRate(fps, + display->getActiveMode() + .fps, + /* setByHwc */ true)); + } + } + })); + } +} + +void SurfaceFlinger::setVsyncEnabled(PhysicalDisplayId id, bool enabled) { + const char* const whence = __func__; + ATRACE_FORMAT("%s (%d) for %" PRIu64, whence, enabled, id.value); // 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(id)->setPendingHardwareVsyncState(enabled); + } - if (const auto display = getDefaultDisplayDeviceLocked(); - display && display->isPoweredOn()) { - setHWCVsyncEnabled(display->getPhysicalId(), mHWCVsyncPendingState); + ATRACE_FORMAT("%s (%d) for %" PRIu64 " (main thread)", whence, enabled, id.value); + if (const auto display = getDisplayDeviceLocked(id); display && display->isPoweredOn()) { + setHWCVsyncEnabled(id, enabled); } })); } @@ -2114,13 +2159,13 @@ bool SurfaceFlinger::isFencePending(const FenceTimePtr& fence, int graceTimeMs) TimePoint SurfaceFlinger::calculateExpectedPresentTime(TimePoint frameTime) const { const auto& schedule = mScheduler->getVsyncSchedule(); - const TimePoint vsyncDeadline = schedule.vsyncDeadlineAfter(frameTime); + const TimePoint vsyncDeadline = schedule->vsyncDeadlineAfter(frameTime); if (mScheduler->vsyncModulator().getVsyncConfig().sfOffset > 0) { return vsyncDeadline; } // Inflate the expected present time if we're targeting the next vsync. - return vsyncDeadline + schedule.period(); + return vsyncDeadline + schedule->period(); } void SurfaceFlinger::configure() FTL_FAKE_GUARD(kMainThreadContext) { @@ -2130,6 +2175,168 @@ void SurfaceFlinger::configure() FTL_FAKE_GUARD(kMainThreadContext) { } } +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; +} + +void SurfaceFlinger::updateLayerHistory(const frontend::LayerSnapshot& snapshot) { + using Changes = frontend::RequestedLayerState::Changes; + if (snapshot.path.isClone() || + !snapshot.changes.any(Changes::FrameRate | Changes::Buffer | Changes::Animation)) { + return; + } + + const auto layerProps = scheduler::LayerProps{ + .visible = snapshot.isVisible, + .bounds = snapshot.geomLayerBounds, + .transform = snapshot.geomLayerTransform, + .setFrameRateVote = snapshot.frameRate, + .frameRateSelectionPriority = snapshot.frameRateSelectionPriority, + }; + + auto it = mLegacyLayers.find(snapshot.sequence); + LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s", + snapshot.getDebugString().c_str()); + + if (snapshot.changes.test(Changes::Animation)) { + it->second->recordLayerHistoryAnimationTx(layerProps); + } + + if (snapshot.changes.test(Changes::FrameRate)) { + it->second->setFrameRateForLayerTree(snapshot.frameRate, layerProps); + } + + if (snapshot.changes.test(Changes::Buffer)) { + it->second->recordLayerHistoryBufferUpdate(layerProps); + } +} + +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()); + } + + bool mustComposite = false; + mustComposite |= 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, + .supportedLayerGenericMetadata = + getHwComposer().getSupportedLayerGenericMetadata(), + .genericLayerMetadataKeyMap = getGenericLayerMetadataKeyMap()}; + mLayerSnapshotBuilder.update(args); + } + + if (mLayerLifecycleManager.getGlobalChanges().any(Changes::Geometry | Changes::Input | + Changes::Hierarchy | Changes::Visibility)) { + mUpdateInputInfo = true; + } + if (mLayerLifecycleManager.getGlobalChanges().any(Changes::VisibleRegion | Changes::Hierarchy | + Changes::Visibility)) { + mVisibleRegionsDirty = true; + } + outTransactionsAreEmpty = mLayerLifecycleManager.getGlobalChanges().get() == 0; + mustComposite |= mLayerLifecycleManager.getGlobalChanges().get() != 0; + + bool newDataLatched = false; + if (!mLegacyFrontEndEnabled) { + ATRACE_NAME("DisplayCallbackAndStatsUpdates"); + applyTransactions(update.transactions, vsyncId); + const nsecs_t latchTime = systemTime(); + bool unused = false; + + for (auto& layer : mLayerLifecycleManager.getLayers()) { + if (layer->changes.test(frontend::RequestedLayerState::Changes::Created) && + layer->bgColorLayer) { + sp<Layer> bgColorLayer = getFactory().createEffectLayer( + LayerCreationArgs(this, nullptr, layer->name, + ISurfaceComposerClient::eFXSurfaceEffect, LayerMetadata(), + std::make_optional(layer->parentId), true)); + mLegacyLayers[bgColorLayer->sequence] = bgColorLayer; + } + if (!layer->hasReadyFrame()) continue; + + auto it = mLegacyLayers.find(layer->id); + LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s", + layer->getDebugString().c_str()); + const bool bgColorOnly = + !layer->externalTexture && (layer->bgColorLayerId != UNASSIGNED_LAYER_ID); + it->second->latchBufferImpl(unused, latchTime, bgColorOnly); + mLayersWithQueuedFrames.emplace(it->second); + } + + for (auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) { + updateLayerHistory(*snapshot); + if (!snapshot->hasReadyFrame) continue; + 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); + } + + { + ATRACE_NAME("LLM:commitChanges"); + mLayerLifecycleManager.commitChanges(); + } + + commitTransactions(); + + // enter boot animation on first buffer latch + if (CC_UNLIKELY(mBootStage == BootStage::BOOTLOADER && newDataLatched)) { + ALOGI("Enter boot animation"); + mBootStage = BootStage::BOOTANIMATION; + } + } + mustComposite |= (getTransactionFlags() & ~eTransactionFlushNeeded) || newDataLatched; + 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 @@ -2147,7 +2354,7 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe ticks<std::milli, float>(mExpectedPresentTime - TimePoint::now()), mExpectedPresentTime == expectedVsyncTime ? "" : " (adjusted)"); - const Period vsyncPeriod = mScheduler->getVsyncSchedule().period(); + 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 of 1ms @@ -2269,45 +2476,34 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe 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 @@ -2394,15 +2590,6 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) 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)) { @@ -2417,29 +2604,26 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) refreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::milliseconds(mDebugFlashDelay); } - const auto prevVsyncTime = mExpectedPresentTime - mScheduler->getVsyncSchedule().period(); + const auto prevVsyncTime = mExpectedPresentTime - mScheduler->getVsyncSchedule()->period(); const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration; refreshArgs.earliestPresentTime = prevVsyncTime - hwcMinWorkDuration; refreshArgs.previousPresentFence = mPreviousPresentFences[0].fenceTime; refreshArgs.scheduledFrameTime = mScheduler->getScheduledFrameTime(); refreshArgs.expectedPresentTime = mExpectedPresentTime.ns(); + refreshArgs.hasTrustedPresentationListener = mNumTrustedPresentationListeners > 0; // Store the present time just before calling to the composition engine so we could notify // 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); @@ -2503,7 +2687,7 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) // 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); + mScheduler->modulateVsync({}, &VsyncModulator::onDisplayRefresh, hasGpuUseOrReuse); mLayersWithQueuedFrames.clear(); if (mLayerTracingEnabled && mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) { @@ -2533,7 +2717,7 @@ void SurfaceFlinger::updateLayerGeometry() { for (auto& layer : mLayersPendingRefresh) { Region visibleReg; visibleReg.set(layer->getScreenBounds()); - invalidateLayerStack(layer, visibleReg); + invalidateLayerStack(layer->getOutputFilter(), visibleReg); } mLayersPendingRefresh.clear(); } @@ -2608,12 +2792,13 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { 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 { @@ -2622,8 +2807,9 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { 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}; @@ -2645,16 +2831,16 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { ? mPresentLatencyTracker.trackPendingFrame(compositeTime, presentFenceTime) : Duration::zero(); - const auto& schedule = mScheduler->getVsyncSchedule(); - const TimePoint vsyncDeadline = schedule.vsyncDeadlineAfter(presentTime); - const Period vsyncPeriod = schedule.period(); + const auto schedule = mScheduler->getVsyncSchedule(); + const TimePoint vsyncDeadline = schedule->vsyncDeadlineAfter(presentTime); + const Period vsyncPeriod = schedule->period(); const nsecs_t vsyncPhase = mVsyncConfiguration->getCurrentConfigs().late.sfOffset; const CompositorTiming compositorTiming(vsyncDeadline.ns(), vsyncPeriod.ns(), vsyncPhase, presentLatency.ns()); for (const auto& layer: mLayersWithQueuedFrames) { - layer->onPostComposition(display, glCompositionDoneFenceTime, presentFenceTime, + layer->onPostComposition(defaultDisplay, glCompositionDoneFenceTime, presentFenceTime, compositorTiming); layer->releasePendingBuffer(presentTime.ns()); } @@ -2722,23 +2908,27 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { mTimeStats->incrementTotalFrames(); mTimeStats->setPresentFenceGlobal(presentFenceTime); - const bool isInternalDisplay = display && - FTL_FAKE_GUARD(mStateLock, mPhysicalDisplays) - .get(display->getPhysicalId()) - .transform(&PhysicalDisplay::isInternal) - .value_or(false); - - if (isInternalDisplay && display && display->getPowerMode() == hal::PowerMode::ON && - presentFenceTime->isValid()) { - mScheduler->addPresentFence(std::move(presentFenceTime)); + { + ftl::FakeGuard guard(mStateLock); + for (const auto& [id, physicalDisplay] : mPhysicalDisplays) { + if (auto displayDevice = getDisplayDeviceLocked(id); + displayDevice && displayDevice->isPoweredOn() && physicalDisplay.isInternal()) { + auto presentFenceTimeI = defaultDisplay && defaultDisplay->getPhysicalId() == id + ? std::move(presentFenceTime) + : std::make_shared<FenceTime>(getHwComposer().getPresentFence(id)); + if (presentFenceTimeI->isValid()) { + mScheduler->addPresentFence(id, std::move(presentFenceTimeI)); + } + } + } } const bool isDisplayConnected = - display && getHwComposer().isConnected(display->getPhysicalId()); + defaultDisplay && getHwComposer().isConnected(defaultDisplay->getPhysicalId()); if (!hasSyncFramework) { - if (isDisplayConnected && display->isPoweredOn()) { - mScheduler->enableHardwareVsync(); + if (isDisplayConnected && defaultDisplay->isPoweredOn()) { + mScheduler->enableHardwareVsync(defaultDisplay->getPhysicalId()); } } @@ -2746,7 +2936,7 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { const size_t appConnections = mScheduler->getEventThreadConnectionCount(mAppConnectionHandle); mTimeStats->recordDisplayEventConnectionCount(sfConnections + appConnections); - if (isDisplayConnected && !display->isPoweredOn()) { + if (isDisplayConnected && !defaultDisplay->isPoweredOn()) { getRenderEngine().cleanupPostRender(); return; } @@ -2781,13 +2971,19 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { } // We avoid any reverse traversal upwards so this shouldn't be too expensive - mDrawingState.traverse([&](Layer* layer) { + traverseLegacyLayers([&](Layer* layer) { if (!layer->hasTrustedPresentationListener()) { return; } - const auto display = - layerStackToDisplay.get(layer->getLayerSnapshot()->outputFilter.layerStack); - layer->updateTrustedPresentationState(display->get(), layer->getLayerSnapshot(), + const frontend::LayerSnapshot* snapshot = (mLayerLifecycleManagerEnabled) + ? mLayerSnapshotBuilder.getSnapshot(layer->sequence) + : layer->getLayerSnapshot(); + std::optional<const DisplayDevice*> displayOpt = std::nullopt; + if (snapshot) { + displayOpt = layerStackToDisplay.get(snapshot->outputFilter.layerStack); + } + const DisplayDevice* display = displayOpt.value_or(nullptr); + layer->updateTrustedPresentationState(display, snapshot, nanoseconds_to_milliseconds(callTime), false); }); } @@ -2848,7 +3044,7 @@ void SurfaceFlinger::commitTransactions() { // so we can call commitTransactionsLocked unconditionally. // We clear the flags with mStateLock held to guarantee that // mCurrentState won't change until the transaction is committed. - mScheduler->modulateVsync(&VsyncModulator::onTransactionCommit); + mScheduler->modulateVsync({}, &VsyncModulator::onTransactionCommit); commitTransactionsLocked(clearTransactionFlags(eTransactionMask)); mDebugInTransaction = 0; @@ -3039,24 +3235,11 @@ sp<DisplayDevice> SurfaceFlinger::setupNewDisplayDeviceInternal( const auto [kernelIdleTimerController, idleTimerTimeoutMs] = getKernelIdleTimerProperties(compositionDisplay->getId()); - const auto enableFrameRateOverride = [&] { - using Config = scheduler::RefreshRateSelector::Config; - if (!sysprop::enable_frame_rate_override(true)) { - return Config::FrameRateOverride::Disabled; - } - - if (sysprop::frame_rate_override_for_native_rates(false)) { - return Config::FrameRateOverride::AppOverrideNativeRefreshRates; - } - - if (!sysprop::frame_rate_override_global(true)) { - return Config::FrameRateOverride::AppOverride; - } - - return Config::FrameRateOverride::Enabled; - }(); - - scheduler::RefreshRateSelector::Config config = + using Config = scheduler::RefreshRateSelector::Config; + const auto enableFrameRateOverride = sysprop::enable_frame_rate_override(true) + ? Config::FrameRateOverride::Enabled + : Config::FrameRateOverride::Disabled; + Config config = {.enableFrameRateOverride = enableFrameRateOverride, .frameRateMultipleThreshold = base::GetIntProperty("debug.sf.frame_rate_multiple_threshold", 0), @@ -3233,7 +3416,7 @@ void SurfaceFlinger::processDisplayAdded(const wp<IBinder>& displayToken, } if (display->isVirtual()) { - display->adjustRefreshRate(mScheduler->getLeaderRefreshRate()); + display->adjustRefreshRate(mScheduler->getPacesetterRefreshRate()); } mDisplays.try_emplace(displayToken, std::move(display)); @@ -3302,7 +3485,7 @@ void SurfaceFlinger::processDisplayChanged(const wp<IBinder>& displayToken, // TODO(b/175678251) Call a listener instead. if (currentState.physical->hwcDisplayId == getHwComposer().getPrimaryHwcDisplayId()) { - updateInternalDisplayVsyncLocked(display); + updateActiveDisplayVsyncLocked(*display); } } return; @@ -3330,15 +3513,15 @@ void SurfaceFlinger::processDisplayChanged(const wp<IBinder>& displayToken, display->setDisplaySize(currentState.width, currentState.height); if (display->getId() == mActiveDisplayId) { - onActiveDisplaySizeChanged(display); + onActiveDisplaySizeChanged(*display); } } } } -void SurfaceFlinger::updateInternalDisplayVsyncLocked(const sp<DisplayDevice>& activeDisplay) { +void SurfaceFlinger::updateActiveDisplayVsyncLocked(const DisplayDevice& activeDisplay) { mVsyncConfiguration->reset(); - const Fps refreshRate = activeDisplay->getActiveMode().fps; + const Fps refreshRate = activeDisplay.getActiveMode().fps; updatePhaseConfiguration(refreshRate); mRefreshRateStats->setRefreshRate(refreshRate); } @@ -3387,7 +3570,8 @@ void SurfaceFlinger::processDisplayChangesLocked() { 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) { @@ -3478,11 +3662,15 @@ void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { // this layer is not visible anymore Region visibleReg; visibleReg.set(layer->getScreenBounds()); - invalidateLayerStack(sp<Layer>::fromExisting(layer), visibleReg); + invalidateLayerStack(layer->getOutputFilter(), visibleReg); } }); } + if (transactionFlags & eInputInfoUpdateNeeded) { + mUpdateInputInfo = true; + } + doCommitTransactions(); } @@ -3566,16 +3754,23 @@ void SurfaceFlinger::buildWindowInfos(std::vector<WindowInfo>& outWindowInfos, outWindowInfos.reserve(sNumWindowInfos); sNumWindowInfos = 0; - 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}; - }); + 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}; + }); - outWindowInfos.push_back(layer->fillInputInfo(opt.value_or(Layer::InputDisplayArgs{}))); - }); + outWindowInfos.push_back(layer->fillInputInfo(opt.value_or(Layer::InputDisplayArgs{}))); + }); + } sNumWindowInfos = outWindowInfos.size(); @@ -3592,17 +3787,9 @@ void SurfaceFlinger::updateCursorAsync() { 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) { @@ -3687,10 +3874,9 @@ void SurfaceFlinger::initScheduler(const sp<const DisplayDevice>& display) { mScheduler = std::make_unique<Scheduler>(static_cast<ICompositor&>(*this), static_cast<ISchedulerCallback&>(*this), features, std::move(modulatorPtr)); - mScheduler->createVsyncSchedule(features); mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector()); - setVsyncEnabled(false); + setVsyncEnabled(display->getPhysicalId(), false); mScheduler->startTimers(); const auto configs = mVsyncConfiguration->getCurrentConfigs(); @@ -3706,7 +3892,7 @@ void SurfaceFlinger::initScheduler(const sp<const DisplayDevice>& display) { /* workDuration */ activeRefreshRate.getPeriod(), /* readyDuration */ configs.late.sfWorkDuration); - mScheduler->initVsync(mScheduler->getVsyncSchedule().getDispatch(), + mScheduler->initVsync(mScheduler->getVsyncSchedule()->getDispatch(), *mFrameTimeline->getTokenManager(), configs.late.sfWorkDuration); mRegionSamplingThread = @@ -3778,10 +3964,10 @@ void SurfaceFlinger::commitOffscreenLayers() { } } -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); } } @@ -3818,6 +4004,14 @@ bool SurfaceFlinger::latchBuffers() { mLayersWithQueuedFrames.emplace(sp<Layer>::fromExisting(layer)); } else { layer->useEmptyDamage(); + if (!layer->hasBuffer()) { + // The last latch time is used to classify a missed frame as buffer stuffing + // instead of a missed frame. This is used to identify scenarios where we + // could not latch a buffer or apply a transaction due to backpressure. + // We only update the latch time for buffer less layers here, the latch time + // is updated for buffer layers when the buffer is latched. + layer->updateLastLatchTime(latchTime); + } } }); mForceTransactionDisplayChange = false; @@ -3901,6 +4095,7 @@ status_t SurfaceFlinger::addClientLayer(const LayerCreationArgs& args, const sp< { 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); @@ -3912,14 +4107,18 @@ uint32_t SurfaceFlinger::getTransactionFlags() const { } 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); + 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 @@ -3963,20 +4162,40 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC const TransactionHandler::TransactionFlushState& flushState) { using TransactionReadiness = TransactionHandler::TransactionReadiness; auto ready = TransactionReadiness::Ready; - flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const layer_state_t& s) -> bool { + flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const layer_state_t& s, + const std::shared_ptr< + renderengine:: + ExternalTexture>& + externalTexture) + -> bool { 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, + externalTexture->getBuffer(), + 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; + } } } @@ -3987,7 +4206,7 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC if (layer->backpressureEnabled() && hasPendingBuffer && transaction.isAutoTimestamp) { ATRACE_NAME("hasPendingBuffer"); ready = TransactionReadiness::NotReady; - return false; + return TraverseBuffersReturnValues::STOP_TRAVERSAL; } // check fence status @@ -4014,14 +4233,14 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC "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; @@ -4088,7 +4307,7 @@ bool SurfaceFlinger::frameIsEarly(TimePoint expectedPresentTime, VsyncId vsyncId return false; } - const Duration earlyLatchVsyncThreshold = mScheduler->getVsyncSchedule().period() / 2; + const Duration earlyLatchVsyncThreshold = mScheduler->getVsyncSchedule()->period() / 2; return predictedPresentTime >= expectedPresentTime && predictedPresentTime - expectedPresentTime >= earlyLatchVsyncThreshold; @@ -4234,9 +4453,8 @@ status_t SurfaceFlinger::setTransactionState( }(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; } @@ -4251,9 +4469,11 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin 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 @@ -4265,15 +4485,26 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin 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; - mScheduler->recordLayerHistory(layer.get(), - isAutoTimestamp ? 0 : desiredPresentTime, - LayerUpdateType::AnimationTX); + const auto layerProps = scheduler::LayerProps{ + .visible = layer->isVisible(), + .bounds = layer->getBounds(), + .transform = layer->getTransform(), + .setFrameRateVote = layer->getFrameRateForLayerTree(), + .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(), + }; + layer->recordLayerHistoryAnimationTx(layerProps); } } } @@ -4300,8 +4531,8 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin 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; @@ -4314,6 +4545,43 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin 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()); + } + needsTraversal = true; + } + + return needsTraversal; +} + uint32_t SurfaceFlinger::setDisplayStateLocked(const DisplayState& s) { const ssize_t index = mCurrentState.displays.indexOfKey(s.token); if (index < 0) return 0; @@ -4417,8 +4685,10 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime } 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; } @@ -4486,7 +4756,7 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime } } if (what & layer_state_t::eBackgroundColorChanged) { - if (layer->setBackgroundColor(s.color.rgb, s.bgColorAlpha, s.bgColorDataspace)) { + if (layer->setBackgroundColor(s.bgColor.rgb, s.bgColor.a, s.bgColorDataspace)) { flags |= eTraversalNeeded; } } @@ -4626,6 +4896,11 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime flags |= eTraversalNeeded; } } + if (what & layer_state_t::eCachingHintChanged) { + if (layer->setCachingHint(s.cachingHint)) { + flags |= eTraversalNeeded; + } + } if (what & layer_state_t::eHdrMetadataChanged) { if (layer->setHdrMetadata(s.hdrMetadata)) flags |= eTraversalNeeded; } @@ -4690,12 +4965,26 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime layer->setFrameTimelineVsyncForBufferlessTransaction(frameTimelineInfo, postTime); } + if ((what & layer_state_t::eBufferChanged) == 0) { + layer->setDesiredPresentTime(desiredPresentTime, isAutoTimestamp); + } + if (what & layer_state_t::eTrustedPresentationInfoChanged) { - layer->setTrustedPresentationInfo(s.trustedPresentationThresholds, - s.trustedPresentationListener); + if (layer->setTrustedPresentationInfo(s.trustedPresentationThresholds, + s.trustedPresentationListener)) { + 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; } - if (layer->setTransactionCompletedListeners(callbackHandles)) flags |= eTraversalNeeded; // Do not put anything that updates layer state or modifies flags after // setTransactionCompletedListener @@ -4708,6 +4997,106 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime 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); + + 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)); + } + } + // TODO(b/238781169) remove after screenshot refactor, currently screenshots + // requires to read drawing state from binder thread. So we need to fix that + // before removing this. + if (what & layer_state_t::eCropChanged) { + if (layer->setCrop(s.crop)) flags |= eTraversalNeeded; + } + 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)) { + flags |= eTraversalNeeded; + } + mLayersWithQueuedFrames.emplace(layer); + } else if (frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) { + layer->setFrameTimelineVsyncForBufferlessTransaction(frameTimelineInfo, postTime); + } + + if ((what & layer_state_t::eBufferChanged) == 0) { + layer->setDesiredPresentTime(desiredPresentTime, isAutoTimestamp); + } + + if (what & layer_state_t::eTrustedPresentationInfoChanged) { + if (layer->setTrustedPresentationInfo(s.trustedPresentationThresholds, + s.trustedPresentationListener)) { + flags |= eTraversalNeeded; + } + } + + const auto& requestedLayerState = mLayerLifecycleManager.getLayerFromId(layer->getSequence()); + bool willPresentCurrentTransaction = + requestedLayerState && requestedLayerState->hasReadyFrame(); + 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; @@ -4776,6 +5165,7 @@ status_t SurfaceFlinger::mirrorDisplay(DisplayId displayId, const LayerCreationA 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()); @@ -4792,7 +5182,7 @@ status_t SurfaceFlinger::mirrorDisplay(DisplayId displayId, const LayerCreationA args.name, args.flags, -1 /* parentId */); } - { + if (mLegacyFrontEndEnabled) { std::scoped_lock<std::mutex> lock(mMirrorDisplayLock); mMirrorDisplays.emplace_back(layerStack, outResult.handle, args.client); } @@ -4878,7 +5268,12 @@ void SurfaceFlinger::markLayerPendingRemovalLocked(const sp<Layer>& layer) { 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); @@ -4886,6 +5281,8 @@ void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32 if (mTransactionTracing) { mTransactionTracing->onHandleRemoved(handle); } + + setTransactionFlags(eTransactionFlushNeeded); } void SurfaceFlinger::onInitializeDisplays() { @@ -4895,8 +5292,18 @@ void SurfaceFlinger::onInitializeDisplays() { const sp<IBinder> token = display->getDisplayToken().promote(); LOG_ALWAYS_FATAL_IF(token == nullptr); + TransactionState state; + state.inputWindowCommands = mInputWindowCommands; + nsecs_t now = systemTime(); + state.desiredPresentTime = now; + state.postTime = now; + state.permissions = layer_state_t::ACCESS_SURFACE_FLINGER; + state.originPid = mPid; + state.originUid = static_cast<int>(getuid()); + uint64_t transactionId = (((uint64_t)mPid) << 32) | mUniqueTransactionId++; + state.id = transactionId; + // reset screen orientation and use primary layer stack - std::vector<ResolvedComposerState> state; Vector<DisplayState> displays; DisplayState d; d.what = DisplayState::eDisplayProjectionChanged | @@ -4908,15 +5315,17 @@ void SurfaceFlinger::onInitializeDisplays() { d.layerStackSpaceRect.makeInvalid(); d.width = 0; d.height = 0; - displays.add(d); + state.displays.add(d); - nsecs_t now = systemTime(); + std::vector<TransactionState> transactions; + transactions.emplace_back(state); - int64_t transactionId = (((int64_t)mPid) << 32) | mUniqueTransactionId++; // It should be on the main thread, apply it directly. - applyTransactionState(FrameTimelineInfo{}, state, displays, 0, mInputWindowCommands, - /* desiredPresentTime */ now, true, {}, /* postTime */ now, true, false, - {}, mPid, getuid(), transactionId); + if (mLegacyFrontEndEnabled) { + applyTransactionsLocked(transactions, /*vsyncId=*/{0}); + } else { + applyAndCommitDisplayTransactionStates(transactions); + } setPowerModeInternal(display, hal::PowerMode::ON); } @@ -4942,7 +5351,6 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal: return; } - const bool isActiveDisplay = displayId == mActiveDisplayId; const bool isInternalDisplay = mPhysicalDisplays.get(displayId) .transform(&PhysicalDisplay::isInternal) .value_or(false); @@ -4959,21 +5367,15 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal: if (!currentModeOpt || *currentModeOpt == hal::PowerMode::OFF) { // Turn on the display - // Activate the display (which involves a modeset to the active mode): - // 1) When the first (a.k.a. primary) display is powered on during boot. - // 2) When the inner or outer display of a foldable is powered on. This condition relies - // on the above DisplayDevice::setPowerMode. If `display` and `activeDisplay` are the - // same display, then the `activeDisplay->isPoweredOn()` below is true, such that the - // display is not activated every time it is powered on. + // Activate the display (which involves a modeset to the active mode) when the inner or + // outer display of a foldable is powered on. This condition relies on the above + // DisplayDevice::setPowerMode. If `display` and `activeDisplay` are the same display, + // then the `activeDisplay->isPoweredOn()` below is true, such that the display is not + // activated every time it is powered on. // // TODO(b/255635821): Remove the concept of active display. - const bool activeDisplayChanged = - isInternalDisplay && (!activeDisplay || !activeDisplay->isPoweredOn()); - - static bool sPrimaryDisplay = true; - if (sPrimaryDisplay || activeDisplayChanged) { - onActiveDisplayChangedLocked(activeDisplay, display); - sPrimaryDisplay = false; + if (isInternalDisplay && (!activeDisplay || !activeDisplay->isPoweredOn())) { + onActiveDisplayChangedLocked(activeDisplay.get(), *display); } // Keep uclamp in a separate syscall and set it before changing to RT due to b/190237315. @@ -4985,10 +5387,12 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal: ALOGW("Couldn't set SCHED_FIFO on display on: %s\n", strerror(errno)); } getHwComposer().setPowerMode(displayId, mode); - if (isActiveDisplay && mode != hal::PowerMode::DOZE_SUSPEND) { - setHWCVsyncEnabled(displayId, mHWCVsyncPendingState); - mScheduler->onScreenAcquired(mAppConnectionHandle); - mScheduler->resyncToHardwareVsync(true, refreshRate); + if (displayId == mActiveDisplayId && mode != hal::PowerMode::DOZE_SUSPEND) { + setHWCVsyncEnabled(displayId, + mScheduler->getVsyncSchedule(displayId) + ->getPendingHardwareVsyncState()); + mScheduler->enableSyntheticVsync(false); + mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, refreshRate); } mVisibleRegionsDirty = true; @@ -5001,13 +5405,13 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal: if (SurfaceFlinger::setSchedAttr(false) != NO_ERROR) { ALOGW("Couldn't set uclamp.min on display off: %s\n", strerror(errno)); } - if (isActiveDisplay && *currentModeOpt != hal::PowerMode::DOZE_SUSPEND) { - mScheduler->disableHardwareVsync(true); - mScheduler->onScreenReleased(mAppConnectionHandle); + if (displayId == mActiveDisplayId && *currentModeOpt != hal::PowerMode::DOZE_SUSPEND) { + mScheduler->disableHardwareVsync(displayId, true); + mScheduler->enableSyntheticVsync(); } // Make sure HWVsync is disabled before turning off the display - setHWCVsyncEnabled(displayId, hal::Vsync::DISABLE); + setHWCVsyncEnabled(displayId, false); getHwComposer().setPowerMode(displayId, mode); mVisibleRegionsDirty = true; @@ -5015,18 +5419,18 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal: } else if (mode == hal::PowerMode::DOZE || mode == hal::PowerMode::ON) { // Update display while dozing getHwComposer().setPowerMode(displayId, mode); - if (isActiveDisplay && *currentModeOpt == hal::PowerMode::DOZE_SUSPEND) { + if (displayId == mActiveDisplayId && *currentModeOpt == hal::PowerMode::DOZE_SUSPEND) { ALOGI("Force repainting for DOZE_SUSPEND -> DOZE or ON."); mVisibleRegionsDirty = true; scheduleRepaint(); - mScheduler->onScreenAcquired(mAppConnectionHandle); - mScheduler->resyncToHardwareVsync(true, refreshRate); + mScheduler->enableSyntheticVsync(false); + mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, refreshRate); } } else if (mode == hal::PowerMode::DOZE_SUSPEND) { // Leave display going to doze - if (isActiveDisplay) { - mScheduler->disableHardwareVsync(true); - mScheduler->onScreenReleased(mAppConnectionHandle); + if (displayId == mActiveDisplayId) { + mScheduler->disableHardwareVsync(displayId, true); + mScheduler->enableSyntheticVsync(); } getHwComposer().setPowerMode(displayId, mode); } else { @@ -5034,10 +5438,10 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal: getHwComposer().setPowerMode(displayId, mode); } - if (isActiveDisplay) { + if (displayId == mActiveDisplayId) { mTimeStats->setPowerMode(mode); mRefreshRateStats->setPowerMode(mode); - mScheduler->setDisplayPowerMode(mode); + mScheduler->setDisplayPowerMode(displayId, mode); } ALOGD("Finished setting power mode %d on display %s", mode, to_string(displayId).c_str()); @@ -5216,14 +5620,6 @@ void SurfaceFlinger::dumpScheduler(std::string& result) const { 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(); @@ -5368,14 +5764,27 @@ LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const { } } + if (mLegacyFrontEndEnabled) { + LayersProto layersProto; + for (const sp<Layer>& layer : mDrawingState.layersSortedByZ) { + if (stackIdsToSkip.find(layer->getLayerStack().id) != stackIdsToSkip.end()) { + continue; + } + layer->writeToProto(layersProto, traceFlags); + } + return layersProto; + } + + const frontend::LayerHierarchy& root = mLayerHierarchyBuilder.getHierarchy(); LayersProto layersProto; - for (const sp<Layer>& layer : mDrawingState.layersSortedByZ) { - if (stackIdsToSkip.find(layer->getLayerStack().id) != stackIdsToSkip.end()) { + for (auto& [child, variant] : root.mChildren) { + if (variant != frontend::LayerHierarchy::Variant::Attached || + stackIdsToSkip.find(child->getLayer()->layerStack.id) != stackIdsToSkip.end()) { continue; } - layer->writeToProto(layersProto, traceFlags); + LayerProtoHelper::writeHierarchyToProto(layersProto, *child, mLayerSnapshotBuilder, + mLegacyLayers, traceFlags); } - return layersProto; } @@ -6455,10 +6864,16 @@ status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, args.useIdentityTransform, args.captureSecureLayers); }); - auto traverseLayers = [this, args, layerStack](const LayerVector::Visitor& visitor) { - traverseLayersInLayerStack(layerStack, args.uid, visitor); - }; - auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + GetLayerSnapshotsFunction getLayerSnapshots; + if (mLayerLifecycleManagerEnabled) { + getLayerSnapshots = + getLayerSnapshotsForScreenshots(layerStack, args.uid, /*snapshotFilterFn=*/nullptr); + } 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), getLayerSnapshots, reqSize, args.pixelFormat, args.allowProtected, args.grayscale, @@ -6492,10 +6907,16 @@ status_t SurfaceFlinger::captureDisplay(DisplayId displayId, false /* captureSecureLayers */); }); - auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) { - traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, visitor); - }; - auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + GetLayerSnapshotsFunction getLayerSnapshots; + if (mLayerLifecycleManagerEnabled) { + getLayerSnapshots = getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID, + /*snapshotFilterFn=*/nullptr); + } 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"); @@ -6590,29 +7011,37 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, return std::make_unique<LayerRenderArea>(*this, parent, crop, reqSize, dataspace, childrenOnly, args.captureSecureLayers); }); - - 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; - } - - auto p = sp<Layer>::fromExisting(layer); - while (p != nullptr) { - if (excludeLayerIds.count(p->sequence) != 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 getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + 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"); @@ -6649,7 +7078,7 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenCommon( ->schedule([=]() { bool protectedLayerFound = false; auto layers = getLayerSnapshots(); - for (auto& [layer, layerFe] : layers) { + for (auto& [_, layerFe] : layers) { protectedLayerFound |= (layerFe->mSnapshot->isVisible && layerFe->mSnapshot->hasProtectedContent); @@ -6744,12 +7173,14 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl( ATRACE_CALL(); auto layers = getLayerSnapshots(); - for (auto& [layer, layerFE] : layers) { + for (auto& [_, 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; + layerFE->mSnapshot->geomInverseLayerTransform = + layerFE->mSnapshot->geomLayerTransform.inverse(); } // We allow the system server to take screenshots of secure layers for @@ -6860,6 +7291,16 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl( return presentFuture; } +void SurfaceFlinger::traverseLegacyLayers(const LayerVector::Visitor& visitor) const { + if (mLayerLifecycleManagerEnabled) { + for (auto& layer : mLegacyLayers) { + visitor(layer.second.get()); + } + } else { + mDrawingState.traverse(visitor); + } +} + // --------------------------------------------------------------------------- void SurfaceFlinger::State::traverse(const LayerVector::Visitor& visitor) const { @@ -7148,10 +7589,20 @@ status_t SurfaceFlinger::setOverrideFrameRate(uid_t uid, float frameRate) { } void SurfaceFlinger::enableRefreshRateOverlay(bool enable) { + bool setByHwc = getHwComposer().hasCapability(Capability::REFRESH_RATE_CHANGED_CALLBACK_DEBUG); for (const auto& [id, display] : mPhysicalDisplays) { if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal) { + if (setByHwc) { + const auto status = + getHwComposer().setRefreshRateChangedCallbackDebugEnabled(id, enable); + if (status != NO_ERROR) { + ALOGE("Error updating the refresh rate changed callback debug enabled"); + return; + } + } + if (const auto device = getDisplayDeviceLocked(id)) { - device->enableRefreshRateOverlay(enable, mRefreshRateOverlaySpinner, + device->enableRefreshRateOverlay(enable, setByHwc, mRefreshRateOverlaySpinner, mRefreshRateOverlayRenderRate, mRefreshRateOverlayShowInMiddle); } @@ -7261,40 +7712,41 @@ void SurfaceFlinger::sample() { mRegionSamplingThread->onCompositionComplete(mScheduler->getScheduledFrameTime()); } -void SurfaceFlinger::onActiveDisplaySizeChanged(const sp<const DisplayDevice>& activeDisplay) { - mScheduler->onActiveDisplayAreaChanged(activeDisplay->getWidth() * activeDisplay->getHeight()); - getRenderEngine().onActiveDisplaySizeChanged(activeDisplay->getSize()); +void SurfaceFlinger::onActiveDisplaySizeChanged(const DisplayDevice& activeDisplay) { + mScheduler->onActiveDisplayAreaChanged(activeDisplay.getWidth() * activeDisplay.getHeight()); + getRenderEngine().onActiveDisplaySizeChanged(activeDisplay.getSize()); } -void SurfaceFlinger::onActiveDisplayChangedLocked(const sp<DisplayDevice>& inactiveDisplay, - const sp<DisplayDevice>& activeDisplay) { +void SurfaceFlinger::onActiveDisplayChangedLocked(const DisplayDevice* inactiveDisplayPtr, + const DisplayDevice& activeDisplay) { ATRACE_CALL(); - if (inactiveDisplay) { - inactiveDisplay->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false); + if (inactiveDisplayPtr) { + inactiveDisplayPtr->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false); } - mActiveDisplayId = activeDisplay->getPhysicalId(); - activeDisplay->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true); + mActiveDisplayId = activeDisplay.getPhysicalId(); + activeDisplay.getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true); - updateInternalDisplayVsyncLocked(activeDisplay); + updateActiveDisplayVsyncLocked(activeDisplay); mScheduler->setModeChangePending(false); - mScheduler->setLeaderDisplay(mActiveDisplayId); + mScheduler->setPacesetterDisplay(mActiveDisplayId); onActiveDisplaySizeChanged(activeDisplay); - mActiveDisplayTransformHint = activeDisplay->getTransformHint(); + mActiveDisplayTransformHint = activeDisplay.getTransformHint(); - // The policy of the new active/leader display may have changed while it was inactive. In that - // case, its preferred mode has not been propagated to HWC (via setDesiredActiveMode). In either - // case, the Scheduler's cachedModeChangedParams must be initialized to the newly active mode, - // and the kernel idle timer of the newly active display must be toggled. + // The policy of the new active/pacesetter display may have changed while it was inactive. In + // that case, its preferred mode has not been propagated to HWC (via setDesiredActiveMode). In + // either case, the Scheduler's cachedModeChangedParams must be initialized to the newly active + // mode, and the kernel idle timer of the newly active display must be toggled. constexpr bool kForce = true; - applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay->refreshRateSelector(), kForce); + applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay.refreshRateSelector(), kForce); } status_t SurfaceFlinger::addWindowInfosListener( - const sp<IWindowInfosListener>& windowInfosListener) const { + const sp<IWindowInfosListener>& windowInfosListener) { mWindowInfosListenerInvoker->addWindowInfosListener(windowInfosListener); + setTransactionFlags(eInputInfoUpdateNeeded); return NO_ERROR; } @@ -7394,24 +7846,18 @@ bool SurfaceFlinger::commitMirrorDisplays(VsyncId vsyncId) { 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() { @@ -7439,6 +7885,169 @@ void SurfaceFlinger::updateLayerMetadataSnapshot() { }); } +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) { + nsecs_t currentTime = systemTime(); + 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); + snapshot->fps = getLayerFramerate(currentTime, snapshot->sequence); + 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, + std::function<bool(const frontend::LayerSnapshot&, bool& outStopTraversal)> + snapshotFilterFn) { + return [&, layerStack, uid]() { + std::vector<std::pair<Layer*, sp<LayerFE>>> layers; + bool stopTraversal = false; + mLayerSnapshotBuilder.forEachVisibleSnapshot( + [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) { + if (stopTraversal) { + return; + } + if (layerStack && snapshot->outputFilter.layerStack != *layerStack) { + return; + } + if (uid != CaptureArgs::UNSET_UID && snapshot->inputInfo.ownerUid != uid) { + return; + } + if (!snapshot->hasSomethingToDraw()) { + return; + } + if (snapshotFilterFn && !snapshotFilterFn(*snapshot, stopTraversal)) { + return; + } + + auto it = mLegacyLayers.find(snapshot->sequence); + LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), + "Couldnt find layer object for %s", + snapshot->getDebugString().c_str()); + Layer* legacyLayer = (it == mLegacyLayers.end()) ? nullptr : it->second.get(); + sp<LayerFE> layerFE = getFactory().createLayerFE(snapshot->name); + layerFE->mSnapshot = std::make_unique<frontend::LayerSnapshot>(*snapshot); + layers.emplace_back(legacyLayer, 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 [&, rootLayerId, uid, excludeLayerIds = std::move(excludeLayerIds), childrenOnly, + parentCrop]() { + auto root = mLayerHierarchyBuilder.getPartialHierarchy(rootLayerId, childrenOnly); + frontend::LayerSnapshotBuilder::Args + args{.root = root, + .layerLifecycleManager = mLayerLifecycleManager, + .forceUpdate = frontend::LayerSnapshotBuilder::ForceUpdateFlags::HIERARCHY, + .displays = mFrontEndDisplayInfos, + .displayChanges = true, + .globalShadowSettings = mDrawingState.globalShadowSettings, + .supportsBlur = mSupportsBlur, + .forceFullDamage = mForceFullDamage, + .parentCrop = {parentCrop}, + .excludeLayerIds = std::move(excludeLayerIds), + .supportedLayerGenericMetadata = + getHwComposer().getSupportedLayerGenericMetadata(), + .genericLayerMetadataKeyMap = getGenericLayerMetadataKeyMap()}; + mLayerSnapshotBuilder.update(args); + + auto getLayerSnapshotsFn = + getLayerSnapshotsForScreenshots({}, uid, /*snapshotFilterFn=*/nullptr); + 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() { @@ -7452,9 +8061,9 @@ binder::Status SurfaceComposerAIDL::bootFinished() { 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); @@ -7751,10 +8360,12 @@ binder::Status SurfaceComposerAIDL::getHdrConversionCapabilities( } binder::Status SurfaceComposerAIDL::setHdrConversionStrategy( - const gui::HdrConversionStrategy& hdrConversionStrategy) { + const gui::HdrConversionStrategy& hdrConversionStrategy, + int32_t* outPreferredHdrOutputType) { status_t status = checkAccessPermission(); if (status == OK) { - status = mFlinger->setHdrConversionStrategy(hdrConversionStrategy); + status = mFlinger->setHdrConversionStrategy(hdrConversionStrategy, + outPreferredHdrOutputType); } return binderStatusFromStatusT(status); } @@ -8146,8 +8757,12 @@ binder::Status SurfaceComposerAIDL::getMaxAcquiredBufferCount(int32_t* buffers) binder::Status SurfaceComposerAIDL::addWindowInfosListener( const sp<gui::IWindowInfosListener>& windowInfosListener) { status_t status; + const int pid = IPCThreadState::self()->getCallingPid(); const int uid = IPCThreadState::self()->getCallingUid(); - if (uid == AID_SYSTEM || uid == AID_GRAPHICS) { + // TODO(b/270566761) update permissions check so that only system_server and shell can add + // WindowInfosListeners + if (uid == AID_SYSTEM || uid == AID_GRAPHICS || + checkPermission(sAccessSurfaceFlinger, pid, uid)) { status = mFlinger->addWindowInfosListener(windowInfosListener); } else { status = PERMISSION_DENIED; @@ -8158,8 +8773,10 @@ binder::Status SurfaceComposerAIDL::addWindowInfosListener( binder::Status SurfaceComposerAIDL::removeWindowInfosListener( const sp<gui::IWindowInfosListener>& windowInfosListener) { status_t status; + const int pid = IPCThreadState::self()->getCallingPid(); const int uid = IPCThreadState::self()->getCallingUid(); - if (uid == AID_SYSTEM || uid == AID_GRAPHICS) { + if (uid == AID_SYSTEM || uid == AID_GRAPHICS || + checkPermission(sAccessSurfaceFlinger, pid, uid)) { status = mFlinger->removeWindowInfosListener(windowInfosListener); } else { status = PERMISSION_DENIED; diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 5b9bfd80f9..338531ff90 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -71,9 +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" @@ -102,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; @@ -125,6 +129,7 @@ class FrameTracer; class ScreenCapturer; class WindowInfosListenerInvoker; +using ::aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData; using frontend::TransactionHandler; using gui::CaptureArgs; using gui::DisplayCaptureArgs; @@ -157,7 +162,8 @@ enum { eDisplayTransactionNeeded = 0x04, eTransformHintUpdateNeeded = 0x08, eTransactionFlushNeeded = 0x10, - eTransactionMask = 0x1f, + eInputInfoUpdateNeeded = 0x20, + eTransactionMask = 0x3f, }; // Latch Unsignaled buffer behaviours @@ -449,6 +455,26 @@ private: 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; @@ -508,7 +534,8 @@ private: 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>&); @@ -532,7 +559,8 @@ private: status_t clearBootDisplayMode(const sp<IBinder>& displayToken); status_t getHdrConversionCapabilities( std::vector<gui::HdrConversionCapability>* hdrConversionCapaabilities) const; - status_t setHdrConversionStrategy(const gui::HdrConversionStrategy& hdrConversionStrategy); + status_t setHdrConversionStrategy(const gui::HdrConversionStrategy& hdrConversionStrategy, + int32_t*); status_t getHdrOutputConversionSupport(bool* outSupport) const; void setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on); void setGameContentType(const sp<IBinder>& displayToken, bool on); @@ -591,7 +619,7 @@ private: status_t getMaxAcquiredBufferCount(int* buffers) const; - status_t addWindowInfosListener(const sp<gui::IWindowInfosListener>& windowInfosListener) const; + status_t addWindowInfosListener(const sp<gui::IWindowInfosListener>& windowInfosListener); status_t removeWindowInfosListener( const sp<gui::IWindowInfosListener>& windowInfosListener) const; @@ -607,6 +635,7 @@ private: const hal::VsyncPeriodChangeTimeline&) override; void onComposerHalSeamlessPossible(hal::HWDisplayId) override; void onComposerHalVsyncIdle(hal::HWDisplayId) override; + void onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) override; // ICompositor overrides: void configure() override; @@ -618,7 +647,7 @@ private: // Toggles hardware VSYNC by calling into HWC. // TODO(b/241286146): Rename for self-explanatory API. - void setVsyncEnabled(bool) override; + void setVsyncEnabled(PhysicalDisplayId, bool) override; void requestDisplayModes(std::vector<display::DisplayModeRequest>) override; void kernelTimerChanged(bool expired) override; void triggerOnFrameRateOverridesChanged() override; @@ -687,6 +716,18 @@ private: 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); + void updateLayerHistory(const frontend::LayerSnapshot& snapshot); + LifecycleUpdate flushLifecycleUpdates() REQUIRES(kMainThreadContext); void updateInputFlinger(); void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext); @@ -714,6 +755,8 @@ private: 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(); @@ -729,7 +772,10 @@ private: 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. @@ -887,7 +933,7 @@ private: // 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) { @@ -953,9 +999,9 @@ private: */ 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>; @@ -1005,13 +1051,11 @@ private: VirtualDisplayId acquireVirtualDisplay(ui::Size, ui::PixelFormat) REQUIRES(mStateLock); void releaseVirtualDisplay(VirtualDisplayId); - // TODO(b/255635821): Replace pointers with references. `inactiveDisplay` is only ever `nullptr` - // in tests, and `activeDisplay` must not be `nullptr` as a precondition. - void onActiveDisplayChangedLocked(const sp<DisplayDevice>& inactiveDisplay, - const sp<DisplayDevice>& activeDisplay) + void onActiveDisplayChangedLocked(const DisplayDevice* inactiveDisplayPtr, + const DisplayDevice& activeDisplay) REQUIRES(mStateLock, kMainThreadContext); - void onActiveDisplaySizeChanged(const sp<const DisplayDevice>&); + void onActiveDisplaySizeChanged(const DisplayDevice&); /* * Debugging & dumpsys @@ -1077,20 +1121,29 @@ private: std::chrono::nanoseconds presentLatency); int getMaxAcquiredBufferCountForRefreshRate(Fps refreshRate) const; - void updateInternalDisplayVsyncLocked(const sp<DisplayDevice>& activeDisplay) + void updateActiveDisplayVsyncLocked(const DisplayDevice& activeDisplay) REQUIRES(mStateLock, kMainThreadContext); bool isHdrLayer(const frontend::LayerSnapshot& snapshot) const; ui::Rotation getPhysicalDisplayOrientation(DisplayId, bool isPrimary) const REQUIRES(mStateLock); + void traverseLegacyLayers(const LayerVector::Visitor& visitor) const; sp<StartPropertySetThread> mStartPropertySetThread; surfaceflinger::Factory& mFactory; pid_t mPid; std::future<void> mRenderEnginePrimeCacheFuture; - // access must be protected by mStateLock + // mStateLock has conventions related to the current thread, because only + // the main thread should modify variables protected by mStateLock. + // - read access from a non-main thread must lock mStateLock, since the main + // thread may modify these variables. + // - write access from a non-main thread is not permitted. + // - read access from the main thread can use an ftl::FakeGuard, since other + // threads must not modify these variables. + // - write access from the main thread must lock mStateLock, since another + // thread may be reading these variables. mutable Mutex mStateLock; State mCurrentState{LayerVector::StateSet::Current}; std::atomic<int32_t> mTransactionFlags = 0; @@ -1145,6 +1198,7 @@ private: // 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; @@ -1281,9 +1335,6 @@ private: 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; @@ -1319,23 +1370,11 @@ private: 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; @@ -1357,6 +1396,13 @@ private: 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<bool(const frontend::LayerSnapshot&, bool& outStopTraversal)> + snapshotFilterFn); + 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; @@ -1369,6 +1415,18 @@ private: 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; @@ -1376,6 +1434,7 @@ private: TransactionHandler mTransactionHandler; display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos; + bool mFrontEndDisplayInfosChanged = false; }; class SurfaceComposerAIDL : public gui::BnSurfaceComposer { @@ -1385,6 +1444,7 @@ public: 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, @@ -1413,8 +1473,8 @@ public: binder::Status getOverlaySupport(gui::OverlayProperties* outProperties) override; binder::Status getHdrConversionCapabilities( std::vector<gui::HdrConversionCapability>*) override; - binder::Status setHdrConversionStrategy( - const gui::HdrConversionStrategy& hdrConversionStrategy) override; + binder::Status setHdrConversionStrategy(const gui::HdrConversionStrategy& hdrConversionStrategy, + int32_t*) override; binder::Status getHdrOutputConversionSupport(bool* outSupport) override; binder::Status setAutoLowLatencyMode(const sp<IBinder>& display, bool on) override; binder::Status setGameContentType(const sp<IBinder>& display, bool on) override; diff --git a/services/surfaceflinger/SurfaceFlingerProperties.cpp b/services/surfaceflinger/SurfaceFlingerProperties.cpp index 5b7303090d..20fa091730 100644 --- a/services/surfaceflinger/SurfaceFlingerProperties.cpp +++ b/services/surfaceflinger/SurfaceFlingerProperties.cpp @@ -367,14 +367,6 @@ bool enable_frame_rate_override(bool defaultValue) { return SurfaceFlingerProperties::enable_frame_rate_override().value_or(defaultValue); } -bool frame_rate_override_for_native_rates(bool defaultValue) { - return SurfaceFlingerProperties::frame_rate_override_for_native_rates().value_or(defaultValue); -} - -bool frame_rate_override_global(bool defaultValue) { - return SurfaceFlingerProperties::frame_rate_override_global().value_or(defaultValue); -} - bool enable_layer_caching(bool defaultValue) { return SurfaceFlingerProperties::enable_layer_caching().value_or(defaultValue); } diff --git a/services/surfaceflinger/SurfaceFlingerProperties.h b/services/surfaceflinger/SurfaceFlingerProperties.h index 09629cf93e..080feee686 100644 --- a/services/surfaceflinger/SurfaceFlingerProperties.h +++ b/services/surfaceflinger/SurfaceFlingerProperties.h @@ -96,10 +96,6 @@ bool update_device_product_info_on_hotplug_reconnect(bool defaultValue); bool enable_frame_rate_override(bool defaultValue); -bool frame_rate_override_for_native_rates(bool defaultValue); - -bool frame_rate_override_global(bool defaultValue); - bool enable_layer_caching(bool defaultValue); bool enable_sdr_dimming(bool defaultValue); diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp index 2f464873ea..ba08ceebc7 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp @@ -15,6 +15,7 @@ */ #include <gui/SurfaceComposerClient.h> +#include <renderengine/mock/FakeExternalTexture.h> #include <ui/Fence.h> #include <ui/Rect.h> @@ -220,12 +221,12 @@ proto::LayerState TransactionProtoParser::toProto(const layer_state_t& layer) { } } if (layer.what & layer_state_t::eBackgroundColorChanged) { - proto.set_bg_color_alpha(layer.bgColorAlpha); + proto.set_bg_color_alpha(layer.bgColor.a); proto.set_bg_color_dataspace(static_cast<int32_t>(layer.bgColorDataspace)); proto::LayerState_Color3* colorProto = proto.mutable_color(); - colorProto->set_r(layer.color.r); - colorProto->set_g(layer.color.g); - colorProto->set_b(layer.color.b); + colorProto->set_r(layer.bgColor.r); + colorProto->set_g(layer.bgColor.g); + colorProto->set_b(layer.bgColor.b); } if (layer.what & layer_state_t::eColorSpaceAgnosticChanged) { proto.set_color_space_agnostic(layer.colorSpaceAgnostic); @@ -313,6 +314,14 @@ TransactionState TransactionProtoParser::fromProto(const proto::TransactionState ResolvedComposerState s; s.state.what = 0; fromProto(proto.layer_changes(i), s.state); + if (s.state.bufferData) { + s.externalTexture = std::make_shared< + renderengine::mock::FakeExternalTexture>(s.state.bufferData->getWidth(), + s.state.bufferData->getHeight(), + s.state.bufferData->getId(), + s.state.bufferData->getPixelFormat(), + s.state.bufferData->getUsage()); + } t.states.emplace_back(s); } @@ -501,12 +510,12 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta layer.windowInfoHandle = sp<gui::WindowInfoHandle>::make(inputInfo); } if (proto.what() & layer_state_t::eBackgroundColorChanged) { - layer.bgColorAlpha = proto.bg_color_alpha(); + layer.bgColor.a = proto.bg_color_alpha(); layer.bgColorDataspace = static_cast<ui::Dataspace>(proto.bg_color_dataspace()); const proto::LayerState_Color3& colorProto = proto.color(); - layer.color.r = colorProto.r(); - layer.color.g = colorProto.g(); - layer.color.b = colorProto.b(); + layer.bgColor.r = colorProto.r(); + layer.bgColor.g = colorProto.g(); + layer.bgColor.b = colorProto.b(); } if (proto.what() & layer_state_t::eColorSpaceAgnosticChanged) { layer.colorSpaceAgnostic = proto.color_space_agnostic(); diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index ab98dbfe2f..31f4723915 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -191,29 +191,20 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, return false; } - Factory mFactory; - sp<MockSurfaceFlinger> flinger = sp<MockSurfaceFlinger>::make(mFactory); - TestableSurfaceFlinger mFlinger(flinger); - mFlinger.setupRenderEngine( + Factory factory; + sp<MockSurfaceFlinger> flingerPtr = sp<MockSurfaceFlinger>::make(factory); + TestableSurfaceFlinger flinger(flingerPtr); + flinger.setupRenderEngine( std::make_unique<testing::NiceMock<renderengine::mock::RenderEngine>>()); - mock::VsyncController* mVsyncController = new testing::NiceMock<mock::VsyncController>(); - mock::VSyncTracker* mVSyncTracker = new testing::NiceMock<mock::VSyncTracker>(); - mock::EventThread* mEventThread = new testing::NiceMock<mock::EventThread>(); - mock::EventThread* mSFEventThread = new testing::NiceMock<mock::EventThread>(); - mFlinger.setupScheduler(std::unique_ptr<scheduler::VsyncController>(mVsyncController), - std::unique_ptr<scheduler::VSyncTracker>(mVSyncTracker), - std::unique_ptr<EventThread>(mEventThread), - std::unique_ptr<EventThread>(mSFEventThread), - TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp, - TestableSurfaceFlinger::kOneDisplayMode, true /* useNiceMock */); - - Hwc2::mock::Composer* mComposer = new testing::NiceMock<Hwc2::mock::Composer>(); - mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer)); - mFlinger.mutableMaxRenderTargetSize() = 16384; - - flinger->setLayerTracingFlags(LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS); - flinger->setLayerTraceSize(512 * 1024); // 512MB buffer size - flinger->startLayerTracing(traceFile.entry(0).elapsed_realtime_nanos()); + flinger.setupMockScheduler({.useNiceMock = true}); + + Hwc2::mock::Composer* composerPtr = new testing::NiceMock<Hwc2::mock::Composer>(); + flinger.setupComposer(std::unique_ptr<Hwc2::Composer>(composerPtr)); + flinger.mutableMaxRenderTargetSize() = 16384; + + flingerPtr->setLayerTracingFlags(LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS); + flingerPtr->setLayerTraceSize(512 * 1024); // 512MB buffer size + flingerPtr->startLayerTracing(traceFile.entry(0).elapsed_realtime_nanos()); std::unique_ptr<TraceGenFlingerDataMapper> mapper = std::make_unique<TraceGenFlingerDataMapper>(); TraceGenFlingerDataMapper* dataMapper = mapper.get(); @@ -234,7 +225,7 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, parser.fromProto(entry.added_layers(j), tracingArgs); gui::CreateSurfaceResult outResult; - LayerCreationArgs args(mFlinger.flinger(), nullptr /* client */, tracingArgs.name, + LayerCreationArgs args(flinger.flinger(), nullptr /* client */, tracingArgs.name, tracingArgs.flags, LayerMetadata(), std::make_optional<int32_t>(tracingArgs.layerId)); @@ -247,10 +238,10 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, } else if (tracingArgs.parentId != -1) { parentHandle = dataMapper->getLayerHandle(tracingArgs.parentId); } - mFlinger.createLayer(args, parentHandle, outResult); + flinger.createLayer(args, parentHandle, outResult); } else { sp<IBinder> mirrorFromHandle = dataMapper->getLayerHandle(tracingArgs.mirrorFromId); - mFlinger.mirrorLayer(args, mirrorFromHandle, outResult); + flinger.mirrorLayer(args, mirrorFromHandle, outResult); } LOG_ALWAYS_FATAL_IF(outResult.layerId != tracingArgs.layerId, "Could not create layer expected:%d actual:%d", tracingArgs.layerId, @@ -261,19 +252,19 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, for (int j = 0; j < entry.transactions_size(); j++) { // apply transactions TransactionState transaction = parser.fromProto(entry.transactions(j)); - mFlinger.setTransactionStateInternal(transaction); + flinger.setTransactionStateInternal(transaction); } const auto frameTime = TimePoint::fromNs(entry.elapsed_realtime_nanos()); const auto vsyncId = VsyncId{entry.vsync_id()}; - mFlinger.commit(frameTime, vsyncId); + flinger.commit(frameTime, vsyncId); for (int j = 0; j < entry.removed_layer_handles_size(); j++) { dataMapper->mLayerHandles.erase(entry.removed_layer_handles(j)); } } - flinger->stopLayerTracing(outputLayersTracePath); + flingerPtr->stopLayerTracing(outputLayersTracePath); ALOGD("End of generating trace file. File written to %s", outputLayersTracePath); dataMapper->mLayerHandles.clear(); return true; diff --git a/services/surfaceflinger/Tracing/tools/run.sh b/services/surfaceflinger/Tracing/tools/run.sh index baa93f1a1c..307a4d8338 100644 --- a/services/surfaceflinger/Tracing/tools/run.sh +++ b/services/surfaceflinger/Tracing/tools/run.sh @@ -5,7 +5,15 @@ set -ex # Build, push and run layertracegenerator $ANDROID_BUILD_TOP/build/soong/soong_ui.bash --make-mode layertracegenerator adb wait-for-device && adb push $OUT/system/bin/layertracegenerator /data/layertracegenerator -echo "Writing transaction trace to file" -adb shell service call SurfaceFlinger 1041 i32 0 -adb shell /data/layertracegenerator + +if [ -z "$1" ] + then + echo "Writing transaction trace to file" + adb shell service call SurfaceFlinger 1041 i32 0 + adb shell /data/layertracegenerator + else + echo "Pushing transaction trace to device" + adb push $1 /data/transaction_trace.winscope + adb shell /data/layertracegenerator /data/transaction_trace.winscope +fi adb pull /data/misc/wmtrace/layers_trace.winscope
\ No newline at end of file diff --git a/services/surfaceflinger/TransactionCallbackInvoker.cpp b/services/surfaceflinger/TransactionCallbackInvoker.cpp index e5de7591ef..3587a726cd 100644 --- a/services/surfaceflinger/TransactionCallbackInvoker.cpp +++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp @@ -92,11 +92,6 @@ status_t TransactionCallbackInvoker::addCallbackHandles( 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 @@ status_t TransactionCallbackInvoker::addCallbackHandle(const sp<CallbackHandle>& 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 @@ status_t TransactionCallbackInvoker::addCallbackHandle(const sp<CallbackHandle>& 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 @@ status_t TransactionCallbackInvoker::addCallbackHandle(const sp<CallbackHandle>& // 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 61ff9bce98..3074795f62 100644 --- a/services/surfaceflinger/TransactionCallbackInvoker.h +++ b/services/surfaceflinger/TransactionCallbackInvoker.h @@ -66,9 +66,6 @@ public: 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 5025c4935c..40d06a8a8c 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: @@ -75,12 +81,18 @@ struct TransactionState { } 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, state->externalTexture); + if (result == STOP_TRAVERSAL) return; + if (result == DELETE_AND_CONTINUE_TRAVERSAL) { + state = states.erase(state); + continue; + } } + state++; } } diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp index a1313e3a03..292083b9bc 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp +++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp @@ -17,7 +17,6 @@ #include <ftl/small_vector.h> #include <gui/ISurfaceComposer.h> -#include "SurfaceFlinger.h" #include "WindowInfosListenerInvoker.h" namespace android { diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h index a1d66a186e..d60a9c4157 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.h +++ b/services/surfaceflinger/WindowInfosListenerInvoker.h @@ -16,6 +16,8 @@ #pragma once +#include <unordered_set> + #include <android/gui/BnWindowInfosReportedListener.h> #include <android/gui/IWindowInfosListener.h> #include <android/gui/IWindowInfosReportedListener.h> @@ -49,8 +51,6 @@ private: static constexpr size_t kStaticCapacity = 3; ftl::SmallMap<wp<IBinder>, const sp<gui::IWindowInfosListener>, kStaticCapacity> mWindowInfosListeners GUARDED_BY(mListenersMutex); - - sp<gui::IWindowInfosReportedListener> mWindowInfosReportedListener; }; } // namespace android diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h index 6a6e3db733..1a951b34ac 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h @@ -41,6 +41,7 @@ class SurfaceComposerClient; 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 @@ struct TestHWC2ComposerCallback : public HWC2::ComposerCallback { 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 cdffbb4724..f27f53bd17 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -226,30 +226,28 @@ public: TestableScheduler(const std::shared_ptr<scheduler::RefreshRateSelector>& selectorPtr, sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback) : TestableScheduler(std::make_unique<android::mock::VsyncController>(), - std::make_unique<android::mock::VSyncTracker>(), selectorPtr, + std::make_shared<android::mock::VSyncTracker>(), selectorPtr, std::move(modulatorPtr), callback) {} TestableScheduler(std::unique_ptr<VsyncController> controller, - std::unique_ptr<VSyncTracker> tracker, + VsyncSchedule::TrackerPtr tracker, 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))); - const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId(); - registerDisplay(displayId, std::move(selectorPtr)); + registerDisplayInternal(displayId, std::move(selectorPtr), + std::shared_ptr<VsyncSchedule>( + new VsyncSchedule(displayId, std::move(tracker), nullptr, + std::move(controller)))); } ConnectionHandle createConnection(std::unique_ptr<EventThread> eventThread) { return Scheduler::createConnection(std::move(eventThread)); } - auto &mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; } - auto &mutableHWVsyncAvailable() { return mHWVsyncAvailable; } - auto &mutableLayerHistory() { return mLayerHistory; } - auto refreshRateSelector() { return leaderSelectorPtr(); } + auto refreshRateSelector() { return pacesetterSelectorPtr(); } void replaceTouchTimer(int64_t millis) { if (mTouchTimer) { @@ -649,10 +647,10 @@ public: // The ISchedulerCallback argument can be nullptr for a no-op implementation. void setupScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController, - std::unique_ptr<scheduler::VSyncTracker> vsyncTracker, + std::shared_ptr<scheduler::VSyncTracker> vsyncTracker, std::unique_ptr<EventThread> appEventThread, std::unique_ptr<EventThread> sfEventThread, - scheduler::ISchedulerCallback *callback = nullptr, + scheduler::ISchedulerCallback* callback = nullptr, bool hasMultipleModes = false) { constexpr DisplayModeId kModeId60{0}; DisplayModes modes = makeModes(mock::createDisplayMode(kModeId60, 60_Hz)); @@ -791,7 +789,7 @@ public: } private: - void setVsyncEnabled(bool) override {} + void setVsyncEnabled(PhysicalDisplayId, bool) override {} void requestDisplayModes(std::vector<display::DisplayModeRequest>) override {} void kernelTimerChanged(bool) override {} void triggerOnFrameRateOverridesChanged() override {} diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp index 11719c435e..c088e7b40c 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp @@ -148,7 +148,7 @@ void LayerFuzzer::invokeBufferStateLayer() { 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>(), diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index 44805dba82..f17d2e1cb4 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -48,6 +48,7 @@ constexpr PowerMode kPowerModes[] = {PowerMode::ON, PowerMode::DOZE, PowerMode:: constexpr uint16_t kRandomStringLength = 256; constexpr std::chrono::duration kSyncPeriod(16ms); +constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u); template <typename T> void dump(T* component, FuzzedDataProvider* fdp) { @@ -76,7 +77,7 @@ private: FuzzedDataProvider mFdp; - std::optional<scheduler::VsyncSchedule> mVsyncSchedule; + std::shared_ptr<scheduler::VsyncSchedule> mVsyncSchedule; }; PhysicalDisplayId SchedulerFuzzer::getPhysicalDisplayId() { @@ -90,12 +91,13 @@ PhysicalDisplayId SchedulerFuzzer::getPhysicalDisplayId() { } void SchedulerFuzzer::fuzzEventThread() { - mVsyncSchedule.emplace(scheduler::VsyncSchedule(std::make_unique<mock::VSyncTracker>(), - std::make_unique<mock::VSyncDispatch>(), - nullptr)); + mVsyncSchedule = std::shared_ptr<scheduler::VsyncSchedule>( + new scheduler::VsyncSchedule(getPhysicalDisplayId(), + std::make_shared<mock::VSyncTracker>(), + std::make_shared<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, + android::impl::EventThread>("fuzzer", mVsyncSchedule, nullptr, nullptr, getVsyncPeriod, (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(), (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>()); @@ -109,8 +111,7 @@ void SchedulerFuzzer::fuzzEventThread() { thread->setDuration((std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(), (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>()); thread->registerDisplayEventConnection(connection); - thread->onScreenAcquired(); - thread->onScreenReleased(); + thread->enableSyntheticVsync(mFdp.ConsumeBool()); dump<android::impl::EventThread>(thread.get(), &mFdp); } @@ -132,7 +133,7 @@ void SchedulerFuzzer::fuzzCallbackToken(scheduler::VSyncDispatchTimerQueue* disp } void SchedulerFuzzer::fuzzVSyncDispatchTimerQueue() { - FuzzImplVSyncTracker stubTracker{mFdp.ConsumeIntegral<nsecs_t>()}; + auto stubTracker = std::make_shared<FuzzImplVSyncTracker>(mFdp.ConsumeIntegral<nsecs_t>()); scheduler::VSyncDispatchTimerQueue mDispatch{std::make_unique<scheduler::ControllableClock>(), stubTracker, mFdp.ConsumeIntegral<nsecs_t>() /*dispatchGroupThreshold*/, @@ -145,17 +146,17 @@ void SchedulerFuzzer::fuzzVSyncDispatchTimerQueue() { scheduler::VSyncDispatchTimerQueueEntry entry( "fuzz", [](auto, auto, auto) {}, mFdp.ConsumeIntegral<nsecs_t>() /*vSyncMoveThreshold*/); - entry.update(stubTracker, 0); + entry.update(*stubTracker, 0); entry.schedule({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(), .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(), .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()}, - stubTracker, 0); + *stubTracker, 0); entry.disarm(); entry.ensureNotRunning(); entry.schedule({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(), .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(), .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()}, - stubTracker, 0); + *stubTracker, 0); auto const wakeup = entry.wakeupTime(); auto const ready = entry.readyTime(); entry.callback(entry.executing(), *wakeup, *ready); @@ -169,7 +170,8 @@ void SchedulerFuzzer::fuzzVSyncPredictor() { uint16_t now = mFdp.ConsumeIntegral<uint16_t>(); uint16_t historySize = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX); uint16_t minimumSamplesForPrediction = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX); - scheduler::VSyncPredictor tracker{mFdp.ConsumeIntegral<uint16_t>() /*period*/, historySize, + scheduler::VSyncPredictor tracker{DEFAULT_DISPLAY_ID, + mFdp.ConsumeIntegral<uint16_t>() /*period*/, historySize, minimumSamplesForPrediction, mFdp.ConsumeIntegral<uint32_t>() /*outlierTolerancePercent*/}; uint16_t period = mFdp.ConsumeIntegral<uint16_t>(); @@ -218,9 +220,9 @@ void SchedulerFuzzer::fuzzLayerHistory() { sp<FuzzImplLayer> layer2 = sp<FuzzImplLayer>::make(flinger.flinger()); for (int i = 0; i < historySize; ++i) { - historyV1.record(layer1.get(), time1, time1, + historyV1.record(layer1->getSequence(), layer1->getLayerProps(), time1, time1, scheduler::LayerHistory::LayerUpdateType::Buffer); - historyV1.record(layer2.get(), time2, time2, + historyV1.record(layer2->getSequence(), layer2->getLayerProps(), time2, time2, scheduler::LayerHistory::LayerUpdateType::Buffer); time1 += mFdp.PickValueInArray(kVsyncPeriods); time2 += mFdp.PickValueInArray(kVsyncPeriods); @@ -242,13 +244,15 @@ void SchedulerFuzzer::fuzzLayerHistory() { void SchedulerFuzzer::fuzzVSyncReactor() { std::shared_ptr<FuzzImplVSyncTracker> vSyncTracker = std::make_shared<FuzzImplVSyncTracker>(); - scheduler::VSyncReactor reactor(std::make_unique<ClockWrapper>( + scheduler::VSyncReactor reactor(DEFAULT_DISPLAY_ID, + std::make_unique<ClockWrapper>( std::make_shared<FuzzImplClock>()), *vSyncTracker, mFdp.ConsumeIntegral<uint8_t>() /*pendingLimit*/, false); - reactor.startPeriodTransition(mFdp.ConsumeIntegral<nsecs_t>()); - bool periodFlushed = mFdp.ConsumeBool(); + reactor.startPeriodTransition(mFdp.ConsumeIntegral<nsecs_t>(), mFdp.ConsumeBool()); + bool periodFlushed = false; // Value does not matter, since this is an out + // param from addHwVsyncTimestamp. reactor.addHwVsyncTimestamp(0, std::nullopt, &periodFlushed); reactor.addHwVsyncTimestamp(mFdp.ConsumeIntegral<nsecs_t>() /*newPeriod*/, std::nullopt, &periodFlushed); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h index 713b71042a..e6be9a8b21 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h @@ -100,7 +100,7 @@ public: return true; } - void setDivisor(unsigned) override {} + void setRenderRate(Fps) override {} nsecs_t nextVSyncTime(nsecs_t timePoint) const { if (timePoint % mPeriod == 0) { diff --git a/services/surfaceflinger/main_surfaceflinger.cpp b/services/surfaceflinger/main_surfaceflinger.cpp index fedd71e921..049567878f 100644 --- a/services/surfaceflinger/main_surfaceflinger.cpp +++ b/services/surfaceflinger/main_surfaceflinger.cpp @@ -139,11 +139,6 @@ int main(int, char**) { 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/sysprop/SurfaceFlingerProperties.sysprop b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop index 8540c3dcfc..bcbe21a483 100644 --- a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop +++ b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop @@ -445,28 +445,6 @@ prop { prop_name: "ro.surface_flinger.enable_frame_rate_override" } -# Limits the frame rate override feature (enable_frame_rate_override) to override the refresh rate -# to native display refresh rates only. Before introducing this flag, native display refresh rates -# was the default behaviour. With this flag we can control which behaviour we want explicitly. -# This flag is introduced as a fail-safe mechanism and planned to be defaulted to false. -prop { - api_name: "frame_rate_override_for_native_rates" - type: Boolean - scope: Public - access: Readonly - prop_name: "ro.surface_flinger.frame_rate_override_for_native_rates" -} - -# Enables the frame rate override feature (enable_frame_rate_override) to -# override the frame rate globally instead of only for individual apps. -prop { - api_name: "frame_rate_override_global" - type: Boolean - scope: Public - access: Readonly - prop_name: "ro.surface_flinger.frame_rate_override_global" -} - # Enables Layer Caching prop { api_name: "enable_layer_caching" diff --git a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt index 93381333a8..348a462038 100644 --- a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt +++ b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt @@ -61,14 +61,6 @@ props { prop_name: "ro.surface_flinger.force_hwc_copy_for_virtual_displays" } prop { - api_name: "frame_rate_override_for_native_rates" - prop_name: "ro.surface_flinger.frame_rate_override_for_native_rates" - } - prop { - api_name: "frame_rate_override_global" - prop_name: "ro.surface_flinger.frame_rate_override_global" - } - prop { api_name: "has_HDR_display" prop_name: "ro.surface_flinger.has_HDR_display" } diff --git a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp index bf7cae9091..0b8c51ec1d 100644 --- a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp +++ b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp @@ -544,6 +544,7 @@ void LayerRenderTypeTransactionTest::setBackgroundColorHelper(uint32_t layerType .apply(); { + SCOPED_TRACE("final color"); auto shot = screenshot(); shot->expectColor(Rect(0, 0, width, height), finalColor); shot->expectBorder(Rect(0, 0, width, height), Color::BLACK); diff --git a/services/surfaceflinger/tests/LayerTrustedPresentationListener_test.cpp b/services/surfaceflinger/tests/LayerTrustedPresentationListener_test.cpp index a843fd17b1..2be8d3b013 100644 --- a/services/surfaceflinger/tests/LayerTrustedPresentationListener_test.cpp +++ b/services/surfaceflinger/tests/LayerTrustedPresentationListener_test.cpp @@ -238,4 +238,50 @@ TEST_F(LayerTrustedPresentationListenerTest, obscuring_with_alpha) { EXPECT_TRUE(pch.awaitCallback()); } +TEST_F(LayerTrustedPresentationListenerTest, obscuring_with_display_overlay) { + auto otherLayer = makeLayer(); + t.show(otherLayer) + .setPosition(otherLayer, 100, 100) + .setLayer(otherLayer, INT32_MAX) + .setFlags(otherLayer, layer_state_t::eLayerSkipScreenshot, + layer_state_t::eLayerSkipScreenshot) + .setLayer(mainLayer, INT32_MAX - 1) + .show(mainLayer) + .setPosition(mainLayer, 100, 100) + .setTrustedPresentationCallback( + mainLayer, + [&](void* context, bool state) { + PresentationCallbackHelper* helper = (PresentationCallbackHelper*)context; + helper->callbackArrived(state); + }, + thresholds, &pch, mCallback) + .apply(); + EXPECT_TRUE(pch.awaitCallback()); +} + +TEST_F(LayerTrustedPresentationListenerTest, obscuring_with_non_overlapping_bounds) { + thresholds.minFractionRendered = 0.5; + auto otherLayer1 = makeLayer(); + auto otherLayer2 = makeLayer(); + t.show(otherLayer1) + .show(otherLayer2) + .setPosition(otherLayer1, 100, 25) + .setLayer(otherLayer1, INT32_MAX) + .setPosition(otherLayer2, 100, 175) + .setLayer(otherLayer2, INT32_MAX) + .setLayer(mainLayer, INT32_MAX - 1) + .show(mainLayer) + .setPosition(mainLayer, 100, 100) + .setTrustedPresentationCallback( + mainLayer, + [&](void* context, bool state) { + PresentationCallbackHelper* helper = (PresentationCallbackHelper*)context; + helper->callbackArrived(state); + }, + thresholds, &pch, mCallback) + .apply(); + + EXPECT_TRUE(pch.awaitCallback()); +} + } // namespace android diff --git a/services/surfaceflinger/tests/MirrorLayer_test.cpp b/services/surfaceflinger/tests/MirrorLayer_test.cpp index e69db7c167..0ea0824732 100644 --- a/services/surfaceflinger/tests/MirrorLayer_test.cpp +++ b/services/surfaceflinger/tests/MirrorLayer_test.cpp @@ -18,6 +18,7 @@ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wconversion" +#include <android-base/properties.h> #include <private/android_filesystem_config.h> #include "LayerTransactionTest.h" #include "utils/TransactionUtils.h" @@ -119,15 +120,20 @@ TEST_F(MirrorLayerTest, MirrorColorLayer) { shot->expectColor(Rect(750, 750, 950, 950), Color::BLACK); } - // Remove child layer + if (base::GetBoolProperty("debug.sf.enable_legacy_frontend", true)) { + GTEST_SKIP() << "Skipping test because mirroring behavior changes with legacy frontend"; + } + + // Remove child layer and verify we can still mirror the layer when + // its offscreen. Transaction().reparent(mChildLayer, nullptr).apply(); { SCOPED_TRACE("Removed Child Layer"); auto shot = screenshot(); // Grandchild mirror - shot->expectColor(Rect(550, 550, 750, 750), Color::RED); + shot->expectColor(Rect(550, 550, 750, 750), Color::BLACK); // Child mirror - shot->expectColor(Rect(750, 750, 950, 950), Color::RED); + shot->expectColor(Rect(750, 750, 950, 950), Color::BLACK); } // Add grandchild layer to offscreen layer @@ -136,9 +142,9 @@ TEST_F(MirrorLayerTest, MirrorColorLayer) { SCOPED_TRACE("Added Grandchild Layer"); auto shot = screenshot(); // Grandchild mirror - shot->expectColor(Rect(550, 550, 750, 750), Color::RED); + shot->expectColor(Rect(550, 550, 750, 750), Color::WHITE); // Child mirror - shot->expectColor(Rect(750, 750, 950, 950), Color::RED); + shot->expectColor(Rect(750, 750, 950, 950), Color::BLACK); } // Add child layer diff --git a/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp b/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp index 16076eaac9..c23fb9bd23 100644 --- a/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp +++ b/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp @@ -85,7 +85,8 @@ public: 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 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_FrameDropping) { 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 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_FrameDropping) { 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 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_Merge_Different_Processes) { 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 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_SetBuffer_OverwriteBuffers) { // 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 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_Merge_Transactions_OverwriteBuffers) // 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 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_Merge_Transactions_OverwriteBuffers) // 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 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_MergeBuffers_Different_Processes) { 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 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_MergeBuffers_Different_Processes) { 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 @@ TEST_F(ReleaseBufferCallbackTest, SetBuffer_OverwriteBuffersWithNull) { // 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 index e9b1fbb354..d0ab105414 100644 --- a/services/surfaceflinger/tests/TextureFiltering_test.cpp +++ b/services/surfaceflinger/tests/TextureFiltering_test.cpp @@ -187,8 +187,6 @@ TEST_F(TextureFilteringTest, LayerCaptureWithCropNoFiltering) { // 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; @@ -224,4 +222,17 @@ TEST_F(TextureFilteringTest, ParentCropNoFiltering) { mCapture->expectColor(Rect{50, 25, 75, 75}, Color::BLUE); } +// Expect no filtering because parent's position transform shouldn't scale the layer. +TEST_F(TextureFilteringTest, ParentHasTransformNoFiltering) { + Transaction().setPosition(mParent, 100, 100).apply(); + + LayerCaptureArgs captureArgs; + captureArgs.layerHandle = mParent->getHandle(); + captureArgs.sourceCrop = Rect{0, 0, 100, 100}; + ScreenCapture::captureLayers(&mCapture, captureArgs); + + mCapture->expectColor(Rect{0, 0, 50, 100}, Color::RED); + mCapture->expectColor(Rect{50, 0, 100, 100}, Color::BLUE); +} + } // namespace android diff --git a/services/surfaceflinger/tests/WindowInfosListener_test.cpp b/services/surfaceflinger/tests/WindowInfosListener_test.cpp index 53c3c3998f..d71486fca7 100644 --- a/services/surfaceflinger/tests/WindowInfosListener_test.cpp +++ b/services/surfaceflinger/tests/WindowInfosListener_test.cpp @@ -18,61 +18,61 @@ #include <gui/SurfaceComposerClient.h> #include <private/android_filesystem_config.h> #include <future> -#include "utils/TransactionUtils.h" namespace android { using Transaction = SurfaceComposerClient::Transaction; using gui::DisplayInfo; using gui::WindowInfo; +using WindowInfosPredicate = std::function<bool(const std::vector<WindowInfo>&)>; + class WindowInfosListenerTest : public ::testing::Test { protected: void SetUp() override { seteuid(AID_SYSTEM); mClient = sp<SurfaceComposerClient>::make(); - mWindowInfosListener = sp<SyncWindowInfosListener>::make(); - mClient->addWindowInfosListener(mWindowInfosListener); } - void TearDown() override { - mClient->removeWindowInfosListener(mWindowInfosListener); - seteuid(AID_ROOT); - } + void TearDown() override { seteuid(AID_ROOT); } - struct SyncWindowInfosListener : public gui::WindowInfosListener { + struct WindowInfosListener : public gui::WindowInfosListener { public: + WindowInfosListener(WindowInfosPredicate predicate, std::promise<void>& promise) + : mPredicate(std::move(predicate)), mPromise(promise) {} + void onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos, const std::vector<DisplayInfo>&) override { - windowInfosPromise.set_value(windowInfos); - } - - std::vector<WindowInfo> waitForWindowInfos() { - std::future<std::vector<WindowInfo>> windowInfosFuture = - windowInfosPromise.get_future(); - std::vector<WindowInfo> windowInfos = windowInfosFuture.get(); - windowInfosPromise = std::promise<std::vector<WindowInfo>>(); - return windowInfos; + if (mPredicate(windowInfos)) { + mPromise.set_value(); + } } private: - std::promise<std::vector<WindowInfo>> windowInfosPromise; + WindowInfosPredicate mPredicate; + std::promise<void>& mPromise; }; sp<SurfaceComposerClient> mClient; - sp<SyncWindowInfosListener> mWindowInfosListener; + + bool waitForWindowInfosPredicate(WindowInfosPredicate predicate) { + std::promise<void> promise; + auto listener = sp<WindowInfosListener>::make(std::move(predicate), promise); + mClient->addWindowInfosListener(listener); + auto future = promise.get_future(); + bool satisfied = future.wait_for(std::chrono::seconds{1}) == std::future_status::ready; + mClient->removeWindowInfosListener(listener); + return satisfied; + } }; std::optional<WindowInfo> findMatchingWindowInfo(WindowInfo targetWindowInfo, std::vector<WindowInfo> windowInfos) { - std::optional<WindowInfo> foundWindowInfo = std::nullopt; for (WindowInfo windowInfo : windowInfos) { if (windowInfo.token == targetWindowInfo.token) { - foundWindowInfo = std::make_optional<>(windowInfo); - break; + return windowInfo; } } - - return foundWindowInfo; + return std::nullopt; } TEST_F(WindowInfosListenerTest, WindowInfoAddedAndRemoved) { @@ -92,15 +92,17 @@ TEST_F(WindowInfosListenerTest, WindowInfoAddedAndRemoved) { .setInputWindowInfo(surfaceControl, windowInfo) .apply(); - std::vector<WindowInfo> windowInfos = mWindowInfosListener->waitForWindowInfos(); - std::optional<WindowInfo> foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos); - ASSERT_NE(std::nullopt, foundWindowInfo); + auto windowPresent = [&](const std::vector<WindowInfo>& windowInfos) { + return findMatchingWindowInfo(windowInfo, windowInfos).has_value(); + }; + ASSERT_TRUE(waitForWindowInfosPredicate(windowPresent)); Transaction().reparent(surfaceControl, nullptr).apply(); - windowInfos = mWindowInfosListener->waitForWindowInfos(); - foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos); - ASSERT_EQ(std::nullopt, foundWindowInfo); + auto windowNotPresent = [&](const std::vector<WindowInfo>& windowInfos) { + return !findMatchingWindowInfo(windowInfo, windowInfos).has_value(); + }; + ASSERT_TRUE(waitForWindowInfosPredicate(windowNotPresent)); } TEST_F(WindowInfosListenerTest, WindowInfoChanged) { @@ -121,19 +123,28 @@ TEST_F(WindowInfosListenerTest, WindowInfoChanged) { .setInputWindowInfo(surfaceControl, windowInfo) .apply(); - std::vector<WindowInfo> windowInfos = mWindowInfosListener->waitForWindowInfos(); - std::optional<WindowInfo> foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos); - ASSERT_NE(std::nullopt, foundWindowInfo); - ASSERT_TRUE(foundWindowInfo->touchableRegion.isEmpty()); + auto windowIsPresentAndTouchableRegionEmpty = [&](const std::vector<WindowInfo>& windowInfos) { + auto foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos); + if (!foundWindowInfo) { + return false; + } + return foundWindowInfo->touchableRegion.isEmpty(); + }; + ASSERT_TRUE(waitForWindowInfosPredicate(windowIsPresentAndTouchableRegionEmpty)); Rect touchableRegions(0, 0, 50, 50); windowInfo.addTouchableRegion(Rect(0, 0, 50, 50)); Transaction().setInputWindowInfo(surfaceControl, windowInfo).apply(); - windowInfos = mWindowInfosListener->waitForWindowInfos(); - foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos); - ASSERT_NE(std::nullopt, foundWindowInfo); - ASSERT_TRUE(foundWindowInfo->touchableRegion.hasSameRects(windowInfo.touchableRegion)); + auto windowIsPresentAndTouchableRegionMatches = + [&](const std::vector<WindowInfo>& windowInfos) { + auto foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos); + if (!foundWindowInfo) { + return false; + } + return foundWindowInfo->touchableRegion.hasSameRects(windowInfo.touchableRegion); + }; + ASSERT_TRUE(waitForWindowInfosPredicate(windowIsPresentAndTouchableRegionMatches)); } } // namespace android diff --git a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp index 0e214af706..5f9214c548 100644 --- a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp +++ b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp @@ -91,6 +91,8 @@ struct LayerInfo { uint64_t curr_frame; float x; float y; + uint32_t bufferWidth; + uint32_t bufferHeight; }; bool operator==(const LayerInfo& lh, const LayerInfo& rh) { @@ -105,7 +107,8 @@ bool compareById(const LayerInfo& a, const LayerInfo& b) { inline void PrintTo(const LayerInfo& info, ::std::ostream* os) { *os << "Layer [" << info.id << "] name=" << info.name << " parent=" << info.parent << " z=" << info.z << " curr_frame=" << info.curr_frame << " x=" << info.x - << " y=" << info.y; + << " y=" << info.y << " bufferWidth=" << info.bufferWidth + << " bufferHeight=" << info.bufferHeight; } struct find_id : std::unary_function<LayerInfo, bool> { @@ -114,6 +117,18 @@ struct find_id : std::unary_function<LayerInfo, bool> { bool operator()(LayerInfo const& m) const { return m.id == id; } }; +static LayerInfo getLayerInfoFromProto(::android::surfaceflinger::LayerProto& proto) { + return {proto.id(), + proto.name(), + proto.parent(), + proto.z(), + proto.curr_frame(), + proto.has_position() ? proto.position().x() : -1, + proto.has_position() ? proto.position().y() : -1, + proto.has_active_buffer() ? proto.active_buffer().width() : 0, + proto.has_active_buffer() ? proto.active_buffer().height() : 0}; +} + TEST_P(TransactionTraceTestSuite, validateEndState) { ASSERT_GT(mActualLayersTraceProto.entry_size(), 0); ASSERT_GT(mExpectedLayersTraceProto.entry_size(), 0); @@ -128,10 +143,7 @@ TEST_P(TransactionTraceTestSuite, validateEndState) { expectedLayers.reserve(static_cast<size_t>(expectedLastEntry.layers().layers_size())); for (int i = 0; i < expectedLastEntry.layers().layers_size(); i++) { auto layer = expectedLastEntry.layers().layers(i); - expectedLayers.push_back({layer.id(), layer.name(), layer.parent(), layer.z(), - layer.curr_frame(), - layer.has_position() ? layer.position().x() : -1, - layer.has_position() ? layer.position().y() : -1}); + expectedLayers.push_back(getLayerInfoFromProto(layer)); } std::sort(expectedLayers.begin(), expectedLayers.end(), compareById); @@ -139,10 +151,7 @@ TEST_P(TransactionTraceTestSuite, validateEndState) { actualLayers.reserve(static_cast<size_t>(actualLastEntry.layers().layers_size())); for (int i = 0; i < actualLastEntry.layers().layers_size(); i++) { auto layer = actualLastEntry.layers().layers(i); - actualLayers.push_back({layer.id(), layer.name(), layer.parent(), layer.z(), - layer.curr_frame(), - layer.has_position() ? layer.position().x() : -1, - layer.has_position() ? layer.position().y() : -1}); + actualLayers.push_back(getLayerInfoFromProto(layer)); } std::sort(actualLayers.begin(), actualLayers.end(), compareById); diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index 87c3c65a74..012a4adbfa 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -103,8 +103,11 @@ cc_test { "SurfaceFlinger_DestroyDisplayTest.cpp", "SurfaceFlinger_DisplayModeSwitching.cpp", "SurfaceFlinger_DisplayTransactionCommitTest.cpp", + "SurfaceFlinger_ExcludeDolbyVisionTest.cpp", "SurfaceFlinger_GetDisplayNativePrimariesTest.cpp", + "SurfaceFlinger_HdrOutputControlTest.cpp", "SurfaceFlinger_HotplugTest.cpp", + "SurfaceFlinger_MultiDisplayPacesetterTest.cpp", "SurfaceFlinger_NotifyPowerBoostTest.cpp", "SurfaceFlinger_OnInitializeDisplaysTest.cpp", "SurfaceFlinger_PowerHintTest.cpp", @@ -112,7 +115,6 @@ cc_test { "SurfaceFlinger_SetPowerModeInternalTest.cpp", "SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp", "SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp", - "SurfaceFlinger_ExcludeDolbyVisionTest.cpp", "SchedulerTest.cpp", "SetFrameRateTest.cpp", "RefreshRateSelectorTest.cpp", @@ -133,6 +135,7 @@ cc_test { "VSyncPredictorTest.cpp", "VSyncReactorTest.cpp", "VsyncConfigurationTest.cpp", + "VsyncScheduleTest.cpp", ], } diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 0416e93f1e..19a93e1820 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -99,7 +99,7 @@ public: ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - setupScheduler(); + mFlinger.setupMockScheduler({.displayId = DEFAULT_DISPLAY_ID}); EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_WIDTH, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_WIDTH), Return(0))); @@ -122,36 +122,6 @@ public: ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); } - void setupScheduler() { - auto eventThread = std::make_unique<mock::EventThread>(); - auto sfEventThread = std::make_unique<mock::EventThread>(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique<mock::VsyncController>(); - auto vsyncTracker = std::make_unique<mock::VSyncTracker>(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread), - TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp, - TestableSurfaceFlinger::kTwoDisplayModes); - } - void setupForceGeometryDirty() { // TODO: This requires the visible region and other related // state to be set, and is problematic for BufferLayers since they are @@ -176,7 +146,6 @@ public: bool mDisplayOff = false; TestableSurfaceFlinger mFlinger; sp<DisplayDevice> mDisplay; - sp<DisplayDevice> mExternalDisplay; sp<compositionengine::mock::DisplaySurface> mDisplaySurface = sp<compositionengine::mock::DisplaySurface>::make(); sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make(); @@ -315,13 +284,16 @@ struct BaseDisplayVariant { constexpr auto kDisplayConnectionType = ui::DisplayConnectionType::Internal; constexpr bool kIsPrimary = true; - test->mDisplay = FakeDisplayDeviceInjector(test->mFlinger, compositionDisplay, - kDisplayConnectionType, HWC_DISPLAY, kIsPrimary) - .setDisplaySurface(test->mDisplaySurface) - .setNativeWindow(test->mNativeWindow) - .setSecure(Derived::IS_SECURE) - .setPowerMode(Derived::INIT_POWER_MODE) - .inject(); + test->mDisplay = + FakeDisplayDeviceInjector(test->mFlinger, compositionDisplay, + kDisplayConnectionType, HWC_DISPLAY, kIsPrimary) + .setDisplaySurface(test->mDisplaySurface) + .setNativeWindow(test->mNativeWindow) + .setSecure(Derived::IS_SECURE) + .setPowerMode(Derived::INIT_POWER_MODE) + .setRefreshRateSelector(test->mFlinger.scheduler()->refreshRateSelector()) + .skipRegisterDisplay() + .inject(); Mock::VerifyAndClear(test->mNativeWindow.get()); constexpr bool kIsInternal = kDisplayConnectionType == ui::DisplayConnectionType::Internal; diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp index e0b508aa73..e32cf8863b 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp @@ -29,9 +29,7 @@ using testing::SetArgPointee; using android::hardware::graphics::composer::hal::HWDisplayId; -using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector; - -DisplayTransactionTest::DisplayTransactionTest() { +DisplayTransactionTest::DisplayTransactionTest(bool withMockScheduler) { const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); @@ -48,7 +46,10 @@ DisplayTransactionTest::DisplayTransactionTest() { return nullptr; }); - injectMockScheduler(); + if (withMockScheduler) { + injectMockScheduler(PhysicalDisplayId::fromPort(0)); + } + mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine)); injectMockComposer(0); @@ -61,7 +62,9 @@ DisplayTransactionTest::~DisplayTransactionTest() { mFlinger.resetScheduler(nullptr); } -void DisplayTransactionTest::injectMockScheduler() { +void DisplayTransactionTest::injectMockScheduler(PhysicalDisplayId displayId) { + LOG_ALWAYS_FATAL_IF(mFlinger.scheduler()); + EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_)); EXPECT_CALL(*mEventThread, createEventConnection(_, _)) .WillOnce(Return(sp<EventThreadConnection>::make(mEventThread, @@ -74,10 +77,11 @@ void DisplayTransactionTest::injectMockScheduler() { mock::EventThread::kCallingUid, ResyncCallback()))); - mFlinger.setupScheduler(std::unique_ptr<scheduler::VsyncController>(mVsyncController), - std::unique_ptr<scheduler::VSyncTracker>(mVSyncTracker), + mFlinger.setupScheduler(std::make_unique<mock::VsyncController>(), + std::make_shared<mock::VSyncTracker>(), std::unique_ptr<EventThread>(mEventThread), std::unique_ptr<EventThread>(mSFEventThread), + TestableSurfaceFlinger::DefaultDisplayMode{displayId}, TestableSurfaceFlinger::SchedulerCallbackImpl::kMock); } diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h index 223f4db889..e64cb38b16 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h @@ -85,7 +85,7 @@ public: // -------------------------------------------------------------------- // Mock/Fake injection - void injectMockScheduler(); + void injectMockScheduler(PhysicalDisplayId); void injectMockComposer(int virtualDisplayCount); void injectFakeBufferQueueFactory(); void injectFakeNativeWindowSurfaceFactory(); @@ -128,8 +128,6 @@ public: renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine(); Hwc2::mock::Composer* mComposer = nullptr; - mock::VsyncController* mVsyncController = new mock::VsyncController; - mock::VSyncTracker* mVSyncTracker = new mock::VSyncTracker; mock::EventThread* mEventThread = new mock::EventThread; mock::EventThread* mSFEventThread = new mock::EventThread; @@ -139,7 +137,7 @@ public: surfaceflinger::mock::NativeWindowSurface* mNativeWindowSurface = nullptr; protected: - DisplayTransactionTest(); + DisplayTransactionTest(bool withMockScheduler = true); }; constexpr int32_t DEFAULT_VSYNC_PERIOD = 16'666'667; @@ -158,7 +156,6 @@ constexpr int POWER_MODE_LEET = 1337; // An out of range power mode value #define BOOL_SUBSTITUTE(TYPENAME) enum class TYPENAME : bool { FALSE = false, TRUE = true }; BOOL_SUBSTITUTE(Async); -BOOL_SUBSTITUTE(Critical); BOOL_SUBSTITUTE(Primary); BOOL_SUBSTITUTE(Secure); BOOL_SUBSTITUTE(Virtual); @@ -238,8 +235,8 @@ struct HwcDisplayIdGetter<PhysicalDisplayIdType<PhysicalDisplay>> { // 1) PhysicalDisplayIdType<...> for generated ID of physical display backed by HWC. // 2) HalVirtualDisplayIdType<...> for hard-coded ID of virtual display backed by HWC. // 3) GpuVirtualDisplayIdType for virtual display without HWC backing. -template <typename DisplayIdType, int width, int height, Critical critical, Async async, - Secure secure, Primary primary, int grallocUsage, int displayFlags> +template <typename DisplayIdType, int width, int height, Async async, Secure secure, + Primary primary, int grallocUsage, int displayFlags> struct DisplayVariant { using DISPLAY_ID = DisplayIdGetter<DisplayIdType>; using CONNECTION_TYPE = DisplayConnectionTypeGetter<DisplayIdType>; @@ -255,9 +252,6 @@ struct DisplayVariant { static constexpr Virtual VIRTUAL = IsPhysicalDisplayId<DisplayIdType>{} ? Virtual::FALSE : Virtual::TRUE; - // When creating native window surfaces for the framebuffer, whether those should be critical - static constexpr Critical CRITICAL = critical; - // When creating native window surfaces for the framebuffer, whether those should be async static constexpr Async ASYNC = async; @@ -486,17 +480,16 @@ constexpr uint32_t GRALLOC_USAGE_PHYSICAL_DISPLAY = constexpr int PHYSICAL_DISPLAY_FLAGS = 0x1; -template <typename PhysicalDisplay, int width, int height, Critical critical> +template <typename PhysicalDisplay, int width, int height> struct PhysicalDisplayVariant - : DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height, critical, - Async::FALSE, Secure::TRUE, PhysicalDisplay::PRIMARY, - GRALLOC_USAGE_PHYSICAL_DISPLAY, PHYSICAL_DISPLAY_FLAGS>, - HwcDisplayVariant< - PhysicalDisplay::HWC_DISPLAY_ID, DisplayType::PHYSICAL, - DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height, critical, - Async::FALSE, Secure::TRUE, PhysicalDisplay::PRIMARY, - GRALLOC_USAGE_PHYSICAL_DISPLAY, PHYSICAL_DISPLAY_FLAGS>, - PhysicalDisplay> {}; + : DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height, Async::FALSE, + Secure::TRUE, PhysicalDisplay::PRIMARY, GRALLOC_USAGE_PHYSICAL_DISPLAY, + PHYSICAL_DISPLAY_FLAGS>, + HwcDisplayVariant<PhysicalDisplay::HWC_DISPLAY_ID, DisplayType::PHYSICAL, + DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height, + Async::FALSE, Secure::TRUE, PhysicalDisplay::PRIMARY, + GRALLOC_USAGE_PHYSICAL_DISPLAY, PHYSICAL_DISPLAY_FLAGS>, + PhysicalDisplay> {}; template <bool hasIdentificationData> struct PrimaryDisplay { @@ -508,14 +501,16 @@ struct PrimaryDisplay { static constexpr auto GET_IDENTIFICATION_DATA = getInternalEdid; }; -template <bool hasIdentificationData> -struct ExternalDisplay { - static constexpr auto CONNECTION_TYPE = ui::DisplayConnectionType::External; +template <ui::DisplayConnectionType connectionType, bool hasIdentificationData> +struct SecondaryDisplay { + static constexpr auto CONNECTION_TYPE = connectionType; static constexpr Primary PRIMARY = Primary::FALSE; static constexpr uint8_t PORT = 254; static constexpr HWDisplayId HWC_DISPLAY_ID = 1002; static constexpr bool HAS_IDENTIFICATION_DATA = hasIdentificationData; - static constexpr auto GET_IDENTIFICATION_DATA = getExternalEdid; + static constexpr auto GET_IDENTIFICATION_DATA = + connectionType == ui::DisplayConnectionType::Internal ? getInternalEdid + : getExternalEdid; }; struct TertiaryDisplay { @@ -525,15 +520,18 @@ struct TertiaryDisplay { static constexpr auto GET_IDENTIFICATION_DATA = getExternalEdid; }; -// A primary display is a physical display that is critical -using PrimaryDisplayVariant = - PhysicalDisplayVariant<PrimaryDisplay<false>, 3840, 2160, Critical::TRUE>; +using PrimaryDisplayVariant = PhysicalDisplayVariant<PrimaryDisplay<false>, 3840, 2160>; + +using InnerDisplayVariant = PhysicalDisplayVariant<PrimaryDisplay<true>, 1840, 2208>; +using OuterDisplayVariant = + PhysicalDisplayVariant<SecondaryDisplay<ui::DisplayConnectionType::Internal, true>, 1080, + 2092>; -// An external display is physical display that is not critical. using ExternalDisplayVariant = - PhysicalDisplayVariant<ExternalDisplay<false>, 1920, 1280, Critical::FALSE>; + PhysicalDisplayVariant<SecondaryDisplay<ui::DisplayConnectionType::External, false>, 1920, + 1280>; -using TertiaryDisplayVariant = PhysicalDisplayVariant<TertiaryDisplay, 1600, 1200, Critical::FALSE>; +using TertiaryDisplayVariant = PhysicalDisplayVariant<TertiaryDisplay, 1600, 1200>; // A virtual display not supported by the HWC. constexpr uint32_t GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY = 0; @@ -542,12 +540,11 @@ constexpr int VIRTUAL_DISPLAY_FLAGS = 0x0; template <int width, int height, Secure secure> struct NonHwcVirtualDisplayVariant - : DisplayVariant<GpuVirtualDisplayIdType, width, height, Critical::FALSE, Async::TRUE, secure, - Primary::FALSE, GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY, - VIRTUAL_DISPLAY_FLAGS> { - using Base = DisplayVariant<GpuVirtualDisplayIdType, width, height, Critical::FALSE, - Async::TRUE, secure, Primary::FALSE, - GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY, VIRTUAL_DISPLAY_FLAGS>; + : DisplayVariant<GpuVirtualDisplayIdType, width, height, Async::TRUE, secure, Primary::FALSE, + GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY, VIRTUAL_DISPLAY_FLAGS> { + using Base = DisplayVariant<GpuVirtualDisplayIdType, width, height, Async::TRUE, secure, + Primary::FALSE, GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY, + VIRTUAL_DISPLAY_FLAGS>; static void injectHwcDisplay(DisplayTransactionTest*) {} @@ -589,17 +586,14 @@ constexpr uint32_t GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY = GRALLOC_USAGE_HW_COMPOSER template <int width, int height, Secure secure> struct HwcVirtualDisplayVariant - : DisplayVariant<HalVirtualDisplayIdType<42>, width, height, Critical::FALSE, Async::TRUE, - secure, Primary::FALSE, GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY, - VIRTUAL_DISPLAY_FLAGS>, - HwcDisplayVariant< - HWC_VIRTUAL_DISPLAY_HWC_DISPLAY_ID, DisplayType::VIRTUAL, - DisplayVariant<HalVirtualDisplayIdType<42>, width, height, Critical::FALSE, - Async::TRUE, secure, Primary::FALSE, - GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY, VIRTUAL_DISPLAY_FLAGS>> { - using Base = DisplayVariant<HalVirtualDisplayIdType<42>, width, height, Critical::FALSE, - Async::TRUE, secure, Primary::FALSE, GRALLOC_USAGE_HW_COMPOSER, - VIRTUAL_DISPLAY_FLAGS>; + : DisplayVariant<HalVirtualDisplayIdType<42>, width, height, Async::TRUE, secure, + Primary::FALSE, GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY, VIRTUAL_DISPLAY_FLAGS>, + HwcDisplayVariant<HWC_VIRTUAL_DISPLAY_HWC_DISPLAY_ID, DisplayType::VIRTUAL, + DisplayVariant<HalVirtualDisplayIdType<42>, width, height, Async::TRUE, + secure, Primary::FALSE, GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY, + VIRTUAL_DISPLAY_FLAGS>> { + using Base = DisplayVariant<HalVirtualDisplayIdType<42>, width, height, Async::TRUE, secure, + Primary::FALSE, GRALLOC_USAGE_HW_COMPOSER, VIRTUAL_DISPLAY_FLAGS>; using Self = HwcVirtualDisplayVariant<width, height, secure>; static std::shared_ptr<compositionengine::Display> injectCompositionDisplay( diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp index b3aba377ee..f1cdca3ee1 100644 --- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp +++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp @@ -125,7 +125,7 @@ protected: ConnectionEventRecorder mConnectionEventCallRecorder{0}; ConnectionEventRecorder mThrottledConnectionEventCallRecorder{0}; - std::optional<scheduler::VsyncSchedule> mVsyncSchedule; + std::shared_ptr<scheduler::VsyncSchedule> mVsyncSchedule; std::unique_ptr<impl::EventThread> mThread; sp<MockEventThreadConnection> mConnection; sp<MockEventThreadConnection> mThrottledConnection; @@ -140,12 +140,12 @@ EventThreadTest::EventThreadTest() { ::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)); - - mock::VSyncDispatch& mockDispatch = - *static_cast<mock::VSyncDispatch*>(&mVsyncSchedule->getDispatch()); + auto mockDispatchPtr = std::make_shared<mock::VSyncDispatch>(); + mVsyncSchedule = std::shared_ptr<scheduler::VsyncSchedule>( + new scheduler::VsyncSchedule(INTERNAL_DISPLAY_ID, + std::make_shared<mock::VSyncTracker>(), mockDispatchPtr, + nullptr)); + mock::VSyncDispatch& mockDispatch = *mockDispatchPtr; EXPECT_CALL(mockDispatch, registerCallback(_, _)) .WillRepeatedly(Invoke(mVSyncCallbackRegisterRecorder.getInvocable())); EXPECT_CALL(mockDispatch, schedule(_, _)) @@ -189,10 +189,9 @@ void EventThreadTest::createThread() { }; mTokenManager = std::make_unique<frametimeline::impl::TokenManager>(); - mThread = - std::make_unique<impl::EventThread>(/*std::move(source), */ "EventThreadTest", - *mVsyncSchedule, mTokenManager.get(), throttleVsync, - getVsyncPeriod, kWorkDuration, kReadyDuration); + mThread = std::make_unique<impl::EventThread>("EventThreadTest", mVsyncSchedule, + mTokenManager.get(), throttleVsync, + getVsyncPeriod, kWorkDuration, kReadyDuration); // EventThread should register itself as VSyncSource callback. EXPECT_TRUE(mVSyncCallbackRegisterRecorder.waitForCall().has_value()); diff --git a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp index 1cd9e49051..f695b096a7 100644 --- a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp +++ b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp @@ -29,9 +29,7 @@ #include "TestableSurfaceFlinger.h" #include "fake/FakeClock.h" #include "mock/DisplayHardware/MockComposer.h" -#include "mock/MockEventThread.h" #include "mock/MockFrameTimeline.h" -#include "mock/MockVsyncController.h" namespace android { @@ -47,7 +45,6 @@ using testing::UnorderedElementsAre; using android::Hwc2::IComposer; using android::Hwc2::IComposerClient; -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; using gui::LayerMetadata; struct TestableFpsListener : public gui::BnFpsListener { @@ -77,7 +74,6 @@ protected: static constexpr uint32_t LAYER_FLAGS = 0; static constexpr int32_t PRIORITY_UNSET = -1; - void setupScheduler(); sp<Layer> createBufferStateLayer(LayerMetadata metadata); TestableSurfaceFlinger mFlinger; @@ -102,7 +98,7 @@ FpsReporterTest::FpsReporterTest() { ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - setupScheduler(); + mFlinger.setupMockScheduler(); mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>()); mFpsListener = sp<TestableFpsListener>::make(); @@ -120,33 +116,6 @@ sp<Layer> FpsReporterTest::createBufferStateLayer(LayerMetadata metadata = {}) { return sp<Layer>::make(args); } -void FpsReporterTest::setupScheduler() { - auto eventThread = std::make_unique<mock::EventThread>(); - auto sfEventThread = std::make_unique<mock::EventThread>(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique<mock::VsyncController>(); - auto vsyncTracker = std::make_unique<mock::VSyncTracker>(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread)); -} - namespace { TEST_F(FpsReporterTest, callsListeners) { diff --git a/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp index ac63a0edbd..1c9aee7443 100644 --- a/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp +++ b/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp @@ -24,8 +24,6 @@ #include "Layer.h" #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockComposer.h" -#include "mock/MockEventThread.h" -#include "mock/MockVsyncController.h" namespace android { @@ -38,8 +36,6 @@ using testing::SetArgPointee; using android::Hwc2::IComposer; using android::Hwc2::IComposerClient; -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; - /** * This class covers all the test that are related to refresh rate selection. */ @@ -56,7 +52,6 @@ protected: static constexpr uint32_t LAYER_FLAGS = 0; static constexpr int32_t PRIORITY_UNSET = -1; - void setupScheduler(); sp<Layer> createBufferStateLayer(); sp<Layer> createEffectLayer(); @@ -76,7 +71,7 @@ RefreshRateSelectionTest::RefreshRateSelectionTest() { ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - setupScheduler(); + mFlinger.setupMockScheduler(); mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>()); } @@ -108,37 +103,8 @@ void RefreshRateSelectionTest::commitTransaction(Layer* layer) { layer->commitTransaction(c); } -void RefreshRateSelectionTest::setupScheduler() { - auto eventThread = std::make_unique<mock::EventThread>(); - auto sfEventThread = std::make_unique<mock::EventThread>(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique<mock::VsyncController>(); - auto vsyncTracker = std::make_unique<mock::VSyncTracker>(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread)); -} - namespace { -/* ------------------------------------------------------------------------ - * Test cases - */ + TEST_F(RefreshRateSelectionTest, testPriorityOnBufferStateLayers) { mParent = createBufferStateLayer(); mChild = createBufferStateLayer(); diff --git a/services/surfaceflinger/tests/unittests/GameModeTest.cpp b/services/surfaceflinger/tests/unittests/GameModeTest.cpp index 29aa7171ba..1b5c6e70f8 100644 --- a/services/surfaceflinger/tests/unittests/GameModeTest.cpp +++ b/services/surfaceflinger/tests/unittests/GameModeTest.cpp @@ -25,15 +25,13 @@ #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockComposer.h" -#include "mock/MockEventThread.h" -#include "mock/MockVsyncController.h" namespace android { using testing::_; using testing::Mock; using testing::Return; -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; + using gui::GameMode; using gui::LayerMetadata; @@ -43,7 +41,7 @@ public: const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - setupScheduler(); + mFlinger.setupMockScheduler(); setupComposer(); } @@ -59,33 +57,6 @@ public: return sp<Layer>::make(args); } - void setupScheduler() { - auto eventThread = std::make_unique<mock::EventThread>(); - auto sfEventThread = std::make_unique<mock::EventThread>(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique<mock::VsyncController>(); - auto vsyncTracker = std::make_unique<mock::VSyncTracker>(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread)); - } - void setupComposer() { mComposer = new Hwc2::mock::Composer(); mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer)); diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp index afbc57add8..da00377cae 100644 --- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp +++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp @@ -52,6 +52,7 @@ namespace aidl = aidl::android::hardware::graphics::composer3; 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, getActiveMode) { } } +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 @@ struct MockHWC2ComposerCallback final : StrictMock<HWC2::ComposerCallback> { 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 763426a9f8..77dc868aaf 100644 --- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp @@ -263,6 +263,37 @@ TEST_F(LayerHierarchyTest, relativeChildMovesOffscreenIsNotTraversable) { EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); } +TEST_F(LayerHierarchyTest, reparentRelativeLayer) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + reparentRelativeLayer(11, 2); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 11, 111}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2, 11, 111}; + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); + + reparentLayer(11, 1); + UPDATE_AND_VERIFY(hierarchyBuilder); + expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 11, 111}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2, 11, 111}; + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); + + setZ(11, 0); + UPDATE_AND_VERIFY(hierarchyBuilder); + expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2}; + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + // mirror tests TEST_F(LayerHierarchyTest, canTraverseMirrorLayer) { LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); @@ -437,10 +468,11 @@ TEST_F(LayerHierarchyTest, backgroundLayersAreBehindParentLayer) { updateBackgroundColor(1, 0.5); UPDATE_AND_VERIFY(hierarchyBuilder); - - std::vector<uint32_t> expectedTraversalPath = {1, 1222, 11, 111, 12, 121, 122, 1221, 13, 2}; + auto bgLayerId = LayerCreationArgs::getInternalLayerId(1); + std::vector<uint32_t> expectedTraversalPath = {1, bgLayerId, 11, 111, 12, + 121, 122, 1221, 13, 2}; EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); - expectedTraversalPath = {1222, 1, 11, 111, 12, 121, 122, 1221, 13, 2}; + expectedTraversalPath = {bgLayerId, 1, 11, 111, 12, 121, 122, 1221, 13, 2}; EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); expectedTraversalPath = {}; EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h index 852cb91b2c..b9a6159c78 100644 --- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h +++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h @@ -172,7 +172,7 @@ protected: transactions.emplace_back(); transactions.back().states.push_back({}); transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged; - transactions.back().states.front().state.bgColorAlpha = alpha; + transactions.back().states.front().state.bgColor.a = alpha; transactions.back().states.front().state.surface = mHandles[id]; mLifecycleManager.applyTransactions(transactions); } @@ -274,6 +274,22 @@ protected: mLifecycleManager.applyTransactions(transactions); } + void setTouchableRegion(uint32_t id, Region region) { + std::vector<TransactionState> transactions; + transactions.emplace_back(); + transactions.back().states.push_back({}); + + transactions.back().states.front().state.what = layer_state_t::eInputInfoChanged; + transactions.back().states.front().state.surface = mHandles[id]; + transactions.back().states.front().state.layerId = static_cast<int32_t>(id); + transactions.back().states.front().state.windowInfoHandle = + sp<gui::WindowInfoHandle>::make(); + auto inputInfo = transactions.back().states.front().state.windowInfoHandle->editInfo(); + inputInfo->touchableRegion = region; + inputInfo->token = sp<BBinder>::make(); + mLifecycleManager.applyTransactions(transactions); + } + LayerLifecycleManager mLifecycleManager; std::unordered_map<uint32_t, sp<LayerHandle>> mHandles; }; diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp index 8397f8d0ce..b7672768b4 100644 --- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp @@ -112,7 +112,8 @@ protected: Fps desiredRefreshRate, int numFrames) { LayerHistory::Summary summary; for (int i = 0; i < numFrames; i++) { - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += frameRate.getPeriodNsecs(); summary = summarizeLayerHistory(time); @@ -155,7 +156,8 @@ TEST_F(LayerHistoryTest, singleLayerNoVoteDefaultCompatibility) { EXPECT_TRUE(summarizeLayerHistory(time).empty()); EXPECT_EQ(0, activeLayerCount()); - history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), 0, time, + LayerHistory::LayerUpdateType::Buffer); history().setDefaultFrameRateCompatibility(layer.get(), true /* contentDetectionEnabled */); EXPECT_TRUE(summarizeLayerHistory(time).empty()); @@ -177,7 +179,8 @@ TEST_F(LayerHistoryTest, singleLayerMinVoteDefaultCompatibility) { EXPECT_TRUE(summarizeLayerHistory(time).empty()); EXPECT_EQ(0, activeLayerCount()); - history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), 0, time, + LayerHistory::LayerUpdateType::Buffer); history().setDefaultFrameRateCompatibility(layer.get(), true /* contentDetectionEnabled */); auto summary = summarizeLayerHistory(time); @@ -205,7 +208,8 @@ TEST_F(LayerHistoryTest, oneLayer) { // Max returned if active layers have insufficient history. for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) { - history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), 0, time, + LayerHistory::LayerUpdateType::Buffer); ASSERT_EQ(1, summarizeLayerHistory(time).size()); EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote); EXPECT_EQ(1, activeLayerCount()); @@ -214,7 +218,8 @@ TEST_F(LayerHistoryTest, oneLayer) { // Max is returned since we have enough history but there is no timestamp votes. for (int i = 0; i < 10; i++) { - history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), 0, time, + LayerHistory::LayerUpdateType::Buffer); ASSERT_EQ(1, summarizeLayerHistory(time).size()); EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote); EXPECT_EQ(1, activeLayerCount()); @@ -232,7 +237,8 @@ TEST_F(LayerHistoryTest, oneInvisibleLayer) { nsecs_t time = systemTime(); - history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), 0, time, + LayerHistory::LayerUpdateType::Buffer); auto summary = summarizeLayerHistory(time); ASSERT_EQ(1, summarizeLayerHistory(time).size()); // Layer is still considered inactive so we expect to get Min @@ -240,7 +246,8 @@ TEST_F(LayerHistoryTest, oneInvisibleLayer) { EXPECT_EQ(1, activeLayerCount()); EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false)); - history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), 0, time, + LayerHistory::LayerUpdateType::Buffer); summary = summarizeLayerHistory(time); EXPECT_TRUE(summarizeLayerHistory(time).empty()); @@ -257,7 +264,8 @@ TEST_F(LayerHistoryTest, explicitTimestamp) { nsecs_t time = systemTime(); for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) { - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += LO_FPS_PERIOD; } @@ -280,7 +288,8 @@ TEST_F(LayerHistoryTest, oneLayerNoVote) { nsecs_t time = systemTime(); for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) { - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += HI_FPS_PERIOD; } @@ -307,7 +316,8 @@ TEST_F(LayerHistoryTest, oneLayerMinVote) { nsecs_t time = systemTime(); for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) { - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += HI_FPS_PERIOD; } @@ -335,7 +345,8 @@ TEST_F(LayerHistoryTest, oneLayerMaxVote) { nsecs_t time = systemTime(); for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) { - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += LO_FPS_PERIOD; } @@ -363,7 +374,8 @@ TEST_F(LayerHistoryTest, oneLayerExplicitVote) { nsecs_t time = systemTime(); for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) { - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += HI_FPS_PERIOD; } @@ -395,7 +407,8 @@ TEST_F(LayerHistoryTest, oneLayerExplicitExactVote) { nsecs_t time = systemTime(); for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) { - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += HI_FPS_PERIOD; } @@ -441,7 +454,8 @@ TEST_F(LayerHistoryTest, multipleLayers) { // layer1 is active but infrequent. for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) { - history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer1->getSequence(), layer1->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += MAX_FREQUENT_LAYER_PERIOD_NS.count(); summary = summarizeLayerHistory(time); } @@ -453,13 +467,15 @@ TEST_F(LayerHistoryTest, multipleLayers) { // layer2 is frequent and has high refresh rate. for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) { - history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer2->getSequence(), layer2->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += HI_FPS_PERIOD; summary = summarizeLayerHistory(time); } // layer1 is still active but infrequent. - history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer1->getSequence(), layer1->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); ASSERT_EQ(2, summary.size()); EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote); @@ -472,7 +488,8 @@ TEST_F(LayerHistoryTest, multipleLayers) { // layer1 is no longer active. // layer2 is frequent and has low refresh rate. for (int i = 0; i < 2 * PRESENT_TIME_HISTORY_SIZE; i++) { - history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer2->getSequence(), layer2->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += LO_FPS_PERIOD; summary = summarizeLayerHistory(time); } @@ -488,10 +505,12 @@ TEST_F(LayerHistoryTest, multipleLayers) { constexpr int RATIO = LO_FPS_PERIOD / HI_FPS_PERIOD; for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) { if (i % RATIO == 0) { - history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer2->getSequence(), layer2->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); } - history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer3->getSequence(), layer3->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += HI_FPS_PERIOD; summary = summarizeLayerHistory(time); } @@ -504,7 +523,8 @@ TEST_F(LayerHistoryTest, multipleLayers) { EXPECT_EQ(2, frequentLayerCount(time)); // layer3 becomes recently active. - history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer3->getSequence(), layer3->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); summary = summarizeLayerHistory(time); ASSERT_EQ(2, summary.size()); EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote); @@ -530,7 +550,8 @@ TEST_F(LayerHistoryTest, multipleLayers) { // layer2 still has low refresh rate. // layer3 becomes inactive. for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) { - history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer2->getSequence(), layer2->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += LO_FPS_PERIOD; summary = summarizeLayerHistory(time); } @@ -551,7 +572,8 @@ TEST_F(LayerHistoryTest, multipleLayers) { // layer3 becomes active and has high refresh rate. for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) { - history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer3->getSequence(), layer3->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += HI_FPS_PERIOD; summary = summarizeLayerHistory(time); } @@ -582,7 +604,8 @@ TEST_F(LayerHistoryTest, inactiveLayers) { // the very first updates makes the layer frequent for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) { - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += MAX_FREQUENT_LAYER_PERIOD_NS.count(); EXPECT_EQ(1, layerCount()); @@ -593,7 +616,8 @@ TEST_F(LayerHistoryTest, inactiveLayers) { } // the next update with the MAX_FREQUENT_LAYER_PERIOD_NS will get us to infrequent - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += MAX_FREQUENT_LAYER_PERIOD_NS.count(); EXPECT_EQ(1, layerCount()); @@ -607,7 +631,8 @@ TEST_F(LayerHistoryTest, inactiveLayers) { // Now even if we post a quick few frame we should stay infrequent for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) { - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += HI_FPS_PERIOD; EXPECT_EQ(1, layerCount()); @@ -618,7 +643,8 @@ TEST_F(LayerHistoryTest, inactiveLayers) { } // More quick frames will get us to frequent again - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += HI_FPS_PERIOD; EXPECT_EQ(1, layerCount()); @@ -645,9 +671,10 @@ TEST_F(LayerHistoryTest, invisibleExplicitLayer) { nsecs_t time = systemTime(); // Post a buffer to the layers to make them active - history().record(explicitVisiblelayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); - history().record(explicitInvisiblelayer.get(), time, time, - LayerHistory::LayerUpdateType::Buffer); + history().record(explicitVisiblelayer->getSequence(), explicitVisiblelayer->getLayerProps(), + time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(explicitInvisiblelayer->getSequence(), explicitInvisiblelayer->getLayerProps(), + time, time, LayerHistory::LayerUpdateType::Buffer); EXPECT_EQ(2, layerCount()); ASSERT_EQ(1, summarizeLayerHistory(time).size()); @@ -673,7 +700,8 @@ TEST_F(LayerHistoryTest, infrequentAnimatingLayer) { // layer is active but infrequent. for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) { - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += MAX_FREQUENT_LAYER_PERIOD_NS.count(); } @@ -684,7 +712,8 @@ TEST_F(LayerHistoryTest, infrequentAnimatingLayer) { EXPECT_EQ(0, animatingLayerCount(time)); // another update with the same cadence keep in infrequent - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += MAX_FREQUENT_LAYER_PERIOD_NS.count(); ASSERT_EQ(1, summarizeLayerHistory(time).size()); @@ -694,7 +723,8 @@ TEST_F(LayerHistoryTest, infrequentAnimatingLayer) { EXPECT_EQ(0, animatingLayerCount(time)); // an update as animation will immediately vote for Max - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::AnimationTX); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::AnimationTX); time += MAX_FREQUENT_LAYER_PERIOD_NS.count(); ASSERT_EQ(1, summarizeLayerHistory(time).size()); @@ -719,7 +749,8 @@ TEST_F(LayerHistoryTest, frequentLayerBecomingInfrequentAndBack) { // Fill up the window with frequent updates for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE; i++) { - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += (60_Hz).getPeriodNsecs(); EXPECT_EQ(1, layerCount()); @@ -731,7 +762,8 @@ TEST_F(LayerHistoryTest, frequentLayerBecomingInfrequentAndBack) { // posting a buffer after long inactivity should retain the layer as active time += std::chrono::nanoseconds(3s).count(); - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); ASSERT_EQ(1, summarizeLayerHistory(time).size()); EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summarizeLayerHistory(time)[0].vote); EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate); @@ -741,9 +773,11 @@ TEST_F(LayerHistoryTest, frequentLayerBecomingInfrequentAndBack) { // posting more infrequent buffer should make the layer infrequent time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count(); - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count(); - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); ASSERT_EQ(1, summarizeLayerHistory(time).size()); EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote); EXPECT_EQ(1, activeLayerCount()); @@ -751,7 +785,8 @@ TEST_F(LayerHistoryTest, frequentLayerBecomingInfrequentAndBack) { EXPECT_EQ(0, animatingLayerCount(time)); // posting another buffer should keep the layer infrequent - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); ASSERT_EQ(1, summarizeLayerHistory(time).size()); EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote); EXPECT_EQ(1, activeLayerCount()); @@ -759,8 +794,10 @@ TEST_F(LayerHistoryTest, frequentLayerBecomingInfrequentAndBack) { EXPECT_EQ(0, animatingLayerCount(time)); // posting more buffers would mean starting of an animation, so making the layer frequent - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); ASSERT_EQ(1, summarizeLayerHistory(time).size()); EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote); EXPECT_EQ(1, activeLayerCount()); @@ -769,7 +806,8 @@ TEST_F(LayerHistoryTest, frequentLayerBecomingInfrequentAndBack) { // posting a buffer after long inactivity should retain the layer as active time += std::chrono::nanoseconds(3s).count(); - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); ASSERT_EQ(1, summarizeLayerHistory(time).size()); EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote); EXPECT_EQ(1, activeLayerCount()); @@ -778,7 +816,8 @@ TEST_F(LayerHistoryTest, frequentLayerBecomingInfrequentAndBack) { // posting another buffer should keep the layer frequent time += (60_Hz).getPeriodNsecs(); - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); ASSERT_EQ(1, summarizeLayerHistory(time).size()); EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote); EXPECT_EQ(1, activeLayerCount()); @@ -801,7 +840,8 @@ TEST_F(LayerHistoryTest, getFramerate) { // layer is active but infrequent. for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) { - history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); time += MAX_FREQUENT_LAYER_PERIOD_NS.count(); } @@ -869,10 +909,10 @@ TEST_P(LayerHistoryTestParameterized, HeuristicLayerWithInfrequentLayer) { const nsecs_t startTime = systemTime(); const std::chrono::nanoseconds heuristicUpdateDelta = 41'666'667ns; - history().record(heuristicLayer.get(), startTime, startTime, - LayerHistory::LayerUpdateType::Buffer); - history().record(infrequentLayer.get(), startTime, startTime, - LayerHistory::LayerUpdateType::Buffer); + history().record(heuristicLayer->getSequence(), heuristicLayer->getLayerProps(), startTime, + startTime, LayerHistory::LayerUpdateType::Buffer); + history().record(infrequentLayer->getSequence(), heuristicLayer->getLayerProps(), startTime, + startTime, LayerHistory::LayerUpdateType::Buffer); nsecs_t time = startTime; nsecs_t lastInfrequentUpdate = startTime; @@ -880,14 +920,15 @@ TEST_P(LayerHistoryTestParameterized, HeuristicLayerWithInfrequentLayer) { int infrequentLayerUpdates = 0; while (infrequentLayerUpdates <= totalInfrequentLayerUpdates) { time += heuristicUpdateDelta.count(); - history().record(heuristicLayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer); + history().record(heuristicLayer->getSequence(), heuristicLayer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); if (time - lastInfrequentUpdate >= infrequentUpdateDelta.count()) { ALOGI("submitting infrequent frame [%d/%d]", infrequentLayerUpdates, totalInfrequentLayerUpdates); lastInfrequentUpdate = time; - history().record(infrequentLayer.get(), time, time, - LayerHistory::LayerUpdateType::Buffer); + history().record(infrequentLayer->getSequence(), infrequentLayer->getLayerProps(), time, + time, LayerHistory::LayerUpdateType::Buffer); infrequentLayerUpdates++; } diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp index 89440a689c..99c1d23c2b 100644 --- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp @@ -372,7 +372,7 @@ TEST_F(LayerLifecycleManagerTest, canAddBackgroundLayer) { std::vector<TransactionState> transactions; transactions.emplace_back(); transactions.back().states.push_back({}); - transactions.back().states.front().state.bgColorAlpha = 0.5; + transactions.back().states.front().state.bgColor.a = 0.5; transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged; sp<LayerHandle> handle = sp<LayerHandle>::make(1u); transactions.back().states.front().state.surface = handle; @@ -383,9 +383,10 @@ TEST_F(LayerLifecycleManagerTest, canAddBackgroundLayer) { EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)); lifecycleManager.commitChanges(); - listener->expectLayersAdded({1, 2}); + auto bgLayerId = LayerCreationArgs::getInternalLayerId(1); + listener->expectLayersAdded({1, bgLayerId}); listener->expectLayersDestroyed({}); - EXPECT_EQ(getRequestedLayerState(lifecycleManager, 2)->color.a, 0.5_hf); + EXPECT_EQ(getRequestedLayerState(lifecycleManager, bgLayerId)->color.a, 0.5_hf); } TEST_F(LayerLifecycleManagerTest, canDestroyBackgroundLayer) { @@ -400,13 +401,13 @@ TEST_F(LayerLifecycleManagerTest, canDestroyBackgroundLayer) { std::vector<TransactionState> transactions; transactions.emplace_back(); transactions.back().states.push_back({}); - transactions.back().states.front().state.bgColorAlpha = 0.5; + transactions.back().states.front().state.bgColor.a = 0.5; transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged; sp<LayerHandle> handle = sp<LayerHandle>::make(1u); transactions.back().states.front().state.surface = handle; transactions.emplace_back(); transactions.back().states.push_back({}); - transactions.back().states.front().state.bgColorAlpha = 0; + transactions.back().states.front().state.bgColor.a = 0; transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged; transactions.back().states.front().state.surface = handle; @@ -417,8 +418,9 @@ TEST_F(LayerLifecycleManagerTest, canDestroyBackgroundLayer) { EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)); lifecycleManager.commitChanges(); - listener->expectLayersAdded({1, 2}); - listener->expectLayersDestroyed({2}); + auto bgLayerId = LayerCreationArgs::getInternalLayerId(1); + listener->expectLayersAdded({1, bgLayerId}); + listener->expectLayersDestroyed({bgLayerId}); } TEST_F(LayerLifecycleManagerTest, onParentDestroyDestroysBackgroundLayer) { @@ -433,7 +435,7 @@ TEST_F(LayerLifecycleManagerTest, onParentDestroyDestroysBackgroundLayer) { std::vector<TransactionState> transactions; transactions.emplace_back(); transactions.back().states.push_back({}); - transactions.back().states.front().state.bgColorAlpha = 0.5; + transactions.back().states.front().state.bgColor.a = 0.5; transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged; sp<LayerHandle> handle = sp<LayerHandle>::make(1u); transactions.back().states.front().state.surface = handle; @@ -446,8 +448,9 @@ TEST_F(LayerLifecycleManagerTest, onParentDestroyDestroysBackgroundLayer) { EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)); lifecycleManager.commitChanges(); - listener->expectLayersAdded({1, 2}); - listener->expectLayersDestroyed({1, 2}); + auto bgLayerId = LayerCreationArgs::getInternalLayerId(1); + listener->expectLayersAdded({1, bgLayerId}); + listener->expectLayersDestroyed({1, bgLayerId}); } } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index aa6a14ed17..db0b907256 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -75,14 +75,14 @@ protected: mHierarchyBuilder.update(mLifecycleManager.getLayers(), mLifecycleManager.getDestroyedLayers()); } - LayerSnapshotBuilder::Args args{ - .root = mHierarchyBuilder.getHierarchy(), - .layerLifecycleManager = mLifecycleManager, - .includeMetadata = false, - .displays = mFrontEndDisplayInfos, - .displayChanges = hasDisplayChanges, - .globalShadowSettings = globalShadowSettings, - }; + LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(), + .layerLifecycleManager = mLifecycleManager, + .includeMetadata = false, + .displays = mFrontEndDisplayInfos, + .displayChanges = hasDisplayChanges, + .globalShadowSettings = globalShadowSettings, + .supportedLayerGenericMetadata = {}, + .genericLayerMetadataKeyMap = {}}; actualBuilder.update(args); // rebuild layer snapshots from scratch and verify that it matches the updated state. @@ -100,6 +100,9 @@ protected: } LayerSnapshot* getSnapshot(uint32_t layerId) { return mSnapshotBuilder.getSnapshot(layerId); } + LayerSnapshot* getSnapshot(const LayerHierarchy::TraversalPath path) { + return mSnapshotBuilder.getSnapshot(path); + } LayerHierarchyBuilder mHierarchyBuilder{{}}; LayerSnapshotBuilder mSnapshotBuilder; @@ -111,23 +114,25 @@ const std::vector<uint32_t> LayerSnapshotTest::STARTING_ZORDER = {1, 11, 111 122, 1221, 13, 2}; TEST_F(LayerSnapshotTest, buildSnapshot) { - LayerSnapshotBuilder::Args args{ - .root = mHierarchyBuilder.getHierarchy(), - .layerLifecycleManager = mLifecycleManager, - .includeMetadata = false, - .displays = mFrontEndDisplayInfos, - .globalShadowSettings = globalShadowSettings, - }; + LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(), + .layerLifecycleManager = mLifecycleManager, + .includeMetadata = false, + .displays = mFrontEndDisplayInfos, + .globalShadowSettings = globalShadowSettings, + .supportedLayerGenericMetadata = {}, + .genericLayerMetadataKeyMap = {}}; LayerSnapshotBuilder builder(args); } TEST_F(LayerSnapshotTest, updateSnapshot) { - LayerSnapshotBuilder::Args args{ - .root = mHierarchyBuilder.getHierarchy(), - .layerLifecycleManager = mLifecycleManager, - .includeMetadata = false, - .displays = mFrontEndDisplayInfos, - .globalShadowSettings = globalShadowSettings, + LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(), + .layerLifecycleManager = mLifecycleManager, + .includeMetadata = false, + .displays = mFrontEndDisplayInfos, + .globalShadowSettings = globalShadowSettings, + .supportedLayerGenericMetadata = {}, + .genericLayerMetadataKeyMap = {} + }; LayerSnapshotBuilder builder; @@ -320,7 +325,7 @@ TEST_F(LayerSnapshotTest, NoLayerVoteForParentWithChildVotes) { // └── 2 // ROOT (DISPLAY 1) // └── 3 (mirrors display 0) -TEST_F(LayerSnapshotTest, displayMirrorRespects) { +TEST_F(LayerSnapshotTest, displayMirrorRespectsLayerSkipScreenshotFlag) { setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot); createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0)); setLayerStack(3, 1); @@ -329,4 +334,96 @@ TEST_F(LayerSnapshotTest, displayMirrorRespects) { UPDATE_AND_VERIFY(mSnapshotBuilder, expected); } +// ROOT (DISPLAY 0) +// ├── 1 +// │ ├── 11 +// │ │ └── 111 +// │ └── 13 +// └── 2 +// ROOT (DISPLAY 3) +// └── 3 (mirrors display 0) +TEST_F(LayerSnapshotTest, mirrorLayerGetsCorrectLayerStack) { + reparentLayer(12, UNASSIGNED_LAYER_ID); + createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0)); + setLayerStack(3, 3); + createDisplayMirrorLayer(4, ui::LayerStack::fromValue(0)); + setLayerStack(4, 4); + + std::vector<uint32_t> expected = {4, 1, 11, 111, 13, 2, 3, 1, 11, + 111, 13, 2, 1, 11, 111, 13, 2}; + UPDATE_AND_VERIFY(mSnapshotBuilder, expected); + EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootId = 3})->outputFilter.layerStack.id, 3u); + EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootId = 4})->outputFilter.layerStack.id, 4u); +} + +// ROOT (DISPLAY 0) +// ├── 1 (crop 50x50) +// │ ├── 11 +// │ │ └── 111 +// │ └── 13 +// └── 2 +// ROOT (DISPLAY 3) +// └── 3 (mirrors display 0) (crop 100x100) +TEST_F(LayerSnapshotTest, mirrorLayerTouchIsCroppedByMirrorRoot) { + reparentLayer(12, UNASSIGNED_LAYER_ID); + createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0)); + setLayerStack(3, 3); + setCrop(1, Rect{50, 50}); + setCrop(3, Rect{100, 100}); + setCrop(111, Rect{200, 200}); + Region touch{Rect{0, 0, 1000, 1000}}; + setTouchableRegion(111, touch); + std::vector<uint32_t> expected = {3, 1, 11, 111, 13, 2, 1, 11, 111, 13, 2}; + UPDATE_AND_VERIFY(mSnapshotBuilder, expected); + EXPECT_TRUE(getSnapshot({.id = 111})->inputInfo.touchableRegion.hasSameRects(touch)); + Region touchCroppedByMirrorRoot{Rect{0, 0, 50, 50}}; + EXPECT_TRUE(getSnapshot({.id = 111, .mirrorRootId = 3}) + ->inputInfo.touchableRegion.hasSameRects(touchCroppedByMirrorRoot)); +} + +TEST_F(LayerSnapshotTest, canRemoveDisplayMirror) { + 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); + destroyLayerHandle(3); + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); +} + +TEST_F(LayerSnapshotTest, cleanUpUnreachableSnapshotsAfterMirroring) { + size_t startingNumSnapshots = mSnapshotBuilder.getSnapshots().size(); + createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0)); + setLayerStack(3, 1); + std::vector<uint32_t> expected = {3, 1, 11, 111, 12, 121, 122, 1221, 13, 2, + 1, 11, 111, 12, 121, 122, 1221, 13, 2}; + UPDATE_AND_VERIFY(mSnapshotBuilder, expected); + destroyLayerHandle(3); + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + + EXPECT_EQ(startingNumSnapshots, mSnapshotBuilder.getSnapshots().size()); +} + +// Rel z doesn't create duplicate snapshots but this is for completeness +TEST_F(LayerSnapshotTest, cleanUpUnreachableSnapshotsAfterRelZ) { + size_t startingNumSnapshots = mSnapshotBuilder.getSnapshots().size(); + reparentRelativeLayer(13, 11); + UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 11, 13, 111, 12, 121, 122, 1221, 2}); + setZ(13, 0); + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + + EXPECT_EQ(startingNumSnapshots, mSnapshotBuilder.getSnapshots().size()); +} + +TEST_F(LayerSnapshotTest, cleanUpUnreachableSnapshotsAfterLayerDestruction) { + size_t startingNumSnapshots = mSnapshotBuilder.getSnapshots().size(); + destroyLayerHandle(2); + destroyLayerHandle(122); + + std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13}; + UPDATE_AND_VERIFY(mSnapshotBuilder, expected); + + EXPECT_LE(startingNumSnapshots - 2, mSnapshotBuilder.getSnapshots().size()); +} + } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp b/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp index ee42e19c34..803e807d7b 100644 --- a/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp +++ b/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * 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. @@ -16,15 +16,8 @@ #include "LayerTestUtils.h" -#include "mock/MockEventThread.h" - namespace android { -using testing::_; -using testing::Return; - -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; - sp<Layer> BufferStateLayerFactory::createLayer(TestableSurfaceFlinger& flinger) { sp<Client> client; LayerCreationArgs args(flinger.flinger(), client, "buffer-state-layer", LAYER_FLAGS, @@ -44,36 +37,7 @@ std::string PrintToStringParamName( } BaseLayerTest::BaseLayerTest() { - setupScheduler(); -} - -void BaseLayerTest::setupScheduler() { - auto eventThread = std::make_unique<mock::EventThread>(); - auto sfEventThread = std::make_unique<mock::EventThread>(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique<mock::VsyncController>(); - auto vsyncTracker = std::make_unique<mock::VSyncTracker>(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread), - TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp, - TestableSurfaceFlinger::kTwoDisplayModes); + mFlinger.setupMockScheduler(); } } // namespace android diff --git a/services/surfaceflinger/tests/unittests/LayerTestUtils.h b/services/surfaceflinger/tests/unittests/LayerTestUtils.h index ab446fafeb..0773d9081e 100644 --- a/services/surfaceflinger/tests/unittests/LayerTestUtils.h +++ b/services/surfaceflinger/tests/unittests/LayerTestUtils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * 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. @@ -63,8 +63,6 @@ class BaseLayerTest : public ::testing::TestWithParam<std::shared_ptr<LayerFacto protected: BaseLayerTest(); - void setupScheduler(); - TestableSurfaceFlinger mFlinger; }; diff --git a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp index 7aa5201f2f..8f1b450b06 100644 --- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp +++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp @@ -67,12 +67,12 @@ struct MockTokenManager : frametimeline::TokenManager { struct MessageQueueTest : testing::Test { void SetUp() override { - EXPECT_CALL(mVSyncDispatch, registerCallback(_, "sf")).WillOnce(Return(mCallbackToken)); + EXPECT_CALL(*mVSyncDispatch, registerCallback(_, "sf")).WillOnce(Return(mCallbackToken)); EXPECT_NO_FATAL_FAILURE(mEventQueue.initVsync(mVSyncDispatch, mTokenManager, kDuration)); - EXPECT_CALL(mVSyncDispatch, unregisterCallback(mCallbackToken)).Times(1); + EXPECT_CALL(*mVSyncDispatch, unregisterCallback(mCallbackToken)).Times(1); } - mock::VSyncDispatch mVSyncDispatch; + std::shared_ptr<mock::VSyncDispatch> mVSyncDispatch = std::make_shared<mock::VSyncDispatch>(); MockTokenManager mTokenManager; TestableMessageQueue mEventQueue; @@ -90,7 +90,7 @@ TEST_F(MessageQueueTest, commit) { .earliestVsync = 0}; EXPECT_FALSE(mEventQueue.getScheduledFrameTime()); - EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); + EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); ASSERT_TRUE(mEventQueue.getScheduledFrameTime()); @@ -103,13 +103,13 @@ TEST_F(MessageQueueTest, commitTwice) { .readyDuration = 0, .earliestVsync = 0}; - EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); + EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); ASSERT_TRUE(mEventQueue.getScheduledFrameTime()); EXPECT_EQ(1234, mEventQueue.getScheduledFrameTime()->time_since_epoch().count()); - EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(4567)); + EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(4567)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); ASSERT_TRUE(mEventQueue.getScheduledFrameTime()); @@ -122,7 +122,7 @@ TEST_F(MessageQueueTest, commitTwiceWithCallback) { .readyDuration = 0, .earliestVsync = 0}; - EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); + EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); ASSERT_TRUE(mEventQueue.getScheduledFrameTime()); @@ -149,7 +149,7 @@ TEST_F(MessageQueueTest, commitTwiceWithCallback) { .readyDuration = 0, .earliestVsync = kPresentTime.ns()}; - EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timingAfterCallback)).WillOnce(Return(0)); + EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timingAfterCallback)).WillOnce(Return(0)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); } @@ -161,7 +161,7 @@ TEST_F(MessageQueueTest, commitWithDurationChange) { .readyDuration = 0, .earliestVsync = 0}; - EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(0)); + EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(0)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); } diff --git a/services/surfaceflinger/tests/unittests/OneShotTimerTest.cpp b/services/surfaceflinger/tests/unittests/OneShotTimerTest.cpp index aafc323a9b..d08e12c7a4 100644 --- a/services/surfaceflinger/tests/unittests/OneShotTimerTest.cpp +++ b/services/surfaceflinger/tests/unittests/OneShotTimerTest.cpp @@ -224,7 +224,8 @@ TEST_F(OneShotTimerTest, DISABLED_timeoutCallbackExecutionTest) { EXPECT_FALSE(mResetTimerCallback.waitForUnexpectedCall().has_value()); } -TEST_F(OneShotTimerTest, noCallbacksAfterStopAndResetTest) { +// TODO(b/186417847) This test is flaky. Reenable once fixed. +TEST_F(OneShotTimerTest, DISABLED_noCallbacksAfterStopAndResetTest) { fake::FakeClock* clock = new fake::FakeClock(); mIdleTimer = std::make_unique<scheduler::OneShotTimer>("TestTimer", 1ms, mResetTimerCallback.getInvocable(), diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 4b15385fa8..dc76b4c90f 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <ftl/fake_guard.h> #include <gmock/gmock.h> #include <gtest/gtest.h> #include <log/log.h> @@ -122,12 +123,6 @@ TEST_F(SchedulerTest, invalidConnectionHandle) { EXPECT_CALL(*mEventThread, onHotplugReceived(_, _)).Times(0); mScheduler->onHotplugReceived(handle, kDisplayId1, false); - EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(0); - mScheduler->onScreenAcquired(handle); - - EXPECT_CALL(*mEventThread, onScreenReleased()).Times(0); - mScheduler->onScreenReleased(handle); - std::string output; EXPECT_CALL(*mEventThread, dump(_)).Times(0); mScheduler->dump(handle, output); @@ -147,12 +142,6 @@ TEST_F(SchedulerTest, validConnectionHandle) { EXPECT_CALL(*mEventThread, onHotplugReceived(kDisplayId1, false)).Times(1); mScheduler->onHotplugReceived(mConnectionHandle, kDisplayId1, false); - EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(1); - mScheduler->onScreenAcquired(mConnectionHandle); - - EXPECT_CALL(*mEventThread, onScreenReleased()).Times(1); - mScheduler->onScreenReleased(mConnectionHandle); - std::string output("dump"); EXPECT_CALL(*mEventThread, dump(output)).Times(1); mScheduler->dump(mConnectionHandle, output); @@ -172,11 +161,12 @@ TEST_F(SchedulerTest, chooseRefreshRateForContentIsNoopWhenModeSwitchingIsNotSup // recordLayerHistory should be a noop ASSERT_EQ(0u, mScheduler->getNumActiveLayers()); - mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer); + mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0, + LayerHistory::LayerUpdateType::Buffer); ASSERT_EQ(0u, mScheduler->getNumActiveLayers()); constexpr hal::PowerMode kPowerModeOn = hal::PowerMode::ON; - mScheduler->setDisplayPowerMode(kPowerModeOn); + FTL_FAKE_GUARD(kMainThreadContext, mScheduler->setDisplayPowerMode(kDisplayId1, kPowerModeOn)); constexpr uint32_t kDisplayArea = 999'999; mScheduler->onActiveDisplayAreaChanged(kDisplayArea); @@ -196,7 +186,8 @@ TEST_F(SchedulerTest, updateDisplayModes) { kDisplay1Mode60->getId())); ASSERT_EQ(0u, mScheduler->getNumActiveLayers()); - mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer); + mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0, + LayerHistory::LayerUpdateType::Buffer); ASSERT_EQ(1u, mScheduler->getNumActiveLayers()); } @@ -245,10 +236,11 @@ TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) { const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger()); EXPECT_CALL(*layer, isVisible()).WillOnce(Return(true)); - mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer); + mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0, + LayerHistory::LayerUpdateType::Buffer); constexpr hal::PowerMode kPowerModeOn = hal::PowerMode::ON; - mScheduler->setDisplayPowerMode(kPowerModeOn); + FTL_FAKE_GUARD(kMainThreadContext, mScheduler->setDisplayPowerMode(kDisplayId1, kPowerModeOn)); constexpr uint32_t kDisplayArea = 999'999; mScheduler->onActiveDisplayAreaChanged(kDisplayArea); diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp index 6adcd5259d..44ab569bd1 100644 --- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp +++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp @@ -380,8 +380,10 @@ TEST_P(SetFrameRateTest, SetOnParentActivatesTree) { commitTransaction(); auto& history = mFlinger.mutableScheduler().mutableLayerHistory(); - history.record(parent.get(), 0, 0, LayerHistory::LayerUpdateType::Buffer); - history.record(child.get(), 0, 0, LayerHistory::LayerUpdateType::Buffer); + history.record(parent->getSequence(), parent->getLayerProps(), 0, 0, + LayerHistory::LayerUpdateType::Buffer); + history.record(child->getSequence(), child->getLayerProps(), 0, 0, + LayerHistory::LayerUpdateType::Buffer); const auto selectorPtr = mFlinger.mutableScheduler().refreshRateSelector(); const auto summary = history.summarize(*selectorPtr, 0); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp index ad3bd353ed..fd1fd47d24 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp @@ -50,7 +50,7 @@ public: mFlinger.configureAndCommit(); mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this) - .setDisplayModes(kModes, kModeId60, std::move(selectorPtr)) + .setRefreshRateSelector(std::move(selectorPtr)) .inject(); // isVsyncPeriodSwitchSupported should return true, otherwise the SF's HWC proxy @@ -100,7 +100,7 @@ void DisplayModeSwitchingTest::setupScheduler( ResyncCallback()))); auto vsyncController = std::make_unique<mock::VsyncController>(); - auto vsyncTracker = std::make_unique<mock::VSyncTracker>(); + auto vsyncTracker = std::make_shared<mock::VSyncTracker>(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) @@ -109,8 +109,8 @@ void DisplayModeSwitchingTest::setupScheduler( EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), std::move(eventThread), std::move(sfEventThread), - TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp, - std::move(selectorPtr)); + std::move(selectorPtr), + TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp); } TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRequired) { @@ -119,7 +119,7 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRe ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); - mFlinger.onActiveDisplayChanged(mDisplay); + mFlinger.onActiveDisplayChanged(*mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), mock::createDisplayModeSpecs(kModeId90.value(), false, 0, @@ -159,7 +159,7 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithoutRefres ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); - mFlinger.onActiveDisplayChanged(mDisplay); + mFlinger.onActiveDisplayChanged(*mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), mock::createDisplayModeSpecs(kModeId90.value(), true, 0, @@ -195,7 +195,7 @@ TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) { ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); - mFlinger.onActiveDisplayChanged(mDisplay); + mFlinger.onActiveDisplayChanged(*mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), mock::createDisplayModeSpecs(kModeId90.value(), false, 0, @@ -238,7 +238,7 @@ TEST_F(DisplayModeSwitchingTest, changeResolution_OnActiveDisplay_WithoutRefresh ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); - mFlinger.onActiveDisplayChanged(mDisplay); + mFlinger.onActiveDisplayChanged(*mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), mock::createDisplayModeSpecs(kModeId90_4K.value(), false, 0, @@ -315,7 +315,7 @@ TEST_F(DisplayModeSwitchingTest, multiDisplay) { EXPECT_EQ(innerDisplay->getActiveMode().modePtr->getId(), kModeId60); EXPECT_EQ(outerDisplay->getActiveMode().modePtr->getId(), kModeId120); - mFlinger.onActiveDisplayChanged(innerDisplay); + mFlinger.onActiveDisplayChanged(*innerDisplay); EXPECT_EQ(NO_ERROR, mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(), @@ -359,7 +359,7 @@ TEST_F(DisplayModeSwitchingTest, multiDisplay) { EXPECT_FALSE(outerDisplay->getDesiredActiveMode()); EXPECT_EQ(outerDisplay->getActiveMode().modePtr->getId(), kModeId120); - mFlinger.onActiveDisplayChanged(outerDisplay); + mFlinger.onActiveDisplayChanged(*outerDisplay); // No transition on the inner display. EXPECT_FALSE(innerDisplay->getDesiredActiveMode()); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HdrOutputControlTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HdrOutputControlTest.cpp new file mode 100644 index 0000000000..a2c54ac621 --- /dev/null +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HdrOutputControlTest.cpp @@ -0,0 +1,89 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "LibSurfaceFlingerUnittests" + +#include <gtest/gtest.h> +#include <gui/AidlStatusUtil.h> +#include <private/gui/ComposerService.h> +#include <private/gui/ComposerServiceAIDL.h> + +#include "DisplayTransactionTestHelpers.h" + +namespace android { + +using aidl::android::hardware::graphics::common::HdrConversionCapability; +using aidl::android::hardware::graphics::common::HdrConversionStrategy; +using GuiHdrConversionStrategyTag = gui::HdrConversionStrategy::Tag; +using gui::aidl_utils::statusTFromBinderStatus; + +TEST(HdrOutputControlTest, testGetHdrOutputConversionSupport) { + sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService()); + + bool hdrOutputConversionSupport; + binder::Status status = sf->getHdrOutputConversionSupport(&hdrOutputConversionSupport); + + ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); +} + +TEST(HdrOutputControlTest, testGetHdrConversionCapabilities) { + sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService()); + + bool hdrOutputConversionSupport; + binder::Status getSupportStatus = + sf->getHdrOutputConversionSupport(&hdrOutputConversionSupport); + ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(getSupportStatus)); + + std::vector<gui::HdrConversionCapability> capabilities; + binder::Status status = sf->getHdrConversionCapabilities(&capabilities); + + if (hdrOutputConversionSupport) { + ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); + } else { + ASSERT_EQ(INVALID_OPERATION, statusTFromBinderStatus(status)); + } +} + +TEST(HdrOutputControlTest, testSetHdrConversionStrategy) { + sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService()); + + bool hdrOutputConversionSupport; + binder::Status getSupportStatus = + sf->getHdrOutputConversionSupport(&hdrOutputConversionSupport); + ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(getSupportStatus)); + + std::vector<HdrConversionStrategy> strategies = + {HdrConversionStrategy(std::in_place_index<static_cast<size_t>( + GuiHdrConversionStrategyTag::passthrough)>), + HdrConversionStrategy(std::in_place_index<static_cast<size_t>( + GuiHdrConversionStrategyTag::autoAllowedHdrTypes)>), + HdrConversionStrategy(std::in_place_index<static_cast<size_t>( + GuiHdrConversionStrategyTag::forceHdrConversion)>)}; + int32_t outPreferredHdrOutputType = 0; + + for (HdrConversionStrategy strategy : strategies) { + binder::Status status = sf->setHdrConversionStrategy(&strategy, &outPreferredHdrOutputType); + + if (hdrOutputConversionSupport) { + ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); + } else { + ASSERT_EQ(INVALID_OPERATION, statusTFromBinderStatus(status)); + } + } +} + +} // namespace android diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayPacesetterTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayPacesetterTest.cpp new file mode 100644 index 0000000000..e38f56e65f --- /dev/null +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayPacesetterTest.cpp @@ -0,0 +1,81 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "LibSurfaceFlingerUnittests" + +#include "DisplayTransactionTestHelpers.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +namespace android { +namespace { + +struct MultiDisplayPacesetterTest : DisplayTransactionTest { + static constexpr bool kWithMockScheduler = false; + MultiDisplayPacesetterTest() : DisplayTransactionTest(kWithMockScheduler) {} +}; + +TEST_F(MultiDisplayPacesetterTest, foldable) { + injectMockScheduler(InnerDisplayVariant::DISPLAY_ID::get()); + + // Inject inner and outer displays with uninitialized power modes. + sp<DisplayDevice> innerDisplay, outerDisplay; + constexpr bool kInitPowerMode = false; + { + InnerDisplayVariant::injectHwcDisplay<kInitPowerMode>(this); + auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this); + injector.setPowerMode(std::nullopt); + injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector()); + innerDisplay = injector.inject(); + } + { + OuterDisplayVariant::injectHwcDisplay<kInitPowerMode>(this); + auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this); + injector.setPowerMode(std::nullopt); + outerDisplay = injector.inject(); + } + + // When the device boots, the inner display should be the pacesetter. + ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), innerDisplay->getPhysicalId()); + + // ...and should still be after powering on. + mFlinger.setPowerModeInternal(innerDisplay, PowerMode::ON); + ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), innerDisplay->getPhysicalId()); + + // The outer display should become the pacesetter after folding. + mFlinger.setPowerModeInternal(innerDisplay, PowerMode::OFF); + mFlinger.setPowerModeInternal(outerDisplay, PowerMode::ON); + ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), outerDisplay->getPhysicalId()); + + // The inner display should become the pacesetter after unfolding. + mFlinger.setPowerModeInternal(outerDisplay, PowerMode::OFF); + mFlinger.setPowerModeInternal(innerDisplay, PowerMode::ON); + ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), innerDisplay->getPhysicalId()); + + // The inner display should stay the pacesetter if both are powered on. + // TODO(b/255635821): The pacesetter should depend on the displays' refresh rates. + mFlinger.setPowerModeInternal(outerDisplay, PowerMode::ON); + ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), innerDisplay->getPhysicalId()); + + // The outer display should become the pacesetter if designated. + mFlinger.scheduler()->setPacesetterDisplay(outerDisplay->getPhysicalId()); + ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), outerDisplay->getPhysicalId()); +} + +} // namespace +} // namespace android diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp index f553a23f3c..98644aa89d 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp @@ -44,7 +44,10 @@ TEST_F(OnInitializeDisplaysTest, onInitializeDisplaysSetsUpPrimaryDisplay) { // We expect a scheduled commit for the display transaction. EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1); - EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); + EXPECT_CALL(static_cast<mock::VSyncTracker&>( + mFlinger.scheduler()->getVsyncSchedule()->getTracker()), + nextAnticipatedVSyncTimeFrom(_)) + .WillRepeatedly(Return(0)); // -------------------------------------------------------------------- // Invocation diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp index 622717f290..7839ef0dbb 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp @@ -28,9 +28,7 @@ #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockComposer.h" #include "mock/DisplayHardware/MockPowerAdvisor.h" -#include "mock/MockEventThread.h" #include "mock/MockTimeStats.h" -#include "mock/MockVsyncController.h" #include "mock/system/window/MockNativeWindow.h" using namespace android; @@ -53,8 +51,6 @@ class SurfaceFlingerPowerHintTest : public Test { public: void SetUp() override; - void setupScheduler(); - protected: TestableSurfaceFlinger mFlinger; renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine(); @@ -68,7 +64,7 @@ protected: }; void SurfaceFlingerPowerHintTest::SetUp() { - setupScheduler(); + mFlinger.setupMockScheduler({.displayId = DEFAULT_DISPLAY_ID}); mComposer = new Hwc2::mock::Composer(); mPowerAdvisor = new Hwc2::mock::PowerAdvisor(); mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine)); @@ -96,39 +92,11 @@ void SurfaceFlingerPowerHintTest::SetUp() { .setDisplaySurface(mDisplaySurface) .setNativeWindow(mNativeWindow) .setPowerMode(hal::PowerMode::ON) + .setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector()) + .skipRegisterDisplay() .inject(); } -void SurfaceFlingerPowerHintTest::setupScheduler() { - auto eventThread = std::make_unique<mock::EventThread>(); - auto sfEventThread = std::make_unique<mock::EventThread>(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique<mock::VsyncController>(); - auto vsyncTracker = std::make_unique<mock::VSyncTracker>(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread), - TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp, - TestableSurfaceFlinger::kTwoDisplayModes); -} - TEST_F(SurfaceFlingerPowerHintTest, sendDurationsIncludingHwcWaitTime) { ON_CALL(*mPowerAdvisor, usePowerHintSession()).WillByDefault(Return(true)); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp index ab732ed485..7754c21805 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp @@ -59,54 +59,45 @@ struct DozeNotSupportedVariant { }; struct EventThreadBaseSupportedVariant { - static void setupVsyncAndEventThreadNoCallExpectations(DisplayTransactionTest* test) { - // The callback should not be notified to toggle VSYNC. - EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_)).Times(0); - - // The event thread should not be notified. - EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(0); - EXPECT_CALL(*test->mEventThread, onScreenAcquired()).Times(0); + static void setupVsyncNoCallExpectations(DisplayTransactionTest* test) { + // Expect no change to hardware nor synthetic VSYNC. + EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_, _)).Times(0); + EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(_)).Times(0); } }; struct EventThreadNotSupportedVariant : public EventThreadBaseSupportedVariant { - static void setupAcquireAndEnableVsyncCallExpectations(DisplayTransactionTest* test) { - // These calls are only expected for the primary display. - - // Instead expect no calls. - setupVsyncAndEventThreadNoCallExpectations(test); + static void setupEnableVsyncCallExpectations(DisplayTransactionTest* test) { + setupVsyncNoCallExpectations(test); } - static void setupReleaseAndDisableVsyncCallExpectations(DisplayTransactionTest* test) { - // These calls are only expected for the primary display. - - // Instead expect no calls. - setupVsyncAndEventThreadNoCallExpectations(test); + static void setupDisableVsyncCallExpectations(DisplayTransactionTest* test) { + setupVsyncNoCallExpectations(test); } }; struct EventThreadIsSupportedVariant : public EventThreadBaseSupportedVariant { - static void setupAcquireAndEnableVsyncCallExpectations(DisplayTransactionTest* test) { - // The callback should be notified to enable VSYNC. - EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(true)).Times(1); - - // The event thread should be notified that the screen was acquired. - EXPECT_CALL(*test->mEventThread, onScreenAcquired()).Times(1); + static void setupEnableVsyncCallExpectations(DisplayTransactionTest* test) { + // Expect to enable hardware VSYNC and disable synthetic VSYNC. + EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_, true)).Times(1); + EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(false)).Times(1); } - static void setupReleaseAndDisableVsyncCallExpectations(DisplayTransactionTest* test) { - // The callback should be notified to disable VSYNC. - EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(false)).Times(1); - - // The event thread should not be notified that the screen was released. - EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(1); + static void setupDisableVsyncCallExpectations(DisplayTransactionTest* test) { + // Expect to disable hardware VSYNC and enable synthetic VSYNC. + EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_, false)).Times(1); + EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(true)).Times(1); } }; struct DispSyncIsSupportedVariant { static void setupResetModelCallExpectations(DisplayTransactionTest* test) { - EXPECT_CALL(*test->mVsyncController, startPeriodTransition(DEFAULT_VSYNC_PERIOD)).Times(1); - EXPECT_CALL(*test->mVSyncTracker, resetModel()).Times(1); + auto vsyncSchedule = test->mFlinger.scheduler()->getVsyncSchedule(); + EXPECT_CALL(static_cast<mock::VsyncController&>(vsyncSchedule->getController()), + startPeriodTransition(DEFAULT_VSYNC_PERIOD, false)) + .Times(1); + EXPECT_CALL(static_cast<mock::VSyncTracker&>(vsyncSchedule->getTracker()), resetModel()) + .Times(1); } }; @@ -133,7 +124,7 @@ struct TransitionOffToOnVariant : public TransitionVariantCommon<PowerMode::OFF, template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::ON); - Case::EventThread::setupAcquireAndEnableVsyncCallExpectations(test); + Case::EventThread::setupEnableVsyncCallExpectations(test); Case::DispSync::setupResetModelCallExpectations(test); Case::setupRepaintEverythingCallExpectations(test); } @@ -148,7 +139,7 @@ struct TransitionOffToDozeSuspendVariant template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE_SUSPEND); - Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test); + Case::EventThread::setupVsyncNoCallExpectations(test); Case::setupRepaintEverythingCallExpectations(test); } @@ -160,7 +151,7 @@ struct TransitionOffToDozeSuspendVariant struct TransitionOnToOffVariant : public TransitionVariantCommon<PowerMode::ON, PowerMode::OFF> { template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupReleaseAndDisableVsyncCallExpectations(test); + Case::EventThread::setupDisableVsyncCallExpectations(test); Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::OFF); } @@ -173,7 +164,7 @@ struct TransitionDozeSuspendToOffVariant : public TransitionVariantCommon<PowerMode::DOZE_SUSPEND, PowerMode::OFF> { template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test); + Case::EventThread::setupVsyncNoCallExpectations(test); Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::OFF); } @@ -185,7 +176,7 @@ struct TransitionDozeSuspendToOffVariant struct TransitionOnToDozeVariant : public TransitionVariantCommon<PowerMode::ON, PowerMode::DOZE> { template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test); + Case::EventThread::setupVsyncNoCallExpectations(test); Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE); } }; @@ -194,7 +185,7 @@ struct TransitionDozeSuspendToDozeVariant : public TransitionVariantCommon<PowerMode::DOZE_SUSPEND, PowerMode::DOZE> { template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupAcquireAndEnableVsyncCallExpectations(test); + Case::EventThread::setupEnableVsyncCallExpectations(test); Case::DispSync::setupResetModelCallExpectations(test); Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE); } @@ -203,7 +194,7 @@ struct TransitionDozeSuspendToDozeVariant struct TransitionDozeToOnVariant : public TransitionVariantCommon<PowerMode::DOZE, PowerMode::ON> { template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test); + Case::EventThread::setupVsyncNoCallExpectations(test); Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::ON); } }; @@ -212,7 +203,7 @@ struct TransitionDozeSuspendToOnVariant : public TransitionVariantCommon<PowerMode::DOZE_SUSPEND, PowerMode::ON> { template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupAcquireAndEnableVsyncCallExpectations(test); + Case::EventThread::setupEnableVsyncCallExpectations(test); Case::DispSync::setupResetModelCallExpectations(test); Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::ON); } @@ -222,7 +213,7 @@ struct TransitionOnToDozeSuspendVariant : public TransitionVariantCommon<PowerMode::ON, PowerMode::DOZE_SUSPEND> { template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupReleaseAndDisableVsyncCallExpectations(test); + Case::EventThread::setupDisableVsyncCallExpectations(test); Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE_SUSPEND); } }; @@ -231,7 +222,7 @@ struct TransitionOnToUnknownVariant : public TransitionVariantCommon<PowerMode::ON, static_cast<PowerMode>(POWER_MODE_LEET)> { template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test); + Case::EventThread::setupVsyncNoCallExpectations(test); Case::setupNoComposerPowerModeCallExpectations(test); } }; @@ -262,8 +253,9 @@ struct DisplayPowerCase { return display; } - static void setInitialPrimaryHWVsyncEnabled(DisplayTransactionTest* test, bool enabled) { - test->mFlinger.scheduler()->mutablePrimaryHWVsyncEnabled() = enabled; + static void setInitialHwVsyncEnabled(DisplayTransactionTest* test, PhysicalDisplayId id, + bool enabled) { + test->mFlinger.scheduler()->setInitialHwVsyncEnabled(id, enabled); } static void setupRepaintEverythingCallExpectations(DisplayTransactionTest* test) { @@ -329,9 +321,12 @@ void SetPowerModeInternalTest::transitionDisplayCommon() { Case::Doze::setupComposerCallExpectations(this); auto display = Case::injectDisplayWithInitialPowerMode(this, Case::Transition::INITIAL_POWER_MODE); - Case::setInitialPrimaryHWVsyncEnabled(this, - PowerModeInitialVSyncEnabled< - Case::Transition::INITIAL_POWER_MODE>::value); + auto displayId = display->getId(); + if (auto physicalDisplayId = PhysicalDisplayId::tryCast(displayId)) { + Case::setInitialHwVsyncEnabled(this, *physicalDisplayId, + PowerModeInitialVSyncEnabled< + Case::Transition::INITIAL_POWER_MODE>::value); + } // -------------------------------------------------------------------- // Call Expectations @@ -404,13 +399,11 @@ TEST_F(SetPowerModeInternalTest, setPowerModeInternalDoesNothingIfVirtualDisplay EXPECT_EQ(PowerMode::ON, display.mutableDisplayDevice()->getPowerMode()); } -// TODO(b/262417075) -TEST_F(SetPowerModeInternalTest, DISABLED_transitionsDisplayFromOffToOnPrimaryDisplay) { +TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOffToOnPrimaryDisplay) { transitionDisplayCommon<PrimaryDisplayPowerCase<TransitionOffToOnVariant>>(); } -// TODO(b/262417075) -TEST_F(SetPowerModeInternalTest, DISABLED_transitionsDisplayFromOffToDozeSuspendPrimaryDisplay) { +TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOffToDozeSuspendPrimaryDisplay) { transitionDisplayCommon<PrimaryDisplayPowerCase<TransitionOffToDozeSuspendVariant>>(); } @@ -446,13 +439,11 @@ TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOnToUnknownPrimaryDisplay transitionDisplayCommon<PrimaryDisplayPowerCase<TransitionOnToUnknownVariant>>(); } -// TODO(b/262417075) -TEST_F(SetPowerModeInternalTest, DISABLED_transitionsDisplayFromOffToOnExternalDisplay) { +TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOffToOnExternalDisplay) { transitionDisplayCommon<ExternalDisplayPowerCase<TransitionOffToOnVariant>>(); } -// TODO(b/262417075) -TEST_F(SetPowerModeInternalTest, DISABLED_transitionsDisplayFromOffToDozeSuspendExternalDisplay) { +TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOffToDozeSuspendExternalDisplay) { transitionDisplayCommon<ExternalDisplayPowerCase<TransitionOffToDozeSuspendVariant>>(); } @@ -488,38 +479,5 @@ TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOnToUnknownExternalDispla transitionDisplayCommon<ExternalDisplayPowerCase<TransitionOnToUnknownVariant>>(); } -// TODO(b/262417075) -TEST_F(SetPowerModeInternalTest, DISABLED_designatesLeaderDisplay) { - using Case = SimplePrimaryDisplayCase; - - // -------------------------------------------------------------------- - // Preconditions - - // Inject a primary display with uninitialized power mode. - constexpr bool kInitPowerMode = false; - Case::Display::injectHwcDisplay<kInitPowerMode>(this); - auto injector = Case::Display::makeFakeExistingDisplayInjector(this); - injector.setPowerMode(std::nullopt); - const auto display = injector.inject(); - - // -------------------------------------------------------------------- - // Invocation - - // FakeDisplayDeviceInjector registers the display with Scheduler, so it has already been - // designated as the leader. Set an arbitrary leader to verify that `setPowerModeInternal` - // designates a leader regardless of any preceding `Scheduler::registerDisplay` call(s). - constexpr PhysicalDisplayId kPlaceholderId = PhysicalDisplayId::fromPort(42); - ASSERT_NE(display->getPhysicalId(), kPlaceholderId); - mFlinger.scheduler()->setLeaderDisplay(kPlaceholderId); - - mFlinger.setPowerModeInternal(display, PowerMode::ON); - - // -------------------------------------------------------------------- - // Postconditions - - // The primary display should be designated as the leader. - EXPECT_EQ(mFlinger.scheduler()->leaderDisplayId(), display->getPhysicalId()); -} - } // namespace } // namespace android diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp index fed6a1ae56..0e5f1ea789 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp @@ -3,47 +3,17 @@ #include <gui/LayerMetadata.h> #include "TestableSurfaceFlinger.h" -#include "mock/MockEventThread.h" -#include "mock/MockVsyncController.h" namespace android { using testing::_; using testing::Return; -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; class SurfaceFlingerUpdateLayerMetadataSnapshotTest : public testing::Test { public: - SurfaceFlingerUpdateLayerMetadataSnapshotTest() { setupScheduler(); } + SurfaceFlingerUpdateLayerMetadataSnapshotTest() { mFlinger.setupMockScheduler(); } protected: - void setupScheduler() { - auto eventThread = std::make_unique<mock::EventThread>(); - auto sfEventThread = std::make_unique<mock::EventThread>(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique<mock::VsyncController>(); - auto vsyncTracker = std::make_unique<mock::VSyncTracker>(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread)); - } - sp<Layer> createLayer(const char* name, LayerMetadata& inOutlayerMetadata) { LayerCreationArgs args = LayerCreationArgs{mFlinger.flinger(), nullptr, name, 0, inOutlayerMetadata}; diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index 6cf61416c0..d4b4434f77 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -37,19 +37,16 @@ class TestableScheduler : public Scheduler, private ICompositor { public: TestableScheduler(RefreshRateSelectorPtr selectorPtr, ISchedulerCallback& callback) : TestableScheduler(std::make_unique<mock::VsyncController>(), - std::make_unique<mock::VSyncTracker>(), std::move(selectorPtr), + std::make_shared<mock::VSyncTracker>(), std::move(selectorPtr), /* modulatorPtr */ nullptr, callback) {} TestableScheduler(std::unique_ptr<VsyncController> controller, - std::unique_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr, + std::shared_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))); - const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId(); - registerDisplay(displayId, std::move(selectorPtr)); + registerDisplay(displayId, std::move(selectorPtr), std::move(controller), + std::move(tracker)); ON_CALL(*this, postMessage).WillByDefault([](sp<MessageHandler>&& handler) { // Execute task to prevent broken promise exception on destruction. @@ -66,22 +63,28 @@ public: 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(); } + auto refreshRateSelector() { return pacesetterSelectorPtr(); } const auto& refreshRateSelectors() const NO_THREAD_SAFETY_ANALYSIS { return mRefreshRateSelectors; } void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) { + registerDisplay(displayId, std::move(selectorPtr), + std::make_unique<mock::VsyncController>(), + std::make_shared<mock::VSyncTracker>()); + } + + void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr, + std::unique_ptr<VsyncController> controller, + std::shared_ptr<VSyncTracker> tracker) { ftl::FakeGuard guard(kMainThreadContext); - Scheduler::registerDisplay(displayId, std::move(selectorPtr)); + Scheduler::registerDisplayInternal(displayId, std::move(selectorPtr), + std::shared_ptr<VsyncSchedule>( + new VsyncSchedule(displayId, std::move(tracker), + std::make_shared< + mock::VSyncDispatch>(), + std::move(controller)))); } void unregisterDisplay(PhysicalDisplayId displayId) { @@ -89,15 +92,16 @@ public: Scheduler::unregisterDisplay(displayId); } - std::optional<PhysicalDisplayId> leaderDisplayId() const NO_THREAD_SAFETY_ANALYSIS { - return mLeaderDisplayId; + std::optional<PhysicalDisplayId> pacesetterDisplayId() const NO_THREAD_SAFETY_ANALYSIS { + return mPacesetterDisplayId; } - void setLeaderDisplay(PhysicalDisplayId displayId) { + void setPacesetterDisplay(PhysicalDisplayId displayId) { ftl::FakeGuard guard(kMainThreadContext); - Scheduler::setLeaderDisplay(displayId); + Scheduler::setPacesetterDisplay(displayId); } + auto& mutableAppConnectionHandle() { return mAppConnectionHandle; } auto& mutableVsyncModulator() { return *mVsyncModulator; } auto& mutableLayerHistory() { return mLayerHistory; } @@ -157,6 +161,13 @@ public: Scheduler::onNonPrimaryDisplayModeChanged(handle, mode); } + void setInitialHwVsyncEnabled(PhysicalDisplayId id, bool enabled) { + auto schedule = getVsyncSchedule(id); + std::lock_guard<std::mutex> lock(schedule->mHwVsyncLock); + schedule->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 68c738fc9d..6334ec808b 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -28,6 +28,7 @@ #include <compositionengine/impl/OutputLayerCompositionState.h> #include <compositionengine/mock/DisplaySurface.h> #include <ftl/fake_guard.h> +#include <ftl/match.h> #include <gui/ScreenCaptureResults.h> #include <ui/DynamicDisplayInfo.h> @@ -47,14 +48,12 @@ #include "TestableScheduler.h" #include "mock/DisplayHardware/MockComposer.h" #include "mock/DisplayHardware/MockDisplayMode.h" +#include "mock/MockEventThread.h" #include "mock/MockFrameTimeline.h" #include "mock/MockFrameTracer.h" #include "mock/MockSchedulerCallback.h" namespace android { - -class EventThread; - namespace renderengine { class RenderEngine; @@ -151,6 +150,11 @@ public: CreateCompositionEngineFunction mCreateCompositionEngine; }; +struct MockSchedulerOptions { + PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(0); + bool useNiceMock = false; +}; + } // namespace surfaceflinger::test class TestableSurfaceFlinger { @@ -189,38 +193,31 @@ public: enum class SchedulerCallbackImpl { kNoOp, kMock }; - static constexpr struct OneDisplayMode { - } kOneDisplayMode; - - static constexpr struct TwoDisplayModes { - } kTwoDisplayModes; + struct DefaultDisplayMode { + // The ID of the injected RefreshRateSelector and its default display mode. + PhysicalDisplayId displayId; + }; - using RefreshRateSelectorPtr = std::shared_ptr<scheduler::RefreshRateSelector>; + using RefreshRateSelectorPtr = scheduler::Scheduler::RefreshRateSelectorPtr; - using DisplayModesVariant = - std::variant<OneDisplayMode, TwoDisplayModes, RefreshRateSelectorPtr>; + using DisplayModesVariant = std::variant<DefaultDisplayMode, RefreshRateSelectorPtr>; void setupScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController, - std::unique_ptr<scheduler::VSyncTracker> vsyncTracker, + std::shared_ptr<scheduler::VSyncTracker> vsyncTracker, std::unique_ptr<EventThread> appEventThread, std::unique_ptr<EventThread> sfEventThread, + DisplayModesVariant modesVariant, SchedulerCallbackImpl callbackImpl = SchedulerCallbackImpl::kNoOp, - DisplayModesVariant modesVariant = kOneDisplayMode, bool useNiceMock = false) { - RefreshRateSelectorPtr selectorPtr; - if (std::holds_alternative<RefreshRateSelectorPtr>(modesVariant)) { - selectorPtr = std::move(std::get<RefreshRateSelectorPtr>(modesVariant)); - } else { - constexpr DisplayModeId kModeId60{0}; - DisplayModes modes = makeModes(mock::createDisplayMode(kModeId60, 60_Hz)); - - if (std::holds_alternative<TwoDisplayModes>(modesVariant)) { - constexpr DisplayModeId kModeId90{1}; - modes.try_emplace(kModeId90, mock::createDisplayMode(kModeId90, 90_Hz)); - } - - selectorPtr = std::make_shared<scheduler::RefreshRateSelector>(modes, kModeId60); - } + RefreshRateSelectorPtr selectorPtr = ftl::match( + modesVariant, + [](DefaultDisplayMode arg) { + constexpr DisplayModeId kModeId60{0}; + return std::make_shared<scheduler::RefreshRateSelector>( + makeModes(mock::createDisplayMode(arg.displayId, kModeId60, 60_Hz)), + kModeId60); + }, + [](RefreshRateSelectorPtr selectorPtr) { return selectorPtr; }); const auto fps = selectorPtr->getActiveMode().fps; mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps); @@ -253,13 +250,47 @@ public: std::move(modulatorPtr), callback); } - mScheduler->initVsync(mScheduler->getVsyncSchedule().getDispatch(), *mTokenManager, 0ms); + mScheduler->initVsync(mScheduler->getVsyncSchedule()->getDispatch(), *mTokenManager, 0ms); + + mScheduler->mutableAppConnectionHandle() = + mScheduler->createConnection(std::move(appEventThread)); - mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread)); + mFlinger->mAppConnectionHandle = mScheduler->mutableAppConnectionHandle(); mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread)); resetScheduler(mScheduler); } + void setupMockScheduler(test::MockSchedulerOptions options = {}) { + using testing::_; + using testing::Return; + + auto eventThread = makeMock<mock::EventThread>(options.useNiceMock); + auto sfEventThread = makeMock<mock::EventThread>(options.useNiceMock); + + EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); + EXPECT_CALL(*eventThread, createEventConnection(_, _)) + .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(), + mock::EventThread::kCallingUid, + ResyncCallback()))); + + EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); + EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) + .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(), + mock::EventThread::kCallingUid, + ResyncCallback()))); + + auto vsyncController = makeMock<mock::VsyncController>(options.useNiceMock); + auto vsyncTracker = makeSharedMock<mock::VSyncTracker>(options.useNiceMock); + + EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); + EXPECT_CALL(*vsyncTracker, currentPeriod()) + .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); + EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); + setupScheduler(std::move(vsyncController), std::move(vsyncTracker), std::move(eventThread), + std::move(sfEventThread), DefaultDisplayMode{options.displayId}, + SchedulerCallbackImpl::kNoOp, options.useNiceMock); + } + void resetScheduler(scheduler::Scheduler* scheduler) { mFlinger->mScheduler.reset(scheduler); } scheduler::TestableScheduler& mutableScheduler() { return *mScheduler; } @@ -474,7 +505,7 @@ public: return mFlinger->setDesiredDisplayModeSpecs(displayToken, specs); } - void onActiveDisplayChanged(const sp<DisplayDevice>& activeDisplay) { + void onActiveDisplayChanged(const DisplayDevice& activeDisplay) { Mutex::Autolock lock(mFlinger->mStateLock); ftl::FakeGuard guard(kMainThreadContext); mFlinger->onActiveDisplayChangedLocked(nullptr, activeDisplay); @@ -773,16 +804,16 @@ public: return mFlinger.mutableDisplays().get(mDisplayToken)->get(); } - // If `selectorPtr` is nullptr, the injector creates RefreshRateSelector from the `modes`. - // Otherwise, it uses `selectorPtr`, which the caller must create using the same `modes`. - // - // TODO(b/182939859): Once `modes` can be retrieved from RefreshRateSelector, remove - // the `selectorPtr` parameter in favor of an alternative setRefreshRateSelector API. - auto& setDisplayModes( - DisplayModes modes, DisplayModeId activeModeId, - std::shared_ptr<scheduler::RefreshRateSelector> selectorPtr = nullptr) { + auto& setDisplayModes(DisplayModes modes, DisplayModeId activeModeId) { mDisplayModes = std::move(modes); mCreationArgs.activeModeId = activeModeId; + mCreationArgs.refreshRateSelector = nullptr; + return *this; + } + + auto& setRefreshRateSelector(RefreshRateSelectorPtr selectorPtr) { + mDisplayModes = selectorPtr->displayModes(); + mCreationArgs.activeModeId = selectorPtr->getActiveMode().modePtr->getId(); mCreationArgs.refreshRateSelector = std::move(selectorPtr); return *this; } @@ -824,6 +855,11 @@ public: return *this; } + auto& skipRegisterDisplay() { + mRegisterDisplay = false; + return *this; + } + sp<DisplayDevice> inject() NO_THREAD_SAFETY_ANALYSIS { const auto displayId = mCreationArgs.compositionDisplay->getDisplayId(); @@ -887,7 +923,7 @@ public: ui::ColorModes(), std::nullopt); - if (mFlinger.scheduler()) { + if (mFlinger.scheduler() && mRegisterDisplay) { mFlinger.scheduler()->registerDisplay(physicalId, display->holdRefreshRateSelector()); } @@ -906,11 +942,22 @@ public: sp<BBinder> mDisplayToken = sp<BBinder>::make(); DisplayDeviceCreationArgs mCreationArgs; DisplayModes mDisplayModes; + bool mRegisterDisplay = true; const std::optional<ui::DisplayConnectionType> mConnectionType; const std::optional<hal::HWDisplayId> mHwcDisplayId; }; private: + template <typename T> + static std::unique_ptr<T> makeMock(bool useNiceMock) { + return useNiceMock ? std::make_unique<testing::NiceMock<T>>() : std::make_unique<T>(); + } + + template <typename T> + static std::shared_ptr<T> makeSharedMock(bool useNiceMock) { + return useNiceMock ? std::make_shared<testing::NiceMock<T>>() : std::make_shared<T>(); + } + static constexpr VsyncId kVsyncId{123}; surfaceflinger::test::Factory mFactory; diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index 859f702fe7..c78148faa9 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -33,15 +33,12 @@ #include "FrontEnd/TransactionHandler.h" #include "TestableSurfaceFlinger.h" #include "TransactionState.h" -#include "mock/MockEventThread.h" -#include "mock/MockVsyncController.h" namespace android { using testing::_; using testing::Return; -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; using frontend::TransactionHandler; constexpr nsecs_t TRANSACTION_TIMEOUT = s2ns(5); @@ -52,7 +49,9 @@ public: ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - setupScheduler(); + mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>()); + mFlinger.setupMockScheduler(); + mFlinger.flinger()->addTransactionReadyFilters(); } ~TransactionApplicationTest() { @@ -61,38 +60,8 @@ public: ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); } - void setupScheduler() { - auto eventThread = std::make_unique<mock::EventThread>(); - auto sfEventThread = std::make_unique<mock::EventThread>(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*mVSyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - - mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>()); - mFlinger.setupScheduler(std::unique_ptr<mock::VsyncController>(mVsyncController), - std::unique_ptr<mock::VSyncTracker>(mVSyncTracker), - std::move(eventThread), std::move(sfEventThread)); - mFlinger.flinger()->addTransactionReadyFilters(); - } - TestableSurfaceFlinger mFlinger; - mock::VsyncController* mVsyncController = new mock::VsyncController(); - mock::VSyncTracker* mVSyncTracker = new mock::VSyncTracker(); - struct TransactionInfo { Vector<ComposerState> states; Vector<DisplayState> displays; diff --git a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp index 1173d1c876..764d19be0b 100644 --- a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp @@ -28,15 +28,13 @@ #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockComposer.h" -#include "mock/MockEventThread.h" -#include "mock/MockVsyncController.h" namespace android { using testing::_; using testing::Mock; using testing::Return; -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; + using PresentState = frametimeline::SurfaceFrame::PresentState; class TransactionFrameTracerTest : public testing::Test { @@ -45,7 +43,7 @@ public: const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - setupScheduler(); + mFlinger.setupMockScheduler(); mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>()); mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine)); } @@ -68,33 +66,6 @@ public: layer->commitTransaction(c); } - void setupScheduler() { - auto eventThread = std::make_unique<mock::EventThread>(); - auto sfEventThread = std::make_unique<mock::EventThread>(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique<mock::VsyncController>(); - auto vsyncTracker = std::make_unique<mock::VSyncTracker>(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread)); - } - TestableSurfaceFlinger mFlinger; renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine(); diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp index ae03db43a7..e2c64917dc 100644 --- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp @@ -28,15 +28,13 @@ #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockComposer.h" -#include "mock/MockEventThread.h" -#include "mock/MockVsyncController.h" namespace android { using testing::_; using testing::Mock; using testing::Return; -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; + using PresentState = frametimeline::SurfaceFrame::PresentState; class TransactionSurfaceFrameTest : public testing::Test { @@ -45,7 +43,7 @@ public: const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - setupScheduler(); + mFlinger.setupMockScheduler(); mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>()); mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine)); } @@ -67,33 +65,6 @@ public: layer->commitTransaction(c); } - void setupScheduler() { - auto eventThread = std::make_unique<mock::EventThread>(); - auto sfEventThread = std::make_unique<mock::EventThread>(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique<mock::VsyncController>(); - auto vsyncTracker = std::make_unique<mock::VSyncTracker>(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread)); - } - TestableSurfaceFlinger mFlinger; renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine(); diff --git a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp index da87f1db17..108151ec65 100644 --- a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp +++ b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp @@ -25,7 +25,6 @@ #include "TestableSurfaceFlinger.h" #include "TunnelModeEnabledReporter.h" #include "mock/DisplayHardware/MockComposer.h" -#include "mock/MockEventThread.h" namespace android { @@ -36,8 +35,6 @@ using testing::Return; using android::Hwc2::IComposer; using android::Hwc2::IComposerClient; -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; - constexpr int DEFAULT_SIDEBAND_STREAM = 51; struct TestableTunnelModeEnabledListener : public gui::BnTunnelModeEnabledListener { @@ -61,8 +58,6 @@ protected: static constexpr uint32_t HEIGHT = 100; static constexpr uint32_t LAYER_FLAGS = 0; - void setupScheduler(); - void setupComposer(uint32_t virtualDisplayCount); sp<Layer> createBufferStateLayer(LayerMetadata metadata); TestableSurfaceFlinger mFlinger; @@ -80,7 +75,7 @@ TunnelModeEnabledReporterTest::TunnelModeEnabledReporterTest() { ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - setupScheduler(); + mFlinger.setupMockScheduler(); mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>()); mFlinger.flinger()->mTunnelModeEnabledReporter = mTunnelModeEnabledReporter; mTunnelModeEnabledReporter->dispatchTunnelModeEnabled(false); @@ -100,33 +95,6 @@ sp<Layer> TunnelModeEnabledReporterTest::createBufferStateLayer(LayerMetadata me return sp<Layer>::make(args); } -void TunnelModeEnabledReporterTest::setupScheduler() { - auto eventThread = std::make_unique<mock::EventThread>(); - auto sfEventThread = std::make_unique<mock::EventThread>(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique<mock::VsyncController>(); - auto vsyncTracker = std::make_unique<mock::VSyncTracker>(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread)); -} - namespace { TEST_F(TunnelModeEnabledReporterTest, callsAddedListeners) { diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp index 47c2deef51..41866a1c50 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp @@ -54,7 +54,7 @@ public: void resetModel() final {} bool needsMoreSamples() const final { return false; } bool isVSyncInPhase(nsecs_t, Fps) const final { return false; } - void setDivisor(unsigned) final {} + void setRenderRate(Fps) final {} void dump(std::string&) const final {} private: @@ -92,7 +92,7 @@ public: void resetModel() final {} bool needsMoreSamples() const final { return false; } bool isVSyncInPhase(nsecs_t, Fps) const final { return false; } - void setDivisor(unsigned) final {} + void setRenderRate(Fps) final {} void dump(std::string&) const final {} private: @@ -109,7 +109,8 @@ struct VSyncDispatchRealtimeTest : testing::Test { class RepeatingCallbackReceiver { public: - RepeatingCallbackReceiver(VSyncDispatch& dispatch, nsecs_t workload, nsecs_t readyDuration) + RepeatingCallbackReceiver(std::shared_ptr<VSyncDispatch> dispatch, nsecs_t workload, + nsecs_t readyDuration) : mWorkload(workload), mReadyDuration(readyDuration), mCallback( @@ -166,9 +167,10 @@ private: }; TEST_F(VSyncDispatchRealtimeTest, triple_alarm) { - FixedRateIdealStubTracker tracker; - VSyncDispatchTimerQueue dispatch(std::make_unique<Timer>(), tracker, mDispatchGroupThreshold, - mVsyncMoveThreshold); + auto tracker = std::make_shared<FixedRateIdealStubTracker>(); + auto dispatch = + std::make_shared<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), tracker, + mDispatchGroupThreshold, mVsyncMoveThreshold); static size_t constexpr num_clients = 3; std::array<RepeatingCallbackReceiver, num_clients> @@ -195,14 +197,15 @@ TEST_F(VSyncDispatchRealtimeTest, triple_alarm) { // starts at 333hz, slides down to 43hz TEST_F(VSyncDispatchRealtimeTest, vascillating_vrr) { auto next_vsync_interval = toNs(3ms); - VRRStubTracker tracker(next_vsync_interval); - VSyncDispatchTimerQueue dispatch(std::make_unique<Timer>(), tracker, mDispatchGroupThreshold, - mVsyncMoveThreshold); + auto tracker = std::make_shared<VRRStubTracker>(next_vsync_interval); + auto dispatch = + std::make_shared<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), tracker, + mDispatchGroupThreshold, mVsyncMoveThreshold); RepeatingCallbackReceiver cb_receiver(dispatch, toNs(1ms), toNs(5ms)); auto const on_each_frame = [&](nsecs_t last_known) { - tracker.set_interval(next_vsync_interval += toNs(1ms), last_known); + tracker->set_interval(next_vsync_interval += toNs(1ms), last_known); }; std::thread eventThread([&] { cb_receiver.repeatedly_schedule(mIterations, on_each_frame); }); @@ -213,9 +216,10 @@ TEST_F(VSyncDispatchRealtimeTest, vascillating_vrr) { // starts at 333hz, jumps to 200hz at frame 10 TEST_F(VSyncDispatchRealtimeTest, fixed_jump) { - VRRStubTracker tracker(toNs(3ms)); - VSyncDispatchTimerQueue dispatch(std::make_unique<Timer>(), tracker, mDispatchGroupThreshold, - mVsyncMoveThreshold); + auto tracker = std::make_shared<VRRStubTracker>(toNs(3ms)); + auto dispatch = + std::make_shared<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), tracker, + mDispatchGroupThreshold, mVsyncMoveThreshold); RepeatingCallbackReceiver cb_receiver(dispatch, toNs(1ms), toNs(5ms)); @@ -223,7 +227,7 @@ TEST_F(VSyncDispatchRealtimeTest, fixed_jump) { auto constexpr jump_frame_at = 10u; auto const on_each_frame = [&](nsecs_t last_known) { if (jump_frame_counter++ == jump_frame_at) { - tracker.set_interval(toNs(5ms), last_known); + tracker->set_interval(toNs(5ms), last_known); } }; std::thread eventThread([&] { cb_receiver.repeatedly_schedule(mIterations, on_each_frame); }); diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp index 14a2860378..7af1da6a31 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp @@ -55,7 +55,7 @@ public: MOCK_METHOD0(resetModel, void()); MOCK_CONST_METHOD0(needsMoreSamples, bool()); MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps)); - MOCK_METHOD(void, setDivisor, (unsigned), (override)); + MOCK_METHOD(void, setRenderRate, (Fps), (override)); MOCK_CONST_METHOD1(dump, void(std::string&)); nsecs_t nextVSyncTime(nsecs_t timePoint) const { @@ -116,13 +116,14 @@ private: class CountingCallback { public: - CountingCallback(VSyncDispatch& dispatch) - : mDispatch(dispatch), - mToken(dispatch.registerCallback(std::bind(&CountingCallback::counter, this, - std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - "test")) {} - ~CountingCallback() { mDispatch.unregisterCallback(mToken); } + CountingCallback(std::shared_ptr<VSyncDispatch> dispatch) + : mDispatch(std::move(dispatch)), + mToken(mDispatch->registerCallback(std::bind(&CountingCallback::counter, this, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3), + "test")) {} + ~CountingCallback() { mDispatch->unregisterCallback(mToken); } operator VSyncDispatch::CallbackToken() const { return mToken; } @@ -132,7 +133,7 @@ public: mReadyTime.push_back(readyTime); } - VSyncDispatch& mDispatch; + std::shared_ptr<VSyncDispatch> mDispatch; VSyncDispatch::CallbackToken mToken; std::vector<nsecs_t> mCalls; std::vector<nsecs_t> mWakeupTime; @@ -141,12 +142,12 @@ public: class PausingCallback { public: - PausingCallback(VSyncDispatch& dispatch, std::chrono::milliseconds pauseAmount) - : mDispatch(dispatch), - mToken(dispatch.registerCallback(std::bind(&PausingCallback::pause, this, - std::placeholders::_1, - std::placeholders::_2), - "test")), + PausingCallback(std::shared_ptr<VSyncDispatch> dispatch, std::chrono::milliseconds pauseAmount) + : mDispatch(std::move(dispatch)), + mToken(mDispatch->registerCallback(std::bind(&PausingCallback::pause, this, + std::placeholders::_1, + std::placeholders::_2), + "test")), mRegistered(true), mPauseAmount(pauseAmount) {} ~PausingCallback() { unregister(); } @@ -181,12 +182,12 @@ public: void unregister() { if (mRegistered) { - mDispatch.unregisterCallback(mToken); + mDispatch->unregisterCallback(mToken); mRegistered = false; } } - VSyncDispatch& mDispatch; + std::shared_ptr<VSyncDispatch> mDispatch; VSyncDispatch::CallbackToken mToken; bool mRegistered = true; @@ -231,22 +232,26 @@ protected: static nsecs_t constexpr mDispatchGroupThreshold = 5; nsecs_t const mPeriod = 1000; nsecs_t const mVsyncMoveThreshold = 300; - NiceMock<MockVSyncTracker> mStubTracker{mPeriod}; - VSyncDispatchTimerQueue mDispatch{createTimeKeeper(), mStubTracker, mDispatchGroupThreshold, - mVsyncMoveThreshold}; + std::shared_ptr<NiceMock<MockVSyncTracker>> mStubTracker = + std::make_shared<NiceMock<MockVSyncTracker>>(mPeriod); + std::shared_ptr<VSyncDispatch> mDispatch = + std::make_shared<VSyncDispatchTimerQueue>(createTimeKeeper(), mStubTracker, + mDispatchGroupThreshold, mVsyncMoveThreshold); }; TEST_F(VSyncDispatchTimerQueueTest, unregistersSetAlarmOnDestruction) { EXPECT_CALL(mMockClock, alarmAt(_, 900)); EXPECT_CALL(mMockClock, alarmCancel()); { - VSyncDispatchTimerQueue mDispatch{createTimeKeeper(), mStubTracker, mDispatchGroupThreshold, - mVsyncMoveThreshold}; + std::shared_ptr<VSyncDispatch> mDispatch = + std::make_shared<VSyncDispatchTimerQueue>(createTimeKeeper(), mStubTracker, + mDispatchGroupThreshold, + mVsyncMoveThreshold); CountingCallback cb(mDispatch); - const auto result = mDispatch.schedule(cb, - {.workDuration = 100, - .readyDuration = 0, - .earliestVsync = 1000}); + const auto result = mDispatch->schedule(cb, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); } @@ -257,10 +262,10 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFuture) { EXPECT_CALL(mMockClock, alarmAt(_, 900)); CountingCallback cb(mDispatch); - const auto result = mDispatch.schedule(cb, - {.workDuration = 100, - .readyDuration = 0, - .earliestVsync = intended}); + const auto result = mDispatch->schedule(cb, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = intended}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); @@ -277,14 +282,15 @@ TEST_F(VSyncDispatchTimerQueueTest, updateAlarmSettingFuture) { EXPECT_CALL(mMockClock, alarmAt(_, 700)).InSequence(seq); CountingCallback cb(mDispatch); - auto result = mDispatch.schedule(cb, - {.workDuration = 100, - .readyDuration = 0, - .earliestVsync = intended}); + auto result = mDispatch->schedule(cb, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = intended}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); - result = mDispatch.update(cb, + result = + mDispatch->update(cb, {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(700, *result); @@ -302,17 +308,17 @@ TEST_F(VSyncDispatchTimerQueueTest, updateDoesntSchedule) { CountingCallback cb(mDispatch); const auto result = - mDispatch.update(cb, - {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended}); + mDispatch->update(cb, + {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended}); EXPECT_FALSE(result.has_value()); } TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFutureWithAdjustmentToTrueVsync) { - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000)).WillOnce(Return(1150)); + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)).WillOnce(Return(1150)); EXPECT_CALL(mMockClock, alarmAt(_, 1050)); CountingCallback cb(mDispatch); - mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); + mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); advanceToNextCallback(); ASSERT_THAT(cb.mCalls.size(), Eq(1)); @@ -323,15 +329,15 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingAdjustmentPast) { auto const now = 234; mMockClock.advanceBy(234); auto const workDuration = 10 * mPeriod; - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(now + workDuration)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(now + workDuration)) .WillOnce(Return(mPeriod * 11)); EXPECT_CALL(mMockClock, alarmAt(_, mPeriod)); CountingCallback cb(mDispatch); - const auto result = mDispatch.schedule(cb, - {.workDuration = workDuration, - .readyDuration = 0, - .earliestVsync = mPeriod}); + const auto result = mDispatch->schedule(cb, + {.workDuration = workDuration, + .readyDuration = 0, + .earliestVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod, *result); } @@ -341,12 +347,13 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancel) { EXPECT_CALL(mMockClock, alarmCancel()); CountingCallback cb(mDispatch); - const auto result = - mDispatch.schedule(cb, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); + const auto result = mDispatch->schedule(cb, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod - 100, *result); - EXPECT_EQ(mDispatch.cancel(cb), CancelResult::Cancelled); + EXPECT_EQ(mDispatch->cancel(cb), CancelResult::Cancelled); } TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLate) { @@ -354,13 +361,14 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLate) { EXPECT_CALL(mMockClock, alarmCancel()); CountingCallback cb(mDispatch); - const auto result = - mDispatch.schedule(cb, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); + const auto result = mDispatch->schedule(cb, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod - 100, *result); mMockClock.advanceBy(950); - EXPECT_EQ(mDispatch.cancel(cb), CancelResult::TooLate); + EXPECT_EQ(mDispatch->cancel(cb), CancelResult::TooLate); } TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLateWhenRunning) { @@ -368,15 +376,16 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLateWhenRunning) { EXPECT_CALL(mMockClock, alarmCancel()); PausingCallback cb(mDispatch, std::chrono::duration_cast<std::chrono::milliseconds>(1s)); - const auto result = - mDispatch.schedule(cb, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); + const auto result = mDispatch->schedule(cb, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod - 100, *result); std::thread pausingThread([&] { mMockClock.advanceToNextCallback(); }); EXPECT_TRUE(cb.waitForPause()); - EXPECT_EQ(mDispatch.cancel(cb), CancelResult::TooLate); + EXPECT_EQ(mDispatch->cancel(cb), CancelResult::TooLate); cb.unpause(); pausingThread.join(); } @@ -389,9 +398,10 @@ TEST_F(VSyncDispatchTimerQueueTest, unregisterSynchronizes) { PausingCallback cb(mDispatch, 50ms); cb.stashResource(resource); - const auto result = - mDispatch.schedule(cb, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); + const auto result = mDispatch->schedule(cb, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod - 100, *result); @@ -408,7 +418,7 @@ TEST_F(VSyncDispatchTimerQueueTest, unregisterSynchronizes) { } TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) { - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)) .Times(4) .WillOnce(Return(1055)) .WillOnce(Return(1063)) @@ -423,8 +433,8 @@ TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch.schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); - mDispatch.schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod}); + mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); + mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod}); advanceToNextCallback(); advanceToNextCallback(); @@ -436,7 +446,7 @@ TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) { } TEST_F(VSyncDispatchTimerQueueTest, noCloseCallbacksAfterPeriodChange) { - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_)) .Times(4) .WillOnce(Return(1000)) .WillOnce(Return(2000)) @@ -450,21 +460,21 @@ TEST_F(VSyncDispatchTimerQueueTest, noCloseCallbacksAfterPeriodChange) { CountingCallback cb(mDispatch); - mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 0}); + mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 0}); advanceToNextCallback(); ASSERT_THAT(cb.mCalls.size(), Eq(1)); EXPECT_THAT(cb.mCalls[0], Eq(1000)); - mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); ASSERT_THAT(cb.mCalls.size(), Eq(2)); EXPECT_THAT(cb.mCalls[1], Eq(2000)); - mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); advanceToNextCallback(); @@ -473,7 +483,7 @@ TEST_F(VSyncDispatchTimerQueueTest, noCloseCallbacksAfterPeriodChange) { } TEST_F(VSyncDispatchTimerQueueTest, rearmsFaroutTimeoutWhenCancellingCloseOne) { - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_)) .Times(4) .WillOnce(Return(10000)) .WillOnce(Return(1000)) @@ -488,10 +498,10 @@ TEST_F(VSyncDispatchTimerQueueTest, rearmsFaroutTimeoutWhenCancellingCloseOne) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch.schedule(cb0, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod * 10}); - mDispatch.schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod}); - mDispatch.cancel(cb1); + mDispatch->schedule(cb0, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod * 10}); + mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod}); + mDispatch->cancel(cb1); } TEST_F(VSyncDispatchTimerQueueTest, noUnnecessaryRearmsWhenRescheduling) { @@ -502,9 +512,9 @@ TEST_F(VSyncDispatchTimerQueueTest, noUnnecessaryRearmsWhenRescheduling) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb1, {.workDuration = 300, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 300, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); } @@ -517,9 +527,9 @@ TEST_F(VSyncDispatchTimerQueueTest, necessaryRearmsWhenModifying) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); } @@ -537,10 +547,10 @@ TEST_F(VSyncDispatchTimerQueueTest, modifyIntoGroup) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb1, - {.workDuration = closeOffset, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, + {.workDuration = closeOffset, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); ASSERT_THAT(cb0.mCalls.size(), Eq(1)); @@ -548,9 +558,11 @@ TEST_F(VSyncDispatchTimerQueueTest, modifyIntoGroup) { ASSERT_THAT(cb1.mCalls.size(), Eq(1)); EXPECT_THAT(cb1.mCalls[0], Eq(mPeriod)); - mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 2000}); - mDispatch.schedule(cb1, - {.workDuration = notCloseOffset, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch->schedule(cb1, + {.workDuration = notCloseOffset, + .readyDuration = 0, + .earliestVsync = 2000}); advanceToNextCallback(); ASSERT_THAT(cb1.mCalls.size(), Eq(2)); EXPECT_THAT(cb1.mCalls[1], Eq(2000)); @@ -570,32 +582,32 @@ TEST_F(VSyncDispatchTimerQueueTest, rearmsWhenEndingAndDoesntCancel) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch.schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); - EXPECT_EQ(mDispatch.cancel(cb0), CancelResult::Cancelled); + EXPECT_EQ(mDispatch->cancel(cb0), CancelResult::Cancelled); } TEST_F(VSyncDispatchTimerQueueTest, setAlarmCallsAtCorrectTimeWithChangingVsync) { - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_)) .Times(3) .WillOnce(Return(950)) .WillOnce(Return(1975)) .WillOnce(Return(2950)); CountingCallback cb(mDispatch); - mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 920}); + mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 920}); mMockClock.advanceBy(850); EXPECT_THAT(cb.mCalls.size(), Eq(1)); - mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1900}); + mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1900}); mMockClock.advanceBy(900); EXPECT_THAT(cb.mCalls.size(), Eq(1)); mMockClock.advanceBy(125); EXPECT_THAT(cb.mCalls.size(), Eq(2)); - mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2900}); + mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2900}); mMockClock.advanceBy(975); EXPECT_THAT(cb.mCalls.size(), Eq(3)); } @@ -606,48 +618,48 @@ TEST_F(VSyncDispatchTimerQueueTest, callbackReentrancy) { EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq); VSyncDispatch::CallbackToken tmp; - tmp = mDispatch.registerCallback( + tmp = mDispatch->registerCallback( [&](auto, auto, auto) { - mDispatch.schedule(tmp, - {.workDuration = 100, - .readyDuration = 0, - .earliestVsync = 2000}); + mDispatch->schedule(tmp, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = 2000}); }, "o.o"); - mDispatch.schedule(tmp, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(tmp, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); } TEST_F(VSyncDispatchTimerQueueTest, callbackReentrantWithPastWakeup) { VSyncDispatch::CallbackToken tmp; std::optional<nsecs_t> lastTarget; - tmp = mDispatch.registerCallback( + tmp = mDispatch->registerCallback( [&](auto timestamp, auto, auto) { auto result = - mDispatch.schedule(tmp, - {.workDuration = 400, - .readyDuration = 0, - .earliestVsync = timestamp - mVsyncMoveThreshold}); - EXPECT_TRUE(result.has_value()); - EXPECT_EQ(mPeriod + timestamp - 400, *result); - result = mDispatch.schedule(tmp, + mDispatch->schedule(tmp, {.workDuration = 400, .readyDuration = 0, - .earliestVsync = timestamp}); + .earliestVsync = timestamp - mVsyncMoveThreshold}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod + timestamp - 400, *result); - result = mDispatch.schedule(tmp, - {.workDuration = 400, - .readyDuration = 0, - .earliestVsync = timestamp + mVsyncMoveThreshold}); + result = mDispatch->schedule(tmp, + {.workDuration = 400, + .readyDuration = 0, + .earliestVsync = timestamp}); + EXPECT_TRUE(result.has_value()); + EXPECT_EQ(mPeriod + timestamp - 400, *result); + result = mDispatch->schedule(tmp, + {.workDuration = 400, + .readyDuration = 0, + .earliestVsync = timestamp + mVsyncMoveThreshold}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod + timestamp - 400, *result); lastTarget = timestamp; }, "oo"); - mDispatch.schedule(tmp, {.workDuration = 999, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(tmp, {.workDuration = 999, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); EXPECT_THAT(lastTarget, Eq(1000)); @@ -663,16 +675,16 @@ TEST_F(VSyncDispatchTimerQueueTest, modificationsAroundVsyncTime) { EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq); CountingCallback cb(mDispatch); - mDispatch.schedule(cb, {.workDuration = 0, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, {.workDuration = 0, .readyDuration = 0, .earliestVsync = 1000}); mMockClock.advanceBy(750); - mDispatch.schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); - mDispatch.schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 2000}); mMockClock.advanceBy(800); - mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); } TEST_F(VSyncDispatchTimerQueueTest, lateModifications) { @@ -685,12 +697,12 @@ TEST_F(VSyncDispatchTimerQueueTest, lateModifications) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch.schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb1, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); - mDispatch.schedule(cb0, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 2000}); - mDispatch.schedule(cb1, {.workDuration = 150, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch->schedule(cb1, {.workDuration = 150, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); advanceToNextCallback(); @@ -702,8 +714,8 @@ TEST_F(VSyncDispatchTimerQueueTest, doesntCancelPriorValidTimerForFutureMod) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch.schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 20000}); + mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 20000}); } TEST_F(VSyncDispatchTimerQueueTest, setsTimerAfterCancellation) { @@ -713,29 +725,30 @@ TEST_F(VSyncDispatchTimerQueueTest, setsTimerAfterCancellation) { EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq); CountingCallback cb0(mDispatch); - mDispatch.schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.cancel(cb0); - mDispatch.schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->cancel(cb0); + mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); } TEST_F(VSyncDispatchTimerQueueTest, makingUpIdsError) { VSyncDispatch::CallbackToken token(100); - EXPECT_FALSE(mDispatch - .schedule(token, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}) - .has_value()); - EXPECT_THAT(mDispatch.cancel(token), Eq(CancelResult::Error)); + EXPECT_FALSE( + mDispatch + ->schedule(token, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}) + .has_value()); + EXPECT_THAT(mDispatch->cancel(token), Eq(CancelResult::Error)); } TEST_F(VSyncDispatchTimerQueueTest, canMoveCallbackBackwardsInTime) { CountingCallback cb0(mDispatch); auto result = - mDispatch.schedule(cb0, - {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, + {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(500, *result); - result = mDispatch.schedule(cb0, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + result = mDispatch->schedule(cb0, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); } @@ -745,14 +758,14 @@ TEST_F(VSyncDispatchTimerQueueTest, doesNotMoveCallbackBackwardsAndSkipASchedule EXPECT_CALL(mMockClock, alarmAt(_, 500)); CountingCallback cb(mDispatch); auto result = - mDispatch.schedule(cb, - {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, + {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(500, *result); mMockClock.advanceBy(400); - result = mDispatch.schedule(cb, - {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000}); + result = mDispatch->schedule(cb, + {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1200, *result); advanceToNextCallback(); @@ -760,19 +773,19 @@ TEST_F(VSyncDispatchTimerQueueTest, doesNotMoveCallbackBackwardsAndSkipASchedule } TEST_F(VSyncDispatchTimerQueueTest, targetOffsetMovingBackALittleCanStillSchedule) { - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)) .Times(2) .WillOnce(Return(1000)) .WillOnce(Return(1002)); CountingCallback cb(mDispatch); auto result = - mDispatch.schedule(cb, - {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, + {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(500, *result); mMockClock.advanceBy(400); - result = mDispatch.schedule(cb, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + result = mDispatch->schedule(cb, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(602, *result); } @@ -780,13 +793,13 @@ TEST_F(VSyncDispatchTimerQueueTest, targetOffsetMovingBackALittleCanStillSchedul TEST_F(VSyncDispatchTimerQueueTest, canScheduleNegativeOffsetAgainstDifferentPeriods) { CountingCallback cb0(mDispatch); auto result = - mDispatch.schedule(cb0, - {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, + {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(500, *result); advanceToNextCallback(); - result = mDispatch.schedule(cb0, - {.workDuration = 1100, .readyDuration = 0, .earliestVsync = 2000}); + result = mDispatch->schedule(cb0, + {.workDuration = 1100, .readyDuration = 0, .earliestVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); } @@ -797,13 +810,13 @@ TEST_F(VSyncDispatchTimerQueueTest, canScheduleLargeNegativeOffset) { EXPECT_CALL(mMockClock, alarmAt(_, 1100)).InSequence(seq); CountingCallback cb0(mDispatch); auto result = - mDispatch.schedule(cb0, - {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, + {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(500, *result); advanceToNextCallback(); - result = mDispatch.schedule(cb0, - {.workDuration = 1900, .readyDuration = 0, .earliestVsync = 2000}); + result = mDispatch->schedule(cb0, + {.workDuration = 1900, .readyDuration = 0, .earliestVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1100, *result); } @@ -813,13 +826,13 @@ TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesNotAffectSchedulingState) CountingCallback cb(mDispatch); auto result = - mDispatch.schedule(cb, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); - result = mDispatch.schedule(cb, - {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000}); + result = mDispatch->schedule(cb, + {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); @@ -865,16 +878,16 @@ TEST_F(VSyncDispatchTimerQueueTest, skipsSchedulingIfTimerReschedulingIsImminent CountingCallback cb2(mDispatch); auto result = - mDispatch.schedule(cb1, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); mMockClock.setLag(100); mMockClock.advanceBy(620); - result = mDispatch.schedule(cb2, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); + result = mDispatch->schedule(cb2, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1900, *result); mMockClock.advanceBy(80); @@ -893,16 +906,16 @@ TEST_F(VSyncDispatchTimerQueueTest, skipsSchedulingIfTimerReschedulingIsImminent CountingCallback cb(mDispatch); auto result = - mDispatch.schedule(cb, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); mMockClock.setLag(100); mMockClock.advanceBy(620); - result = mDispatch.schedule(cb, - {.workDuration = 370, .readyDuration = 0, .earliestVsync = 2000}); + result = mDispatch->schedule(cb, + {.workDuration = 370, .readyDuration = 0, .earliestVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1630, *result); mMockClock.advanceBy(80); @@ -919,19 +932,19 @@ TEST_F(VSyncDispatchTimerQueueTest, skipsRearmingWhenNotNextScheduled) { CountingCallback cb2(mDispatch); auto result = - mDispatch.schedule(cb1, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); - result = mDispatch.schedule(cb2, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); + result = mDispatch->schedule(cb2, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1900, *result); mMockClock.setLag(100); mMockClock.advanceBy(620); - EXPECT_EQ(mDispatch.cancel(cb2), CancelResult::Cancelled); + EXPECT_EQ(mDispatch->cancel(cb2), CancelResult::Cancelled); mMockClock.advanceBy(80); @@ -948,19 +961,19 @@ TEST_F(VSyncDispatchTimerQueueTest, rearmsWhenCancelledAndIsNextScheduled) { CountingCallback cb2(mDispatch); auto result = - mDispatch.schedule(cb1, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); - result = mDispatch.schedule(cb2, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); + result = mDispatch->schedule(cb2, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1900, *result); mMockClock.setLag(100); mMockClock.advanceBy(620); - EXPECT_EQ(mDispatch.cancel(cb1), CancelResult::Cancelled); + EXPECT_EQ(mDispatch->cancel(cb1), CancelResult::Cancelled); EXPECT_THAT(cb1.mCalls.size(), Eq(0)); EXPECT_THAT(cb2.mCalls.size(), Eq(0)); @@ -975,21 +988,21 @@ TEST_F(VSyncDispatchTimerQueueTest, laggedTimerGroupsCallbacksWithinLag) { CountingCallback cb2(mDispatch); Sequence seq; - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)) .InSequence(seq) .WillOnce(Return(1000)); EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq); - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)) .InSequence(seq) .WillOnce(Return(1000)); auto result = - mDispatch.schedule(cb1, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); - result = mDispatch.schedule(cb2, - {.workDuration = 390, .readyDuration = 0, .earliestVsync = 1000}); + result = mDispatch->schedule(cb2, + {.workDuration = 390, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(610, *result); @@ -1011,10 +1024,10 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFutureWithReadyDuration) { EXPECT_CALL(mMockClock, alarmAt(_, 900)); CountingCallback cb(mDispatch); - const auto result = mDispatch.schedule(cb, - {.workDuration = 70, - .readyDuration = 30, - .earliestVsync = intended}); + const auto result = mDispatch->schedule(cb, + {.workDuration = 70, + .readyDuration = 30, + .earliestVsync = intended}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); advanceToNextCallback(); @@ -1033,8 +1046,8 @@ TEST_F(VSyncDispatchTimerQueueTest, updatesVsyncTimeForCloseWakeupTime) { CountingCallback cb(mDispatch); - mDispatch.schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); @@ -1052,7 +1065,8 @@ class VSyncDispatchTimerQueueEntryTest : public testing::Test { protected: nsecs_t const mPeriod = 1000; nsecs_t const mVsyncMoveThreshold = 200; - NiceMock<MockVSyncTracker> mStubTracker{mPeriod}; + std::shared_ptr<NiceMock<MockVSyncTracker>> mStubTracker = + std::make_shared<NiceMock<MockVSyncTracker>>(mPeriod); }; TEST_F(VSyncDispatchTimerQueueEntryTest, stateAfterInitialization) { @@ -1070,7 +1084,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, stateScheduling) { EXPECT_FALSE(entry.wakeupTime()); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); auto const wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); @@ -1084,7 +1098,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, stateSchedulingReallyLongWakeupLatency) auto const duration = 500; auto const now = 8750; - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(now + duration)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(now + duration)) .Times(1) .WillOnce(Return(10000)); VSyncDispatchTimerQueueEntry entry( @@ -1092,7 +1106,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, stateSchedulingReallyLongWakeupLatency) EXPECT_FALSE(entry.wakeupTime()); EXPECT_TRUE(entry.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 994}, - mStubTracker, now) + *mStubTracker.get(), now) .has_value()); auto const wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); @@ -1115,7 +1129,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, runCallback) { mVsyncMoveThreshold); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); auto const wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); @@ -1137,7 +1151,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, runCallback) { } TEST_F(VSyncDispatchTimerQueueEntryTest, updateCallback) { - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_)) .Times(2) .WillOnce(Return(1000)) .WillOnce(Return(1020)); @@ -1146,17 +1160,17 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, updateCallback) { "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); EXPECT_FALSE(entry.wakeupTime()); - entry.update(mStubTracker, 0); + entry.update(*mStubTracker.get(), 0); EXPECT_FALSE(entry.wakeupTime()); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); auto wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); EXPECT_THAT(wakeup, Eq(900)); - entry.update(mStubTracker, 0); + entry.update(*mStubTracker.get(), 0); wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); EXPECT_THAT(*wakeup, Eq(920)); @@ -1166,9 +1180,9 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, skipsUpdateIfJustScheduled) { VSyncDispatchTimerQueueEntry entry( "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); - entry.update(mStubTracker, 0); + entry.update(*mStubTracker.get(), 0); auto const wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); @@ -1179,24 +1193,24 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, willSnapToNextTargettableVSync) { VSyncDispatchTimerQueueEntry entry( "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); entry.executing(); // 1000 is executing // had 1000 not been executing, this could have been scheduled for time 800. EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); EXPECT_THAT(*entry.wakeupTime(), Eq(1800)); EXPECT_THAT(*entry.readyTime(), Eq(2000)); EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); EXPECT_THAT(*entry.wakeupTime(), Eq(1950)); EXPECT_THAT(*entry.readyTime(), Eq(2000)); EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 1001}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); EXPECT_THAT(*entry.wakeupTime(), Eq(1800)); EXPECT_THAT(*entry.readyTime(), Eq(2000)); @@ -1208,24 +1222,24 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); Sequence seq; - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(500)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500)) .InSequence(seq) .WillOnce(Return(1000)); - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(500)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500)) .InSequence(seq) .WillOnce(Return(1000)); - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold)) .InSequence(seq) .WillOnce(Return(2000)); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); entry.executing(); // 1000 is executing EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); } @@ -1233,16 +1247,16 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, reportsScheduledIfStillTime) { VSyncDispatchTimerQueueEntry entry( "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); EXPECT_TRUE(entry.schedule({.workDuration = 1200, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); } @@ -1255,7 +1269,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, storesPendingUpdatesUntilUpdate) { entry.addPendingWorkloadUpdate( {.workDuration = effectualOffset, .readyDuration = 0, .earliestVsync = 400}); EXPECT_TRUE(entry.hasPendingWorkloadUpdate()); - entry.update(mStubTracker, 0); + entry.update(*mStubTracker.get(), 0); EXPECT_FALSE(entry.hasPendingWorkloadUpdate()); EXPECT_THAT(*entry.wakeupTime(), Eq(mPeriod - effectualOffset)); } @@ -1276,7 +1290,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, runCallbackWithReadyDuration) { mVsyncMoveThreshold); EXPECT_TRUE(entry.schedule({.workDuration = 70, .readyDuration = 30, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); auto const wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index 3095e8aa9a..43d683d0fa 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -47,6 +47,8 @@ std::vector<nsecs_t> generateVsyncTimestamps(size_t count, nsecs_t period, nsecs return vsyncs; } +constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u); + struct VSyncPredictorTest : testing::Test { nsecs_t mNow = 0; nsecs_t mPeriod = 1000; @@ -55,7 +57,7 @@ struct VSyncPredictorTest : testing::Test { static constexpr size_t kOutlierTolerancePercent = 25; static constexpr nsecs_t mMaxRoundingError = 100; - VSyncPredictor tracker{mPeriod, kHistorySize, kMinimumSamplesForPrediction, + VSyncPredictor tracker{DEFAULT_DISPLAY_ID, mPeriod, kHistorySize, kMinimumSamplesForPrediction, kOutlierTolerancePercent}; }; @@ -376,7 +378,8 @@ TEST_F(VSyncPredictorTest, doesNotPredictBeforeTimePointWithHigherIntercept) { // See b/151146131 TEST_F(VSyncPredictorTest, hasEnoughPrecision) { - VSyncPredictor tracker{mPeriod, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent}; + VSyncPredictor tracker{DEFAULT_DISPLAY_ID, mPeriod, 20, kMinimumSamplesForPrediction, + kOutlierTolerancePercent}; std::vector<nsecs_t> const simulatedVsyncs{840873348817, 840890049444, 840906762675, 840923581635, 840940161584, 840956868096, 840973702473, 840990256277, 841007116851, @@ -468,7 +471,7 @@ TEST_F(VSyncPredictorTest, isVSyncInPhase) { const auto maxPeriods = 15; for (int divisor = 1; divisor < maxDivisor; divisor++) { for (int i = 0; i < maxPeriods; i++) { - const bool expectedInPhase = (i % divisor) == 0; + const bool expectedInPhase = ((kMinimumSamplesForPrediction - 1 + i) % divisor) == 0; EXPECT_THAT(expectedInPhase, tracker.isVSyncInPhase(mNow + i * mPeriod - bias, Fps::fromPeriodNsecs(divisor * mPeriod))) @@ -478,6 +481,28 @@ TEST_F(VSyncPredictorTest, isVSyncInPhase) { } } +TEST_F(VSyncPredictorTest, isVSyncInPhaseForDivisors) { + auto last = mNow; + for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); + mNow += mPeriod; + last = mNow; + tracker.addVsyncTimestamp(mNow); + } + + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod)); + + EXPECT_TRUE(tracker.isVSyncInPhase(mNow + 1 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 2))); + EXPECT_FALSE(tracker.isVSyncInPhase(mNow + 2 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 2))); + EXPECT_TRUE(tracker.isVSyncInPhase(mNow + 3 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 2))); + + EXPECT_FALSE(tracker.isVSyncInPhase(mNow + 5 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 4))); + EXPECT_TRUE(tracker.isVSyncInPhase(mNow + 3 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 4))); + EXPECT_FALSE(tracker.isVSyncInPhase(mNow + 4 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 4))); + EXPECT_FALSE(tracker.isVSyncInPhase(mNow + 6 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 4))); + EXPECT_TRUE(tracker.isVSyncInPhase(mNow + 7 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 4))); +} + TEST_F(VSyncPredictorTest, inconsistentVsyncValueIsFlushedEventually) { EXPECT_TRUE(tracker.addVsyncTimestamp(600)); EXPECT_TRUE(tracker.needsMoreSamples()); @@ -532,7 +557,7 @@ TEST_F(VSyncPredictorTest, robustToDuplicateTimestamps_60hzRealTraceData) { EXPECT_THAT(intercept, IsCloseTo(expectedIntercept, mMaxRoundingError)); } -TEST_F(VSyncPredictorTest, setDivisorIsRespected) { +TEST_F(VSyncPredictorTest, setRenderRateIsRespected) { auto last = mNow; for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); @@ -541,7 +566,7 @@ TEST_F(VSyncPredictorTest, setDivisorIsRespected) { tracker.addVsyncTimestamp(mNow); } - tracker.setDivisor(3); + tracker.setRenderRate(Fps::fromPeriodNsecs(3 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod)); @@ -552,6 +577,55 @@ TEST_F(VSyncPredictorTest, setDivisorIsRespected) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 7 * mPeriod)); } +TEST_F(VSyncPredictorTest, setRenderRateOfDivisorIsInPhase) { + auto last = mNow; + for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); + mNow += mPeriod; + last = mNow; + tracker.addVsyncTimestamp(mNow); + } + + const auto refreshRate = Fps::fromPeriodNsecs(mPeriod); + + tracker.setRenderRate(refreshRate / 4); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 3 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 7 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 11 * mPeriod)); + + tracker.setRenderRate(refreshRate / 2); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 3 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 5 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5 * mPeriod), Eq(mNow + 7 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 9 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 9 * mPeriod), Eq(mNow + 11 * mPeriod)); + + tracker.setRenderRate(refreshRate / 6); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 7 * mPeriod)); +} + +TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) { + auto last = mNow; + for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); + mNow += mPeriod; + last = mNow; + tracker.addVsyncTimestamp(mNow); + } + + tracker.setRenderRate(Fps::fromPeriodNsecs(3.5f * mPeriod)); + + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1100), Eq(mNow + 2 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 2100), Eq(mNow + 3 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3100), Eq(mNow + 4 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 4100), Eq(mNow + 5 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod)); +} + } // namespace android::scheduler // TODO(b/129481165): remove the #pragma below and fix conversion issues diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp index 1fb2709f8d..122192b036 100644 --- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp @@ -50,7 +50,7 @@ public: MOCK_METHOD0(resetModel, void()); MOCK_CONST_METHOD0(needsMoreSamples, bool()); MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps)); - MOCK_METHOD(void, setDivisor, (unsigned), (override)); + MOCK_METHOD(void, setRenderRate, (Fps), (override)); MOCK_CONST_METHOD1(dump, void(std::string&)); }; @@ -91,13 +91,15 @@ std::shared_ptr<android::FenceTime> generateSignalledFenceWithTime(nsecs_t time) return ft; } +constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u); + class VSyncReactorTest : public testing::Test { protected: VSyncReactorTest() : mMockTracker(std::make_shared<NiceMock<MockVSyncTracker>>()), mMockClock(std::make_shared<NiceMock<MockClock>>()), - mReactor(std::make_unique<ClockWrapper>(mMockClock), *mMockTracker, kPendingLimit, - false /* supportKernelIdleTimer */) { + mReactor(DEFAULT_DISPLAY_ID, std::make_unique<ClockWrapper>(mMockClock), *mMockTracker, + kPendingLimit, false /* supportKernelIdleTimer */) { ON_CALL(*mMockClock, now()).WillByDefault(Return(mFakeNow)); ON_CALL(*mMockTracker, currentPeriod()).WillByDefault(Return(period)); } @@ -192,7 +194,7 @@ TEST_F(VSyncReactorTest, ignoresProperlyAfterAPeriodConfirmation) { mReactor.setIgnorePresentFences(true); nsecs_t const newPeriod = 5000; - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(0, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); @@ -205,7 +207,7 @@ TEST_F(VSyncReactorTest, ignoresProperlyAfterAPeriodConfirmation) { TEST_F(VSyncReactorTest, setPeriodCalledOnceConfirmedChange) { nsecs_t const newPeriod = 5000; EXPECT_CALL(*mMockTracker, setPeriod(_)).Times(0); - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); bool periodFlushed = true; EXPECT_TRUE(mReactor.addHwVsyncTimestamp(10000, std::nullopt, &periodFlushed)); @@ -224,7 +226,7 @@ TEST_F(VSyncReactorTest, setPeriodCalledOnceConfirmedChange) { TEST_F(VSyncReactorTest, changingPeriodBackAbortsConfirmationProcess) { nsecs_t sampleTime = 0; nsecs_t const newPeriod = 5000; - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); bool periodFlushed = true; EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); @@ -232,7 +234,7 @@ TEST_F(VSyncReactorTest, changingPeriodBackAbortsConfirmationProcess) { EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); - mReactor.startPeriodTransition(period); + mReactor.startPeriodTransition(period, false); EXPECT_FALSE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); } @@ -242,13 +244,13 @@ TEST_F(VSyncReactorTest, changingToAThirdPeriodWillWaitForLastPeriod) { nsecs_t const secondPeriod = 5000; nsecs_t const thirdPeriod = 2000; - mReactor.startPeriodTransition(secondPeriod); + mReactor.startPeriodTransition(secondPeriod, false); bool periodFlushed = true; EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); - mReactor.startPeriodTransition(thirdPeriod); + mReactor.startPeriodTransition(thirdPeriod, false); EXPECT_TRUE( mReactor.addHwVsyncTimestamp(sampleTime += secondPeriod, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); @@ -289,14 +291,14 @@ TEST_F(VSyncReactorTest, reportedBadTimestampFromPredictorWillReactivateHwVSyncP TEST_F(VSyncReactorTest, presentFenceAdditionDoesNotInterruptConfirmationProcess) { nsecs_t const newPeriod = 5000; - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); EXPECT_TRUE(mReactor.addPresentFence(generateSignalledFenceWithTime(0))); } TEST_F(VSyncReactorTest, setPeriodCalledFirstTwoEventsNewPeriod) { nsecs_t const newPeriod = 5000; EXPECT_CALL(*mMockTracker, setPeriod(_)).Times(0); - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); bool periodFlushed = true; EXPECT_TRUE(mReactor.addHwVsyncTimestamp(5000, std::nullopt, &periodFlushed)); @@ -321,7 +323,7 @@ TEST_F(VSyncReactorTest, addResyncSamplePeriodChanges) { bool periodFlushed = false; nsecs_t const newPeriod = 4000; - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); auto time = 0; auto constexpr numTimestampSubmissions = 10; @@ -346,7 +348,7 @@ TEST_F(VSyncReactorTest, addHwVsyncTimestampDozePreempt) { bool periodFlushed = false; nsecs_t const newPeriod = 4000; - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); auto time = 0; // If the power mode is not DOZE or DOZE_SUSPEND, it is still collecting timestamps. @@ -363,7 +365,7 @@ TEST_F(VSyncReactorTest, addPresentFenceWhileAwaitingPeriodConfirmationRequestsH auto time = 0; bool periodFlushed = false; nsecs_t const newPeriod = 4000; - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); time += period; mReactor.addHwVsyncTimestamp(time, std::nullopt, &periodFlushed); @@ -379,7 +381,7 @@ TEST_F(VSyncReactorTest, hwVsyncIsRequestedForTracker) { auto time = 0; bool periodFlushed = false; nsecs_t const newPeriod = 4000; - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); static auto constexpr numSamplesWithNewPeriod = 4; Sequence seq; @@ -406,7 +408,7 @@ TEST_F(VSyncReactorTest, hwVsyncturnsOffOnConfirmationWhenTrackerDoesntRequest) auto time = 0; bool periodFlushed = false; nsecs_t const newPeriod = 4000; - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); Sequence seq; EXPECT_CALL(*mMockTracker, needsMoreSamples()) @@ -426,7 +428,7 @@ TEST_F(VSyncReactorTest, hwVsyncIsRequestedForTrackerMultiplePeriodChanges) { nsecs_t const newPeriod1 = 4000; nsecs_t const newPeriod2 = 7000; - mReactor.startPeriodTransition(newPeriod1); + mReactor.startPeriodTransition(newPeriod1, false); Sequence seq; EXPECT_CALL(*mMockTracker, needsMoreSamples()) @@ -445,7 +447,7 @@ TEST_F(VSyncReactorTest, hwVsyncIsRequestedForTrackerMultiplePeriodChanges) { EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod1, std::nullopt, &periodFlushed)); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod1, std::nullopt, &periodFlushed)); - mReactor.startPeriodTransition(newPeriod2); + mReactor.startPeriodTransition(newPeriod2, false); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod1, std::nullopt, &periodFlushed)); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod2, std::nullopt, &periodFlushed)); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod2, std::nullopt, &periodFlushed)); @@ -458,7 +460,7 @@ TEST_F(VSyncReactorTest, periodChangeWithGivenVsyncPeriod) { mReactor.setIgnorePresentFences(true); nsecs_t const newPeriod = 5000; - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(0, 0, &periodFlushed)); EXPECT_FALSE(periodFlushed); @@ -472,8 +474,9 @@ TEST_F(VSyncReactorTest, periodChangeWithGivenVsyncPeriod) { TEST_F(VSyncReactorTest, periodIsMeasuredIfIgnoringComposer) { // Create a reactor which supports the kernel idle timer - auto idleReactor = VSyncReactor(std::make_unique<ClockWrapper>(mMockClock), *mMockTracker, - kPendingLimit, true /* supportKernelIdleTimer */); + auto idleReactor = + VSyncReactor(DEFAULT_DISPLAY_ID, std::make_unique<ClockWrapper>(mMockClock), + *mMockTracker, kPendingLimit, true /* supportKernelIdleTimer */); bool periodFlushed = true; EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(4); @@ -481,7 +484,7 @@ TEST_F(VSyncReactorTest, periodIsMeasuredIfIgnoringComposer) { // First, set the same period, which should only be confirmed when we receive two // matching callbacks - idleReactor.startPeriodTransition(10000); + idleReactor.startPeriodTransition(10000, false); EXPECT_TRUE(idleReactor.addHwVsyncTimestamp(0, 0, &periodFlushed)); EXPECT_FALSE(periodFlushed); // Correct period but incorrect timestamp delta @@ -494,7 +497,7 @@ TEST_F(VSyncReactorTest, periodIsMeasuredIfIgnoringComposer) { // Then, set a new period, which should be confirmed as soon as we receive a callback // reporting the new period nsecs_t const newPeriod = 5000; - idleReactor.startPeriodTransition(newPeriod); + idleReactor.startPeriodTransition(newPeriod, false); // Incorrect timestamp delta and period EXPECT_TRUE(idleReactor.addHwVsyncTimestamp(20000, 10000, &periodFlushed)); EXPECT_FALSE(periodFlushed); diff --git a/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp b/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp new file mode 100644 index 0000000000..4010fa6a5b --- /dev/null +++ b/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp @@ -0,0 +1,270 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "LibSurfaceFlingerUnittests" + +#include <ftl/fake_guard.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <log/log.h> + +#include <scheduler/Fps.h> +#include "Scheduler/VsyncSchedule.h" +#include "ThreadContext.h" +#include "mock/MockSchedulerCallback.h" +#include "mock/MockVSyncDispatch.h" +#include "mock/MockVSyncTracker.h" +#include "mock/MockVsyncController.h" + +using testing::_; + +namespace android { + +constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u); + +class VsyncScheduleTest : public testing::Test { +protected: + VsyncScheduleTest(); + ~VsyncScheduleTest() override; + + scheduler::mock::SchedulerCallback mCallback; + const std::unique_ptr<scheduler::VsyncSchedule> mVsyncSchedule = + std::unique_ptr<scheduler::VsyncSchedule>( + new scheduler::VsyncSchedule(DEFAULT_DISPLAY_ID, + std::make_shared<mock::VSyncTracker>(), + std::make_shared<mock::VSyncDispatch>(), + std::make_unique<mock::VsyncController>())); + + mock::VsyncController& getController() { + return *static_cast<mock::VsyncController*>(&mVsyncSchedule->getController()); + } +}; + +VsyncScheduleTest::VsyncScheduleTest() { + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); +} + +VsyncScheduleTest::~VsyncScheduleTest() { + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); +} + +namespace { + +using namespace testing; + +TEST_F(VsyncScheduleTest, InitiallyDisallowed) { + ASSERT_FALSE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */)); +} + +TEST_F(VsyncScheduleTest, EnableDoesNothingWhenDisallowed) { + EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0); + + mVsyncSchedule->enableHardwareVsync(mCallback); +} + +TEST_F(VsyncScheduleTest, DisableDoesNothingWhenDisallowed) { + EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0); + + mVsyncSchedule->disableHardwareVsync(mCallback, false /* disallow */); +} + +TEST_F(VsyncScheduleTest, DisableDoesNothingWhenDisallowed2) { + EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0); + + mVsyncSchedule->disableHardwareVsync(mCallback, true /* disallow */); +} + +TEST_F(VsyncScheduleTest, MakeAllowed) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); +} + +TEST_F(VsyncScheduleTest, DisableDoesNothingWhenDisabled) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0); + + mVsyncSchedule->disableHardwareVsync(mCallback, false /* disallow */); +} + +TEST_F(VsyncScheduleTest, DisableDoesNothingWhenDisabled2) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0); + + mVsyncSchedule->disableHardwareVsync(mCallback, true /* disallow */); +} + +TEST_F(VsyncScheduleTest, EnableWorksWhenDisabled) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, true)); + + mVsyncSchedule->enableHardwareVsync(mCallback); +} + +TEST_F(VsyncScheduleTest, EnableWorksOnce) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, true)); + + mVsyncSchedule->enableHardwareVsync(mCallback); + + EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0); + mVsyncSchedule->enableHardwareVsync(mCallback); +} + +TEST_F(VsyncScheduleTest, AllowedIsSticky) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */)); +} + +TEST_F(VsyncScheduleTest, EnableDisable) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, true)); + + mVsyncSchedule->enableHardwareVsync(mCallback); + + EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, false)); + mVsyncSchedule->disableHardwareVsync(mCallback, false /* disallow */); +} + +TEST_F(VsyncScheduleTest, EnableDisable2) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, true)); + + mVsyncSchedule->enableHardwareVsync(mCallback); + + EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, false)); + mVsyncSchedule->disableHardwareVsync(mCallback, true /* disallow */); +} + +TEST_F(VsyncScheduleTest, StartPeriodTransition) { + // Note: startPeriodTransition is only called when hardware vsyncs are + // allowed. + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + + const Period period = (60_Hz).getPeriod(); + + EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, true)); + EXPECT_CALL(getController(), startPeriodTransition(period.ns(), false)); + + mVsyncSchedule->startPeriodTransition(mCallback, period, false); +} + +TEST_F(VsyncScheduleTest, StartPeriodTransitionAlreadyEnabled) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + mVsyncSchedule->enableHardwareVsync(mCallback); + + const Period period = (60_Hz).getPeriod(); + + EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0); + EXPECT_CALL(getController(), startPeriodTransition(period.ns(), false)); + + mVsyncSchedule->startPeriodTransition(mCallback, period, false); +} + +TEST_F(VsyncScheduleTest, StartPeriodTransitionForce) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + + const Period period = (60_Hz).getPeriod(); + + EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, true)); + EXPECT_CALL(getController(), startPeriodTransition(period.ns(), true)); + + mVsyncSchedule->startPeriodTransition(mCallback, period, true); +} + +TEST_F(VsyncScheduleTest, AddResyncSampleDisallowed) { + const Period period = (60_Hz).getPeriod(); + const auto timestamp = TimePoint::now(); + + EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0); + EXPECT_CALL(getController(), addHwVsyncTimestamp(_, _, _)).Times(0); + + mVsyncSchedule->addResyncSample(mCallback, timestamp, period); +} + +TEST_F(VsyncScheduleTest, AddResyncSampleDisabled) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + const Period period = (60_Hz).getPeriod(); + const auto timestamp = TimePoint::now(); + + EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0); + EXPECT_CALL(getController(), addHwVsyncTimestamp(_, _, _)).Times(0); + + mVsyncSchedule->addResyncSample(mCallback, timestamp, period); +} + +TEST_F(VsyncScheduleTest, AddResyncSampleReturnsTrue) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + mVsyncSchedule->enableHardwareVsync(mCallback); + + const Period period = (60_Hz).getPeriod(); + const auto timestamp = TimePoint::now(); + + EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0); + EXPECT_CALL(getController(), + addHwVsyncTimestamp(timestamp.ns(), std::optional<nsecs_t>(period.ns()), _)) + .WillOnce(Return(true)); + + mVsyncSchedule->addResyncSample(mCallback, timestamp, period); +} + +TEST_F(VsyncScheduleTest, AddResyncSampleReturnsFalse) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + mVsyncSchedule->enableHardwareVsync(mCallback); + + const Period period = (60_Hz).getPeriod(); + const auto timestamp = TimePoint::now(); + + EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, false)); + EXPECT_CALL(getController(), + addHwVsyncTimestamp(timestamp.ns(), std::optional<nsecs_t>(period.ns()), _)) + .WillOnce(Return(false)); + + mVsyncSchedule->addResyncSample(mCallback, timestamp, period); +} + +TEST_F(VsyncScheduleTest, PendingState) FTL_FAKE_GUARD(kMainThreadContext) { + ASSERT_FALSE(mVsyncSchedule->getPendingHardwareVsyncState()); + mVsyncSchedule->setPendingHardwareVsyncState(true); + ASSERT_TRUE(mVsyncSchedule->getPendingHardwareVsyncState()); + + mVsyncSchedule->setPendingHardwareVsyncState(false); + ASSERT_FALSE(mVsyncSchedule->getPendingHardwareVsyncState()); +} + +TEST_F(VsyncScheduleTest, DisableDoesNotMakeAllowed) { + ASSERT_FALSE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */)); + mVsyncSchedule->disableHardwareVsync(mCallback, false /* disallow */); + ASSERT_FALSE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */)); +} + +TEST_F(VsyncScheduleTest, DisallowMakesNotAllowed) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + mVsyncSchedule->disableHardwareVsync(mCallback, true /* disallow */); + ASSERT_FALSE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */)); +} + +TEST_F(VsyncScheduleTest, StillAllowedAfterDisable) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + mVsyncSchedule->disableHardwareVsync(mCallback, false /* disallow */); + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */)); +} + +} // namespace +} // namespace android diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h index 5e29fe7539..5dc3490eb8 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h @@ -148,8 +148,9 @@ public: MOCK_METHOD1(getHdrConversionCapabilities, Error(std::vector< aidl::android::hardware::graphics::common::HdrConversionCapability>*)); - MOCK_METHOD1(setHdrConversionStrategy, - Error(aidl::android::hardware::graphics::common::HdrConversionStrategy)); + MOCK_METHOD2(setHdrConversionStrategy, + Error(aidl::android::hardware::graphics::common::HdrConversionStrategy, + aidl::android::hardware::graphics::common::Hdr*)); MOCK_METHOD2(getSupportedContentTypes, V2_4::Error(Display, std::vector<IComposerClient::ContentType>*)); MOCK_METHOD2(setContentType, V2_4::Error(Display, IComposerClient::ContentType)); @@ -174,6 +175,7 @@ public: Error(aidl::android::hardware::graphics::composer3::OverlayProperties*)); MOCK_METHOD1(onHotplugConnect, void(Display)); MOCK_METHOD1(onHotplugDisconnect, void(Display)); + MOCK_METHOD(Error, setRefreshRateChangedCallbackDebugEnabled, (Display, bool)); }; } // namespace Hwc2::mock diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h index f8567bd636..8d57049219 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h +++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h @@ -29,27 +29,28 @@ public: EventThread(); ~EventThread() override; - MOCK_CONST_METHOD2(createEventConnection, - sp<EventThreadConnection>(ResyncCallback, EventRegistrationFlags)); - MOCK_METHOD0(onScreenReleased, void()); - MOCK_METHOD0(onScreenAcquired, void()); - MOCK_METHOD2(onHotplugReceived, void(PhysicalDisplayId, bool)); - MOCK_METHOD1(onModeChanged, void(const scheduler::FrameRateMode &)); - MOCK_METHOD2(onFrameRateOverridesChanged, - void(PhysicalDisplayId, std::vector<FrameRateOverride>)); - MOCK_CONST_METHOD1(dump, void(std::string&)); - MOCK_METHOD2(setDuration, - void(std::chrono::nanoseconds workDuration, - std::chrono::nanoseconds readyDuration)); - MOCK_METHOD1(registerDisplayEventConnection, - status_t(const sp<android::EventThreadConnection> &)); - MOCK_METHOD2(setVsyncRate, void(uint32_t, const sp<android::EventThreadConnection> &)); - MOCK_METHOD1(requestNextVsync, void(const sp<android::EventThreadConnection> &)); + MOCK_METHOD(sp<EventThreadConnection>, createEventConnection, + (ResyncCallback, EventRegistrationFlags), (const, override)); + MOCK_METHOD(void, enableSyntheticVsync, (bool), (override)); + MOCK_METHOD(void, onHotplugReceived, (PhysicalDisplayId, bool), (override)); + MOCK_METHOD(void, onModeChanged, (const scheduler::FrameRateMode&), (override)); + MOCK_METHOD(void, onFrameRateOverridesChanged, + (PhysicalDisplayId, std::vector<FrameRateOverride>), (override)); + MOCK_METHOD(void, dump, (std::string&), (const, override)); + MOCK_METHOD(void, setDuration, + (std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration), + (override)); + MOCK_METHOD(status_t, registerDisplayEventConnection, + (const sp<android::EventThreadConnection>&), (override)); + MOCK_METHOD(void, setVsyncRate, (uint32_t, const sp<android::EventThreadConnection>&), + (override)); + MOCK_METHOD(void, requestNextVsync, (const sp<android::EventThreadConnection>&), (override)); MOCK_METHOD(VsyncEventData, getLatestVsyncEventData, - (const sp<android::EventThreadConnection> &), (const)); - MOCK_METHOD1(requestLatestConfig, void(const sp<android::EventThreadConnection> &)); - MOCK_METHOD1(pauseVsyncCallback, void(bool)); - MOCK_METHOD0(getEventThreadConnectionCount, size_t()); + (const sp<android::EventThreadConnection>&), (const, override)); + MOCK_METHOD(void, requestLatestConfig, (const sp<android::EventThreadConnection>&)); + MOCK_METHOD(void, pauseVsyncCallback, (bool)); + MOCK_METHOD(size_t, getEventThreadConnectionCount, (), (override)); + MOCK_METHOD(void, onNewVsyncSchedule, (std::shared_ptr<scheduler::VsyncSchedule>), (override)); }; } // namespace android::mock diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h index 7d4b159e5e..a8eca2192f 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h +++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h @@ -18,19 +18,19 @@ #include <gmock/gmock.h> -#include "Scheduler/Scheduler.h" +#include "Scheduler/ISchedulerCallback.h" namespace android::scheduler::mock { struct SchedulerCallback final : ISchedulerCallback { - MOCK_METHOD(void, setVsyncEnabled, (bool), (override)); + MOCK_METHOD(void, setVsyncEnabled, (PhysicalDisplayId, bool), (override)); MOCK_METHOD(void, requestDisplayModes, (std::vector<display::DisplayModeRequest>), (override)); MOCK_METHOD(void, kernelTimerChanged, (bool), (override)); MOCK_METHOD(void, triggerOnFrameRateOverridesChanged, (), (override)); }; struct NoOpSchedulerCallback final : ISchedulerCallback { - void setVsyncEnabled(bool) override {} + void setVsyncEnabled(PhysicalDisplayId, bool) override {} void requestDisplayModes(std::vector<display::DisplayModeRequest>) override {} void kernelTimerChanged(bool) override {} void triggerOnFrameRateOverridesChanged() override {} diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h index 6893154259..dcf25e18a8 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h +++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h @@ -34,7 +34,7 @@ public: MOCK_METHOD0(resetModel, void()); MOCK_CONST_METHOD0(needsMoreSamples, bool()); MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps)); - MOCK_METHOD(void, setDivisor, (unsigned), (override)); + MOCK_METHOD(void, setRenderRate, (Fps), (override)); MOCK_CONST_METHOD1(dump, void(std::string&)); }; diff --git a/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h b/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h index 4ef91dacb2..69ec60acd4 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h +++ b/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h @@ -28,12 +28,12 @@ public: ~VsyncController() override; MOCK_METHOD(bool, addPresentFence, (std::shared_ptr<FenceTime>), (override)); - MOCK_METHOD3(addHwVsyncTimestamp, bool(nsecs_t, std::optional<nsecs_t>, bool*)); - MOCK_METHOD1(startPeriodTransition, void(nsecs_t)); - MOCK_METHOD1(setIgnorePresentFences, void(bool)); + MOCK_METHOD(bool, addHwVsyncTimestamp, (nsecs_t, std::optional<nsecs_t>, bool*), (override)); + MOCK_METHOD(void, startPeriodTransition, (nsecs_t, bool), (override)); + MOCK_METHOD(void, setIgnorePresentFences, (bool), (override)); MOCK_METHOD(void, setDisplayPowerMode, (hal::PowerMode), (override)); - MOCK_CONST_METHOD1(dump, void(std::string&)); + MOCK_METHOD(void, dump, (std::string&), (const, override)); }; } // namespace android::mock diff --git a/vulkan/vkjson/Android.bp b/vulkan/vkjson/Android.bp index b6d3a0b45f..b544245a7a 100644 --- a/vulkan/vkjson/Android.bp +++ b/vulkan/vkjson/Android.bp @@ -25,10 +25,8 @@ cc_library_shared { ".", ], shared_libs: [ - "libvulkan", - ], - whole_static_libs: [ "libjsoncpp", + "libvulkan", ], export_shared_lib_headers: [ "libvulkan", |