diff options
365 files changed, 16474 insertions, 7468 deletions
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc index 3e6d2e01f8..a3e29a81f1 100644 --- a/cmds/atrace/atrace.rc +++ b/cmds/atrace/atrace.rc @@ -317,7 +317,7 @@ on late-init && property:ro.boot.fastboot.boottrace= # Only create the tracing instance if persist.mm_events.enabled # Attempting to remove the tracing instance after it has been created # will likely fail with EBUSY as it would be in use by traced_probes. -on post-fs-data && property:persist.mm_events.enabled=true +on mm_events_property_available && property:persist.mm_events.enabled=true # Create MM Events Tracing Instance for Kmem Activity Trigger mkdir /sys/kernel/debug/tracing/instances/mm_events 0755 system system mkdir /sys/kernel/tracing/instances/mm_events 0755 system system @@ -402,6 +402,9 @@ 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 +on property:ro.persistent_properties.ready=true + trigger mm_events_property_available + # Handle hyp tracing instance on late-init && property:ro.boot.hypervisor.vm.supported=1 diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp index 23f185e305..341fabb32a 100644 --- a/cmds/dumpstate/Android.bp +++ b/cmds/dumpstate/Android.bp @@ -114,6 +114,7 @@ cc_defaults { "libincidentcompanion", "libdumpsys", "libserviceutils", + "android.tracing.flags_c_lib", ], } diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp index befb5d4b5b..5f109fb224 100644 --- a/cmds/dumpstate/dumpstate.cpp +++ b/cmds/dumpstate/dumpstate.cpp @@ -17,49 +17,9 @@ #define LOG_TAG "dumpstate" #define ATRACE_TAG ATRACE_TAG_ALWAYS -#include <dirent.h> -#include <errno.h> -#include <fcntl.h> -#include <inttypes.h> -#include <libgen.h> -#include <limits.h> -#include <math.h> -#include <poll.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/mount.h> -#include <sys/poll.h> -#include <sys/prctl.h> -#include <sys/resource.h> -#include <sys/stat.h> -#include <sys/time.h> -#include <sys/wait.h> -#include <signal.h> -#include <stdarg.h> -#include <string.h> -#include <sys/capability.h> -#include <sys/inotify.h> -#include <sys/klog.h> -#include <time.h> -#include <unistd.h> - -#include <chrono> -#include <cmath> -#include <fstream> -#include <functional> -#include <future> -#include <memory> -#include <numeric> -#include <regex> -#include <set> -#include <string> -#include <utility> -#include <vector> +#include "dumpstate.h" #include <aidl/android/hardware/dumpstate/IDumpstateDevice.h> -#include <android_app_admin_flags.h> #include <android-base/file.h> #include <android-base/properties.h> #include <android-base/scopeguard.h> @@ -74,6 +34,8 @@ #include <android/hardware/dumpstate/1.1/types.h> #include <android/hidl/manager/1.0/IServiceManager.h> #include <android/os/IIncidentCompanion.h> +#include <android_app_admin_flags.h> +#include <android_tracing.h> #include <binder/IServiceManager.h> #include <cutils/multiuser.h> #include <cutils/native_handle.h> @@ -81,21 +43,60 @@ #include <cutils/sockets.h> #include <cutils/trace.h> #include <debuggerd/client.h> +#include <dirent.h> #include <dumpsys.h> #include <dumputils/dump_utils.h> +#include <errno.h> +#include <fcntl.h> #include <hardware_legacy/power.h> #include <hidl/ServiceManagement.h> +#include <inttypes.h> +#include <libgen.h> +#include <limits.h> #include <log/log.h> #include <log/log_read.h> +#include <math.h> #include <openssl/sha.h> +#include <poll.h> #include <private/android_filesystem_config.h> #include <private/android_logger.h> #include <serviceutils/PriorityDumper.h> +#include <signal.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/capability.h> +#include <sys/inotify.h> +#include <sys/klog.h> +#include <sys/mount.h> +#include <sys/poll.h> +#include <sys/prctl.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> #include <utils/StrongPointer.h> #include <vintf/VintfObject.h> + +#include <chrono> +#include <cmath> +#include <fstream> +#include <functional> +#include <future> +#include <memory> +#include <numeric> +#include <regex> +#include <set> +#include <string> +#include <utility> +#include <vector> + #include "DumpstateInternal.h" #include "DumpstateService.h" -#include "dumpstate.h" namespace dumpstate_hal_hidl_1_0 = android::hardware::dumpstate::V1_0; namespace dumpstate_hal_hidl = android::hardware::dumpstate::V1_1; @@ -247,7 +248,7 @@ static const std::string DUMP_NETSTATS_PROTO_TASK = "DUMP NETSTATS PROTO"; static const std::string DUMP_HALS_TASK = "DUMP HALS"; static const std::string DUMP_BOARD_TASK = "dumpstate_board()"; static const std::string DUMP_CHECKINS_TASK = "DUMP CHECKINS"; -static const std::string POST_PROCESS_UI_TRACES_TASK = "POST-PROCESS UI TRACES"; +static const std::string SERIALIZE_PERFETTO_TRACE_TASK = "SERIALIZE PERFETTO TRACE"; namespace android { namespace os { @@ -1087,11 +1088,11 @@ static void DumpNetstatsProto() { static void MaybeAddSystemTraceToZip() { // This function copies into the .zip the system trace that was snapshotted - // by the early call to MaybeSnapshotSystemTrace(), if any background + // by the early call to MaybeSnapshotSystemTraceAsync(), if any background // tracing was happening. bool system_trace_exists = access(SYSTEM_TRACE_SNAPSHOT, F_OK) == 0; if (!system_trace_exists) { - // No background trace was happening at the time MaybeSnapshotSystemTrace() was invoked. + // No background trace was happening at the time MaybeSnapshotSystemTraceAsync() was invoked if (!PropertiesHelper::IsUserBuild()) { MYLOGI( "No system traces found. Check for previously uploaded traces by looking for " @@ -1563,6 +1564,13 @@ static void DumpstateLimitedOnly() { RunDumpsys("DROPBOX SYSTEM SERVER CRASHES", {"dropbox", "-p", "system_server_crash"}); RunDumpsys("DROPBOX SYSTEM APP CRASHES", {"dropbox", "-p", "system_app_crash"}); + + printf("========================================================\n"); + printf("== ANR Traces\n"); + printf("========================================================\n"); + + AddAnrTraceFiles(); + printf("========================================================\n"); printf("== Final progress (pid %d): %d/%d (estimated %d)\n", ds.pid_, ds.progress_->Get(), ds.progress_->GetMax(), ds.progress_->GetInitialMax()); @@ -1646,7 +1654,7 @@ Dumpstate::RunStatus Dumpstate::dumpstate() { // Enqueue slow functions into the thread pool, if the parallel run is enabled. std::future<std::string> dump_hals, dump_incident_report, dump_board, dump_checkins, - dump_netstats_report, post_process_ui_traces; + dump_netstats_report; if (ds.dump_pool_) { // Pool was shutdown in DumpstateDefaultAfterCritical method in order to // drop root user. Restarts it. @@ -3084,8 +3092,9 @@ void Dumpstate::Cancel() { } void Dumpstate::PreDumpUiData() { - MaybeSnapshotSystemTrace(); + auto snapshot_system_trace = MaybeSnapshotSystemTraceAsync(); MaybeSnapshotUiTraces(); + MaybeWaitForSnapshotSystemTrace(std::move(snapshot_system_trace)); } /* @@ -3271,13 +3280,15 @@ Dumpstate::RunStatus Dumpstate::RunInternal(int32_t calling_uid, // duration is logged into MYLOG instead. PrintHeader(); + std::future<std::string> snapshot_system_trace; + bool is_dumpstate_restricted = options_->telephony_only || options_->wifi_only || options_->limited_only; if (!is_dumpstate_restricted) { // Snapshot the system trace now (if running) to avoid that dumpstate's // own activity pushes out interesting data from the trace ring buffer. // The trace file is added to the zip by MaybeAddSystemTraceToZip(). - MaybeSnapshotSystemTrace(); + snapshot_system_trace = MaybeSnapshotSystemTraceAsync(); // Invoke critical dumpsys to preserve system state, before doing anything else. RunDumpsysCritical(); @@ -3288,6 +3299,7 @@ Dumpstate::RunStatus Dumpstate::RunInternal(int32_t calling_uid, } MaybeTakeEarlyScreenshot(); + MaybeWaitForSnapshotSystemTrace(std::move(snapshot_system_trace)); onUiIntensiveBugreportDumpsFinished(calling_uid); MaybeCheckUserConsent(calling_uid, calling_package); if (options_->telephony_only) { @@ -3384,31 +3396,59 @@ void Dumpstate::MaybeTakeEarlyScreenshot() { TakeScreenshot(); } -void Dumpstate::MaybeSnapshotSystemTrace() { +std::future<std::string> Dumpstate::MaybeSnapshotSystemTraceAsync() { // When capturing traces via bugreport handler (BH), this function will be invoked twice: // 1) When BH invokes IDumpstate::PreDumpUiData() // 2) When BH invokes IDumpstate::startBugreport(flags = BUGREPORT_USE_PREDUMPED_UI_DATA) // In this case we don't want to re-invoke perfetto in step 2. // In all other standard invocation states, this function is invoked once // without the flag BUGREPORT_USE_PREDUMPED_UI_DATA. + // This function must run asynchronously to avoid delaying MaybeTakeEarlyScreenshot() in the + // standard invocation states (b/316110955). if (options_->use_predumped_ui_data) { - return; + return {}; + } + + // Create temporary file for the command's output + std::string outPath = ds.bugreport_internal_dir_ + "/tmp_serialize_perfetto_trace"; + auto outFd = android::base::unique_fd(TEMP_FAILURE_RETRY( + open(outPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))); + if (outFd < 0) { + MYLOGE("Could not open %s to serialize perfetto trace.\n", outPath.c_str()); + return {}; } // If a stale file exists already, remove it. unlink(SYSTEM_TRACE_SNAPSHOT); - // If a background system trace is happening and is marked as "suitable for - // bugreport" (i.e. bugreport_score > 0 in the trace config), this command - // will stop it and serialize into SYSTEM_TRACE_SNAPSHOT. In the (likely) - // case that no trace is ongoing, this command is a no-op. - // Note: this should not be enqueued as we need to freeze the trace before - // dumpstate starts. Otherwise the trace ring buffers will contain mostly - // the dumpstate's own activity which is irrelevant. - RunCommand("SERIALIZE PERFETTO TRACE", {"perfetto", "--save-for-bugreport"}, - CommandOptions::WithTimeout(10).DropRoot().CloseAllFileDescriptorsOnExec().Build()); - // MaybeAddSystemTraceToZip() will take care of copying the trace in the zip - // file in the later stages. + MYLOGI("Launching async '%s'", SERIALIZE_PERFETTO_TRACE_TASK.c_str()) + return std::async( + std::launch::async, [this, outPath = std::move(outPath), outFd = std::move(outFd)] { + // If a background system trace is happening and is marked as "suitable for + // bugreport" (i.e. bugreport_score > 0 in the trace config), this command + // will stop it and serialize into SYSTEM_TRACE_SNAPSHOT. In the (likely) + // case that no trace is ongoing, this command is a no-op. + // Note: this should not be enqueued as we need to freeze the trace before + // dumpstate starts. Otherwise the trace ring buffers will contain mostly + // the dumpstate's own activity which is irrelevant. + RunCommand( + SERIALIZE_PERFETTO_TRACE_TASK, {"perfetto", "--save-for-bugreport"}, + CommandOptions::WithTimeout(10).DropRoot().CloseAllFileDescriptorsOnExec().Build(), + false, outFd); + // MaybeAddSystemTraceToZip() will take care of copying the trace in the zip + // file in the later stages. + + return outPath; + }); +} + +void Dumpstate::MaybeWaitForSnapshotSystemTrace(std::future<std::string> task) { + if (!task.valid()) { + return; + } + + WaitForTask(std::move(task), SERIALIZE_PERFETTO_TRACE_TASK, STDOUT_FILENO); } void Dumpstate::MaybeSnapshotUiTraces() { @@ -3416,16 +3456,24 @@ void Dumpstate::MaybeSnapshotUiTraces() { return; } - const std::vector<std::vector<std::string>> dumpTracesForBugReportCommands = { - {"dumpsys", "activity", "service", "SystemUIService", "WMShell", "protolog", - "save-for-bugreport"}, - {"dumpsys", "activity", "service", "SystemUIService", "WMShell", "transitions", "tracing", - "save-for-bugreport"}, + std::vector<std::vector<std::string>> dumpTracesForBugReportCommands = { {"cmd", "input_method", "tracing", "save-for-bugreport"}, {"cmd", "window", "tracing", "save-for-bugreport"}, {"cmd", "window", "shell", "tracing", "save-for-bugreport"}, }; + if (!android_tracing_perfetto_transition_tracing()) { + dumpTracesForBugReportCommands.push_back({"dumpsys", "activity", "service", + "SystemUIService", "WMShell", "transitions", + "tracing", "save-for-bugreport"}); + } + + if (!android_tracing_perfetto_protolog()) { + dumpTracesForBugReportCommands.push_back({"dumpsys", "activity", "service", + "SystemUIService", "WMShell", "protolog", + "save-for-bugreport"}); + } + for (const auto& command : dumpTracesForBugReportCommands) { RunCommand( // Empty name because it's not intended to be classified as a bugreport section. diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h index de732c0dd9..46d949e303 100644 --- a/cmds/dumpstate/dumpstate.h +++ b/cmds/dumpstate/dumpstate.h @@ -570,7 +570,8 @@ class Dumpstate { RunStatus dumpstate(); void MaybeTakeEarlyScreenshot(); - void MaybeSnapshotSystemTrace(); + std::future<std::string> MaybeSnapshotSystemTraceAsync(); + void MaybeWaitForSnapshotSystemTrace(std::future<std::string> task); void MaybeSnapshotUiTraces(); void MaybeAddUiTracesToZip(); diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp index fc828864d5..2afabed813 100644 --- a/cmds/dumpstate/tests/dumpstate_test.cpp +++ b/cmds/dumpstate/tests/dumpstate_test.cpp @@ -16,23 +16,7 @@ #define LOG_TAG "dumpstate_test" -#include "DumpstateInternal.h" -#include "DumpstateService.h" -#include "android/os/BnDumpstate.h" #include "dumpstate.h" -#include "DumpPool.h" - -#include <gmock/gmock.h> -#include <gmock/gmock-matchers.h> -#include <gtest/gtest.h> - -#include <fcntl.h> -#include <libgen.h> -#include <signal.h> -#include <sys/types.h> -#include <unistd.h> -#include <filesystem> -#include <thread> #include <aidl/android/hardware/dumpstate/IDumpstateDevice.h> #include <android-base/file.h> @@ -41,10 +25,27 @@ #include <android-base/strings.h> #include <android-base/unique_fd.h> #include <android/hardware/dumpstate/1.1/types.h> +#include <android_tracing.h> #include <cutils/log.h> #include <cutils/properties.h> +#include <fcntl.h> +#include <gmock/gmock-matchers.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <libgen.h> +#include <signal.h> +#include <sys/types.h> +#include <unistd.h> #include <ziparchive/zip_archive.h> +#include <filesystem> +#include <thread> + +#include "DumpPool.h" +#include "DumpstateInternal.h" +#include "DumpstateService.h" +#include "android/os/BnDumpstate.h" + namespace android { namespace os { namespace dumpstate { @@ -999,10 +1000,13 @@ TEST_F(DumpstateTest, DumpPool_withParallelRunDisabled_isNull) { TEST_F(DumpstateTest, PreDumpUiData) { // These traces are always enabled, i.e. they are always pre-dumped - const std::vector<std::filesystem::path> uiTraces = { - std::filesystem::path{"/data/misc/wmtrace/wm_transition_trace.winscope"}, - std::filesystem::path{"/data/misc/wmtrace/shell_transition_trace.winscope"}, - }; + std::vector<std::filesystem::path> uiTraces; + if (!android_tracing_perfetto_transition_tracing()) { + uiTraces.push_back( + std::filesystem::path{"/data/misc/wmtrace/wm_transition_trace.winscope"}); + uiTraces.push_back( + std::filesystem::path{"/data/misc/wmtrace/shell_transition_trace.winscope"}); + } for (const auto traceFile : uiTraces) { std::system(("rm -f " + traceFile.string()).c_str()); diff --git a/cmds/evemu-record/README.md b/cmds/evemu-record/README.md new file mode 100644 index 0000000000..5d16d51da0 --- /dev/null +++ b/cmds/evemu-record/README.md @@ -0,0 +1,48 @@ +# `evemu-record` + +This is a Rust implementation of the `evemu-record` command from the [FreeDesktop project's evemu +suite][FreeDesktop]. It records the descriptor and events produced by a single input device in a +[simple text-based format][format] that can be replayed using the [`uinput` command on +Android][uinput] or the FreeDesktop evemu tools on other Linux-based platforms. It is included by +default with `userdebug` and `eng` builds of Android. + +The command-line interface is the same as that of the FreeDesktop version, except for +Android-specific features. For usage instructions, run `evemu-record --help`. + +## Usage example + +From a computer connected to the device over ADB, you can start a recording: + +``` +$ adb shell evemu-record > my-recording.evemu +Available devices: +/dev/input/event0: gpio_keys +/dev/input/event1: s2mpg12-power-keys +/dev/input/event2: NVTCapacitiveTouchScreen +/dev/input/event3: NVTCapacitivePen +/dev/input/event4: uinput-folio +/dev/input/event5: ACME Touchpad +Select the device event number [0-5]: 5 +``` + +...then use the input device for a while, and press Ctrl+C to finish. You will now have a +`my-recording.evemu` file that you can examine in a text editor. To replay it, use the [`uinput` +command][uinput]: + +``` +$ adb shell uinput - < my-recording.evemu +``` + +## Android-specific features + +### Timestamp bases + +By default, event timestamps are recorded relative to the time of the first event received during +the recording. Passing `--timestamp-base=boot` causes the timestamps to be recorded relative to the +system boot time instead. While this does not affect the playback of the recording, it can be useful +for matching recorded events with other logs that use such timestamps, such as `dmesg` or the +touchpad gesture debug logs emitted by `TouchpadInputMapper`. + +[FreeDesktop]: https://gitlab.freedesktop.org/libevdev/evemu +[format]: https://gitlab.freedesktop.org/libevdev/evemu#device-description-format +[uinput]: https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/cmds/uinput/README.md diff --git a/data/etc/android.hardware.location.gps.xml b/data/etc/android.hardware.location.gps.xml index 72ab73228e..2a55370832 100644 --- a/data/etc/android.hardware.location.gps.xml +++ b/data/etc/android.hardware.location.gps.xml @@ -17,6 +17,5 @@ <!-- These are the location-related features for devices that include GPS. --> <permissions> <feature name="android.hardware.location" /> - <feature name="android.hardware.location.network" /> <feature name="android.hardware.location.gps" /> </permissions> diff --git a/data/etc/car_core_hardware.xml b/data/etc/car_core_hardware.xml index 95b8110b6e..beb69f81dd 100644 --- a/data/etc/car_core_hardware.xml +++ b/data/etc/car_core_hardware.xml @@ -28,7 +28,6 @@ <feature name="android.hardware.audio.output" /> <feature name="android.hardware.location" /> - <feature name="android.hardware.location.network" /> <feature name="android.hardware.bluetooth" /> <feature name="android.hardware.touchscreen" /> <feature name="android.hardware.microphone" /> diff --git a/data/etc/wearable_core_hardware.xml b/data/etc/wearable_core_hardware.xml index 855b11005f..4c9932d4fb 100644 --- a/data/etc/wearable_core_hardware.xml +++ b/data/etc/wearable_core_hardware.xml @@ -36,6 +36,7 @@ <feature name="android.hardware.security.model.compatible" /> <!-- basic system services --> + <feature name="android.software.credentials" /> <feature name="android.software.home_screen" /> <feature name="android.software.secure_lock_screen" /> diff --git a/include/android/asset_manager.h b/include/android/asset_manager.h index 2ac7d4d350..6420cd0064 100644 --- a/include/android/asset_manager.h +++ b/include/android/asset_manager.h @@ -29,6 +29,10 @@ #include <sys/cdefs.h> #include <sys/types.h> +#if defined(__APPLE__) +typedef off_t off64_t; // Mac OSX does not define off64_t +#endif + #ifdef __cplusplus extern "C" { #endif diff --git a/include/android/input.h b/include/android/input.h index 16d86af44c..b5c1e5c354 100644 --- a/include/android/input.h +++ b/include/android/input.h @@ -1490,6 +1490,14 @@ int32_t AMotionEvent_getClassification(const AInputEvent* motion_event) */ const AInputEvent* AMotionEvent_fromJava(JNIEnv* env, jobject motionEvent) __INTRODUCED_IN(31); +/** + * Creates a java android.view.InputEvent object that is a copy of the specified native + * {@link AInputEvent}. Returns null on error + * + * Available since API level 35. + */ +jobject AInputEvent_toJava(JNIEnv* env, const AInputEvent* aInputEvent) __INTRODUCED_IN(35); + struct AInputQueue; /** * Input queue diff --git a/include/android/keycodes.h b/include/android/keycodes.h index f8fb256fae..79cdbcaf7b 100644 --- a/include/android/keycodes.h +++ b/include/android/keycodes.h @@ -839,6 +839,10 @@ enum { AKEYCODE_MACRO_3 = 315, /** User customizable key #4. */ AKEYCODE_MACRO_4 = 316, + /** Open Emoji picker */ + AKEYCODE_EMOJI_PICKER = 317, + /** Take Screenshot */ + AKEYCODE_SCREENSHOT = 318, // NOTE: If you add a new keycode here you must also add it to several other files. // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list. diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h index 9d2c79139f..3c82d888b5 100644 --- a/include/android/performance_hint.h +++ b/include/android/performance_hint.h @@ -123,7 +123,8 @@ typedef struct APerformanceHintSession APerformanceHintSession; * * @return APerformanceHintManager instance on success, nullptr on failure. */ -APerformanceHintManager* _Nullable APerformanceHint_getManager() __INTRODUCED_IN(__ANDROID_API_T__); +APerformanceHintManager* _Nullable APerformanceHint_getManager() + __INTRODUCED_IN(__ANDROID_API_T__); /** * Creates a session for the given set of threads and sets their initial target work @@ -232,14 +233,14 @@ int APerformanceHint_setPreferPowerEfficiency( * @param workDuration The {@link AWorkDuration} structure of times the thread group took to * complete its last task in nanoseconds breaking down into different components. * - * The work period start timestamp, actual total duration and actual CPU duration must be - * positive. + * The work period start timestamp and actual total duration must be greater than zero. * - * The actual GPU duration must be non-negative. If the actual GPU duration is 0, it means - * the actual GPU duration is not measured. + * The actual CPU and GPU durations must be greater than or equal to zero, and at least one + * of them must be greater than zero. When one of them is equal to zero, it means that type + * of work was not measured for this workload. * * @return 0 on success. - * EINVAL if session is nullptr or any duration is an invalid number. + * EINVAL if any duration is an invalid number. * EPIPE if communication with the system service has failed. */ int APerformanceHint_reportActualWorkDuration2( @@ -260,14 +261,15 @@ AWorkDuration* _Nonnull AWorkDuration_create() __INTRODUCED_IN(__ANDROID_API_V__ * * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()} */ -void AWorkDuration_release(AWorkDuration* _Nonnull WorkDuration) __INTRODUCED_IN(__ANDROID_API_V__); +void AWorkDuration_release(AWorkDuration* _Nonnull aWorkDuration) + __INTRODUCED_IN(__ANDROID_API_V__); /** * Sets the work period start timestamp in nanoseconds. * * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()} * @param workPeriodStartTimestampNanos The work period start timestamp in nanoseconds based on - * CLOCK_MONOTONIC about when the work starts, the timestamp must be positive. + * CLOCK_MONOTONIC about when the work starts. This timestamp must be greater than zero. */ void AWorkDuration_setWorkPeriodStartTimestampNanos(AWorkDuration* _Nonnull aWorkDuration, int64_t workPeriodStartTimestampNanos) __INTRODUCED_IN(__ANDROID_API_V__); @@ -276,8 +278,8 @@ void AWorkDuration_setWorkPeriodStartTimestampNanos(AWorkDuration* _Nonnull aWor * Sets the actual total work duration in nanoseconds. * * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()} - * @param actualTotalDurationNanos The actual total work duration in nanoseconds, the number must be - * positive. + * @param actualTotalDurationNanos The actual total work duration in nanoseconds. This number must + * be greater than zero. */ void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* _Nonnull aWorkDuration, int64_t actualTotalDurationNanos) __INTRODUCED_IN(__ANDROID_API_V__); @@ -286,8 +288,9 @@ void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* _Nonnull aWorkDura * Sets the actual CPU work duration in nanoseconds. * * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()} - * @param actualCpuDurationNanos The actual CPU work duration in nanoseconds, the number must be - * positive. + * @param actualCpuDurationNanos The actual CPU work duration in nanoseconds. This number must be + * greater than or equal to zero. If it is equal to zero, that means the CPU was not + * measured. */ void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* _Nonnull aWorkDuration, int64_t actualCpuDurationNanos) __INTRODUCED_IN(__ANDROID_API_V__); @@ -297,7 +300,7 @@ void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* _Nonnull aWorkDurati * * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}. * @param actualGpuDurationNanos The actual GPU work duration in nanoseconds, the number must be - * non-negative. If the actual GPU duration is 0, it means the actual GPU duration is + * greater than or equal to zero. If it is equal to zero, that means the GPU was not * measured. */ void AWorkDuration_setActualGpuDurationNanos(AWorkDuration* _Nonnull aWorkDuration, diff --git a/include/android/surface_control.h b/include/android/surface_control.h index cce2e46471..321737e226 100644 --- a/include/android/surface_control.h +++ b/include/android/surface_control.h @@ -528,9 +528,8 @@ void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* transactio /** * 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. + * dataspace has RANGE_EXTENDED set on it. See: ASurfaceTransaction_setDesiredHdrHeadroom, prefer + * using this API for formats that encode an HDR/SDR ratio as part of generating the buffer. * * @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 @@ -564,6 +563,12 @@ void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* transactio * determined entirely by the dataspace being used (ie, typically SDR * however PQ or HLG transfer functions will still result in HDR) * + * When called after ASurfaceTransaction_setDesiredHdrHeadroom, the + * desiredRatio will override the desiredHeadroom provided by + * ASurfaceTransaction_setDesiredHdrHeadroom. Conversely, when called before + * ASurfaceTransaction_setDesiredHdrHeadroom, the desiredHeadroom provided by + *. ASurfaceTransaction_setDesiredHdrHeadroom will override the desiredRatio. + * * Must be finite && >= 1.0f * * Available since API level 34. @@ -574,6 +579,45 @@ void ASurfaceTransaction_setExtendedRangeBrightness(ASurfaceTransaction* transac float desiredRatio) __INTRODUCED_IN(__ANDROID_API_U__); /** + * Sets the desired hdr headroom for the layer. See: ASurfaceTransaction_setExtendedRangeBrightness, + * prefer using this API for formats that conform to HDR standards like HLG or HDR10, that do not + * communicate a HDR/SDR ratio as part of generating the buffer. + * + * @param surface_control The layer whose desired hdr headroom is being specified + * + * @param desiredHeadroom The desired hdr/sdr ratio as represented as peakHdrBrightnessInNits / + * targetSdrWhitePointInNits. This can be used to communicate the max + * desired brightness range of the panel. The system may not be able to, or + * may choose not to, deliver the requested range. + * + * While requesting a large desired ratio will result in the most + * dynamic range, 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. + * + * Default value is 0.0f and indicates that the system will choose the best + * headroom for this surface control's content. Typically, this means that + * HLG/PQ encoded content will be displayed with some HDR headroom greater + * than 1.0. + * + * When called after ASurfaceTransaction_setExtendedRangeBrightness, the + * desiredHeadroom will override the desiredRatio provided by + * ASurfaceTransaction_setExtendedRangeBrightness. Conversely, when called + * before ASurfaceTransaction_setExtendedRangeBrightness, the desiredRatio + * provided by ASurfaceTransaction_setExtendedRangeBrightness will override + * the desiredHeadroom. + * + * Must be finite && >= 1.0f or 0.0f to indicate there is no desired + * headroom. + * + * Available since API level 35. + */ +void ASurfaceTransaction_setDesiredHdrHeadroom(ASurfaceTransaction* transaction, + ASurfaceControl* surface_control, + float desiredHeadroom) + __INTRODUCED_IN(__ANDROID_API_V__); + +/** * Same as ASurfaceTransaction_setFrameRateWithChangeStrategy(transaction, surface_control, * frameRate, compatibility, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS). * diff --git a/include/android/thermal.h b/include/android/thermal.h index 0b57e9376d..fa168cdecb 100644 --- a/include/android/thermal.h +++ b/include/android/thermal.h @@ -111,7 +111,7 @@ typedef struct AThermalManager AThermalManager; * It's passed the updated thermal status as parameter, as well as the * pointer provided by the client that registered a callback. */ -typedef void (*AThermal_StatusCallback)(void* data, AThermalStatus status); +typedef void (*AThermal_StatusCallback)(void* _Nullable data, AThermalStatus status); /** * Acquire an instance of the thermal manager. This must be freed using @@ -121,7 +121,7 @@ typedef void (*AThermal_StatusCallback)(void* data, AThermalStatus status); * * @return manager instance on success, nullptr on failure. */ -AThermalManager* AThermal_acquireManager() __INTRODUCED_IN(30); +AThermalManager* _Nonnull AThermal_acquireManager() __INTRODUCED_IN(30); /** * Release the thermal manager pointer acquired via @@ -131,7 +131,7 @@ AThermalManager* AThermal_acquireManager() __INTRODUCED_IN(30); * * @param manager The manager to be released. */ -void AThermal_releaseManager(AThermalManager *manager) __INTRODUCED_IN(30); +void AThermal_releaseManager(AThermalManager* _Nonnull manager) __INTRODUCED_IN(30); /** * Gets the current thermal status. @@ -143,7 +143,8 @@ void AThermal_releaseManager(AThermalManager *manager) __INTRODUCED_IN(30); * * @return current thermal status, ATHERMAL_STATUS_ERROR on failure. */ -AThermalStatus AThermal_getCurrentThermalStatus(AThermalManager *manager) __INTRODUCED_IN(30); +AThermalStatus +AThermal_getCurrentThermalStatus(AThermalManager* _Nonnull manager) __INTRODUCED_IN(30); /** * Register the thermal status listener for thermal status change. @@ -160,8 +161,9 @@ AThermalStatus AThermal_getCurrentThermalStatus(AThermalManager *manager) __INTR * EPERM if the required permission is not held. * EPIPE if communication with the system service has failed. */ -int AThermal_registerThermalStatusListener(AThermalManager *manager, - AThermal_StatusCallback callback, void *data) __INTRODUCED_IN(30); +int AThermal_registerThermalStatusListener(AThermalManager* _Nonnull manager, + AThermal_StatusCallback _Nullable callback, + void* _Nullable data) __INTRODUCED_IN(30); /** * Unregister the thermal status listener previously resgistered. @@ -178,8 +180,9 @@ int AThermal_registerThermalStatusListener(AThermalManager *manager, * EPERM if the required permission is not held. * EPIPE if communication with the system service has failed. */ -int AThermal_unregisterThermalStatusListener(AThermalManager *manager, - AThermal_StatusCallback callback, void *data) __INTRODUCED_IN(30); +int AThermal_unregisterThermalStatusListener(AThermalManager* _Nonnull manager, + AThermal_StatusCallback _Nullable callback, + void* _Nullable data) __INTRODUCED_IN(30); /** * Provides an estimate of how much thermal headroom the device currently has before @@ -219,8 +222,8 @@ int AThermal_unregisterThermalStatusListener(AThermalManager *manager, * as described above. Returns NaN if the device does not support this functionality or * if this function is called significantly faster than once per second. */ -float AThermal_getThermalHeadroom(AThermalManager *manager, - int forecastSeconds) __INTRODUCED_IN(31); +float AThermal_getThermalHeadroom(AThermalManager* _Nonnull manager, + int forecastSeconds) __INTRODUCED_IN(31); /** * This struct defines an instance of headroom threshold value and its status. @@ -282,9 +285,10 @@ struct AThermalHeadroomThreshold { * EPIPE if communication with the system service has failed. * ENOSYS if the feature is disabled by the current system. */ -int AThermal_getThermalHeadroomThresholds(AThermalManager* manager, - const AThermalHeadroomThreshold ** outThresholds, - size_t* size) __INTRODUCED_IN(35); +int AThermal_getThermalHeadroomThresholds(AThermalManager* _Nonnull manager, + const AThermalHeadroomThreshold* _Nonnull + * _Nullable outThresholds, + size_t* _Nonnull size) __INTRODUCED_IN(35); #ifdef __cplusplus } diff --git a/include/ftl/details/future.h b/include/ftl/details/future.h index df1323e8be..8d82e0ffd2 100644 --- a/include/ftl/details/future.h +++ b/include/ftl/details/future.h @@ -73,8 +73,18 @@ class BaseFuture<Self, T, std::future> { return std::get<Impl>(self()).get(); } + template <class Rep, class Period> + std::future_status wait_for(const std::chrono::duration<Rep, Period>& timeout_duration) const { + if (std::holds_alternative<T>(self())) { + return std::future_status::ready; + } + + return std::get<Impl>(self()).wait_for(timeout_duration); + } + private: auto& self() { return static_cast<Self&>(*this).future_; } + const auto& self() const { return static_cast<const Self&>(*this).future_; } }; template <typename Self, typename T> @@ -90,6 +100,15 @@ class BaseFuture<Self, T, std::shared_future> { return std::get<Impl>(self()).get(); } + template <class Rep, class Period> + std::future_status wait_for(const std::chrono::duration<Rep, Period>& timeout_duration) const { + if (std::holds_alternative<T>(self())) { + return std::future_status::ready; + } + + return std::get<Impl>(self()).wait_for(timeout_duration); + } + private: const auto& self() const { return static_cast<const Self&>(*this).future_; } }; diff --git a/include/ftl/expected.h b/include/ftl/expected.h new file mode 100644 index 0000000000..12b6102b6f --- /dev/null +++ b/include/ftl/expected.h @@ -0,0 +1,65 @@ +/* + * Copyright 2024 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/expected.h> +#include <ftl/optional.h> + +#include <utility> + +namespace android::ftl { + +// Superset of base::expected<T, E> with monadic operations. +// +// TODO: Extend std::expected<T, E> in C++23. +// +template <typename T, typename E> +struct Expected final : base::expected<T, E> { + using Base = base::expected<T, E>; + using Base::expected; + + using Base::error; + using Base::has_value; + using Base::value; + + template <typename P> + constexpr bool has_error(P predicate) const { + return !has_value() && predicate(error()); + } + + constexpr Optional<T> value_opt() const& { + return has_value() ? Optional(value()) : std::nullopt; + } + + constexpr Optional<T> value_opt() && { + return has_value() ? Optional(std::move(value())) : std::nullopt; + } + + // Delete new for this class. Its base doesn't have a virtual destructor, and + // if it got deleted via base class pointer, it would cause undefined + // behavior. There's not a good reason to allocate this object on the heap + // anyway. + static void* operator new(size_t) = delete; + static void* operator new[](size_t) = delete; +}; + +template <typename E> +constexpr auto Unexpected(E&& error) { + return base::unexpected(std::forward<E>(error)); +} + +} // namespace android::ftl diff --git a/include/ftl/fake_guard.h b/include/ftl/fake_guard.h index bacd1b29ef..e6012516fc 100644 --- a/include/ftl/fake_guard.h +++ b/include/ftl/fake_guard.h @@ -85,6 +85,5 @@ struct [[clang::scoped_lockable]] FakeGuard final { #define FTL_MAKE_FAKE_GUARD(arg1, arg2, guard, ...) guard -// The void argument suppresses a warning about zero variadic macro arguments. #define FTL_FAKE_GUARD(...) \ - FTL_MAKE_FAKE_GUARD(__VA_ARGS__, FTL_FAKE_GUARD2, FTL_FAKE_GUARD1, void)(__VA_ARGS__) + FTL_MAKE_FAKE_GUARD(__VA_ARGS__, FTL_FAKE_GUARD2, FTL_FAKE_GUARD1, )(__VA_ARGS__) diff --git a/include/ftl/future.h b/include/ftl/future.h index c78f9b76b6..dad180ff8e 100644 --- a/include/ftl/future.h +++ b/include/ftl/future.h @@ -51,6 +51,7 @@ class Future final : public details::BaseFuture<Future<T, FutureImpl>, T, Future // Forwarding functions. Base::share is only defined when FutureImpl is std::future, whereas the // following are defined for either FutureImpl: using Base::get; + using Base::wait_for; // Attaches a continuation to the future. The continuation is a function that maps T to either R // or ftl::Future<R>. In the former case, the chain wraps the result in a future as if by diff --git a/include/ftl/small_map.h b/include/ftl/small_map.h index 49cde7fedc..83d5967464 100644 --- a/include/ftl/small_map.h +++ b/include/ftl/small_map.h @@ -107,12 +107,20 @@ class SmallMap final { template <typename Q, typename W, std::size_t M, typename E> SmallMap(SmallMap<Q, W, M, E> other) : map_(std::move(other.map_)) {} + static constexpr size_type static_capacity() { return N; } + size_type max_size() const { return map_.max_size(); } size_type size() const { return map_.size(); } bool empty() const { return map_.empty(); } // Returns whether the map is backed by static or dynamic storage. - bool dynamic() const { return map_.dynamic(); } + bool dynamic() const { + if constexpr (static_capacity() > 0) { + return map_.dynamic(); + } else { + return true; + } + } iterator begin() { return map_.begin(); } const_iterator begin() const { return cbegin(); } @@ -171,9 +179,15 @@ class SmallMap final { return {it, false}; } - auto& ref = map_.emplace_back(std::piecewise_construct, std::forward_as_tuple(key), - std::forward_as_tuple(std::forward<Args>(args)...)); - return {&ref, true}; + decltype(auto) ref_or_it = + map_.emplace_back(std::piecewise_construct, std::forward_as_tuple(key), + std::forward_as_tuple(std::forward<Args>(args)...)); + + if constexpr (static_capacity() > 0) { + return {&ref_or_it, true}; + } else { + return {ref_or_it, true}; + } } // Replaces a mapping if it exists, and returns an iterator to it. Returns the end() iterator diff --git a/include/ftl/small_vector.h b/include/ftl/small_vector.h index 11294c3ac8..43e9fac5e2 100644 --- a/include/ftl/small_vector.h +++ b/include/ftl/small_vector.h @@ -124,30 +124,29 @@ class SmallVector final : details::ArrayTraits<T>, details::ArrayComparators<Sma DISPATCH(size_type, size, const) DISPATCH(bool, empty, const) - // noexcept to suppress warning about zero variadic macro arguments. - DISPATCH(iterator, begin, noexcept) + DISPATCH(iterator, begin, ) DISPATCH(const_iterator, begin, const) DISPATCH(const_iterator, cbegin, const) - DISPATCH(iterator, end, noexcept) + DISPATCH(iterator, end, ) DISPATCH(const_iterator, end, const) DISPATCH(const_iterator, cend, const) - DISPATCH(reverse_iterator, rbegin, noexcept) + DISPATCH(reverse_iterator, rbegin, ) DISPATCH(const_reverse_iterator, rbegin, const) DISPATCH(const_reverse_iterator, crbegin, const) - DISPATCH(reverse_iterator, rend, noexcept) + DISPATCH(reverse_iterator, rend, ) DISPATCH(const_reverse_iterator, rend, const) DISPATCH(const_reverse_iterator, crend, const) - DISPATCH(iterator, last, noexcept) + DISPATCH(iterator, last, ) DISPATCH(const_iterator, last, const) - DISPATCH(reference, front, noexcept) + DISPATCH(reference, front, ) DISPATCH(const_reference, front, const) - DISPATCH(reference, back, noexcept) + DISPATCH(reference, back, ) DISPATCH(const_reference, back, const) reference operator[](size_type i) { @@ -211,13 +210,13 @@ class SmallVector final : details::ArrayTraits<T>, details::ArrayComparators<Sma // // The last() and end() iterators are invalidated. // - DISPATCH(void, pop_back, noexcept) + DISPATCH(void, pop_back, ) // Removes all elements. // // All iterators are invalidated. // - DISPATCH(void, clear, noexcept) + DISPATCH(void, clear, ) #undef DISPATCH diff --git a/include/input/AccelerationCurve.h b/include/input/AccelerationCurve.h new file mode 100644 index 0000000000..0cf648a2f7 --- /dev/null +++ b/include/input/AccelerationCurve.h @@ -0,0 +1,49 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <cstdint> +#include <vector> + +namespace android { + +/** + * Describes a section of an acceleration curve as a function which outputs a scaling factor (gain) + * for the pointer movement, given the speed of the mouse or finger (in mm/s): + * + * gain(input_speed_mm_per_s) = baseGain + reciprocal / input_speed_mm_per_s + */ +struct AccelerationCurveSegment { + /** + * The maximum pointer speed at which this segment should apply, in mm/s. The last segment in a + * curve should always set this to infinity. + */ + double maxPointerSpeedMmPerS; + /** The gain for this segment before the reciprocal is taken into account. */ + double baseGain; + /** The reciprocal part of the formula, which should be divided by the input speed. */ + double reciprocal; +}; + +/** + * Creates an acceleration curve for the given pointer sensitivity value. The sensitivity value + * should be between -7 (for the lowest sensitivity) and 7, inclusive. + */ +std::vector<AccelerationCurveSegment> createAccelerationCurveForPointerSensitivity( + int32_t sensitivity); + +} // namespace android diff --git a/include/input/Input.h b/include/input/Input.h index 1c4ea6b416..7b253a53ef 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -515,6 +515,8 @@ struct PointerProperties { PointerProperties& operator=(const PointerProperties&) = default; }; +std::ostream& operator<<(std::ostream& out, const PointerProperties& properties); + // TODO(b/211379801) : Use a strong type from ftl/mixins.h instead using DeviceId = int32_t; diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h index b7751f704a..57b659d9ee 100644 --- a/include/input/InputDevice.h +++ b/include/input/InputDevice.h @@ -75,6 +75,17 @@ struct InputDeviceIdentifier { bool operator!=(const InputDeviceIdentifier&) const = default; }; +/** + * Holds View related behaviors for an InputDevice. + */ +struct InputDeviceViewBehavior { + /** + * The smooth scroll behavior that applies for all source/axis, if defined by the device. + * Empty optional if the device has not specified the default smooth scroll behavior. + */ + std::optional<bool> shouldSmoothScroll; +}; + /* Types of input device sensors. Keep sync with core/java/android/hardware/Sensor.java */ enum class InputDeviceSensorType : int32_t { ACCELEROMETER = ASENSOR_TYPE_ACCELEROMETER, @@ -266,7 +277,8 @@ public: void initialize(int32_t id, int32_t generation, int32_t controllerNumber, const InputDeviceIdentifier& identifier, const std::string& alias, - bool isExternal, bool hasMic, int32_t associatedDisplayId); + bool isExternal, bool hasMic, int32_t associatedDisplayId, + InputDeviceViewBehavior viewBehavior = {{}}); inline int32_t getId() const { return mId; } inline int32_t getControllerNumber() const { return mControllerNumber; } @@ -298,6 +310,8 @@ public: return mKeyboardLayoutInfo; } + inline const InputDeviceViewBehavior& getViewBehavior() const { return mViewBehavior; } + inline void setKeyCharacterMap(const std::shared_ptr<KeyCharacterMap> value) { mKeyCharacterMap = value; } @@ -359,6 +373,8 @@ private: std::unordered_map<int32_t, InputDeviceLightInfo> mLights; /* Map from battery ID to battery info */ std::unordered_map<int32_t, InputDeviceBatteryInfo> mBatteries; + /** The View related behaviors for the device. */ + InputDeviceViewBehavior mViewBehavior; }; /* Types of input device configuration files. */ diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h index 59b9495c42..42dcd3c394 100644 --- a/include/input/InputTransport.h +++ b/include/input/InputTransport.h @@ -35,18 +35,16 @@ #include <android-base/result.h> #include <android-base/unique_fd.h> +#include <android/os/InputChannelCore.h> #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> #include <utils/Errors.h> -#include <utils/RefBase.h> #include <utils/Timers.h> - namespace android { class Parcel; @@ -231,18 +229,15 @@ struct InputMessage { * input messages across processes. Each channel has a descriptive name for debugging purposes. * * Each endpoint has its own InputChannel object that specifies its file descriptor. + * For parceling, this relies on android::os::InputChannelCore, defined in aidl. * * The input channel is closed when all references to it are released. */ -class InputChannel : public Parcelable { +class InputChannel : private android::os::InputChannelCore { public: - static std::unique_ptr<InputChannel> create(const std::string& name, - android::base::unique_fd fd, sp<IBinder> token); - InputChannel() = default; - InputChannel(const InputChannel& other) - : mName(other.mName), mFd(other.dupFd()), mToken(other.mToken){}; - InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token); - ~InputChannel() override; + static std::unique_ptr<InputChannel> create(android::os::InputChannelCore&& parceledChannel); + ~InputChannel(); + /** * Create a pair of input channels. * The two returned input channels are equivalent, and are labeled as "server" and "client" @@ -254,9 +249,8 @@ public: std::unique_ptr<InputChannel>& outServerChannel, std::unique_ptr<InputChannel>& outClientChannel); - inline std::string getName() const { return mName; } - inline const android::base::unique_fd& getFd() const { return mFd; } - inline sp<IBinder> getToken() const { return mToken; } + inline std::string getName() const { return name; } + inline int getFd() const { return fd.get(); } /* Send a message to the other endpoint. * @@ -283,13 +277,37 @@ public: */ status_t receiveMessage(InputMessage* msg); + /* Tells whether there is a message in the channel available to be received. + * + * This is only a performance hint and may return false negative results. Clients should not + * rely on availability of the message based on the return value. + */ + bool probablyHasInput() const; + + /* Wait until there is a message in the channel. + * + * The |timeout| specifies how long to block waiting for an input event to appear. Negative + * values are not allowed. + * + * In some cases returning before timeout expiration can happen without a message available. + * This could happen after the channel was closed on the other side. Another possible reason + * is incorrect setup of the channel. + */ + void waitForMessage(std::chrono::milliseconds timeout) const; + /* Return a new object that has a duplicate of this channel's fd. */ std::unique_ptr<InputChannel> dup() const; - void copyTo(InputChannel& outChannel) const; + void copyTo(android::os::InputChannelCore& outChannel) const; - status_t readFromParcel(const android::Parcel* parcel) override; - status_t writeToParcel(android::Parcel* parcel) const override; + /** + * Similar to "copyTo", but it takes ownership of the provided InputChannel (and after this is + * called, it destroys it). + * @param from the InputChannel that should be converted to InputChannelCore + * @param outChannel the pre-allocated InputChannelCore to which to transfer the 'from' channel + */ + static void moveChannel(std::unique_ptr<InputChannel> from, + android::os::InputChannelCore& outChannel); /** * The connection token is used to identify the input connection, i.e. @@ -305,26 +323,11 @@ public: */ sp<IBinder> getConnectionToken() const; - bool operator==(const InputChannel& inputChannel) const { - struct stat lhs, rhs; - if (fstat(mFd.get(), &lhs) != 0) { - return false; - } - if (fstat(inputChannel.getFd().get(), &rhs) != 0) { - return false; - } - // If file descriptors are pointing to same inode they are duplicated fds. - return inputChannel.getName() == getName() && inputChannel.getConnectionToken() == mToken && - lhs.st_ino == rhs.st_ino; - } - private: - base::unique_fd dupFd() const; - - std::string mName; - base::unique_fd mFd; + static std::unique_ptr<InputChannel> create(const std::string& name, + android::base::unique_fd fd, sp<IBinder> token); - sp<IBinder> mToken; + InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token); }; /* @@ -339,7 +342,7 @@ public: ~InputPublisher(); /* Gets the underlying input channel. */ - inline std::shared_ptr<InputChannel> getChannel() { return mChannel; } + inline InputChannel& getChannel() const { return *mChannel; } /* Publishes a key event to the input channel. * @@ -518,6 +521,13 @@ public: */ int32_t getPendingBatchSource() const; + /* Returns true when there is *likely* a pending batch or a pending event in the channel. + * + * This is only a performance hint and may return false negative results. Clients should not + * rely on availability of the message based on the return value. + */ + bool probablyHasInput() const; + std::string dump() const; private: diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h index 83fffa37c6..3470be4dce 100644 --- a/include/input/PrintTools.h +++ b/include/input/PrintTools.h @@ -117,11 +117,12 @@ std::string dumpMapKeys(const std::map<K, V>& map, template <typename T> std::string dumpVector(const std::vector<T>& values, std::string (*valueToString)(const T&) = constToString) { - std::string dump = valueToString(values[0]); - for (size_t i = 1; i < values.size(); i++) { - dump += ", " + valueToString(values[i]); + std::string out; + for (const auto& value : values) { + out += out.empty() ? "[" : ", "; + out += valueToString(value); } - return dump; + return out.empty() ? "[]" : (out + "]"); } const char* toString(bool value); diff --git a/include/input/VelocityControl.h b/include/input/VelocityControl.h index b78f63e1ae..7c58c87f8b 100644 --- a/include/input/VelocityControl.h +++ b/include/input/VelocityControl.h @@ -16,7 +16,10 @@ #pragma once +#include <vector> + #include <android-base/stringprintf.h> +#include <input/AccelerationCurve.h> #include <input/Input.h> #include <input/VelocityTracker.h> #include <utils/Timers.h> @@ -86,12 +89,7 @@ struct VelocityControlParameters { class VelocityControl { public: VelocityControl(); - - /* Gets the various parameters. */ - const VelocityControlParameters& getParameters() const; - - /* Sets the various parameters. */ - void setParameters(const VelocityControlParameters& parameters); + virtual ~VelocityControl() {} /* Resets the current movement counters to zero. * This has the effect of nullifying any acceleration. */ @@ -101,16 +99,55 @@ public: * scaled / accelerated delta based on the current velocity. */ void move(nsecs_t eventTime, float* deltaX, float* deltaY); -private: +protected: + virtual void scaleDeltas(float* deltaX, float* deltaY) = 0; + // If no movements are received within this amount of time, // we assume the movement has stopped and reset the movement counters. static const nsecs_t STOP_TIME = 500 * 1000000; // 500 ms - VelocityControlParameters mParameters; - nsecs_t mLastMovementTime; float mRawPositionX, mRawPositionY; VelocityTracker mVelocityTracker; }; +/** + * Velocity control using a simple acceleration curve where the acceleration factor increases + * linearly with movement speed, subject to minimum and maximum values. + */ +class SimpleVelocityControl : public VelocityControl { +public: + /** Gets the various parameters. */ + const VelocityControlParameters& getParameters() const; + + /** Sets the various parameters. */ + void setParameters(const VelocityControlParameters& parameters); + +protected: + virtual void scaleDeltas(float* deltaX, float* deltaY) override; + +private: + VelocityControlParameters mParameters; +}; + +/** Velocity control using a curve made up of multiple reciprocal segments. */ +class CurvedVelocityControl : public VelocityControl { +public: + CurvedVelocityControl(); + + /** Sets the curve to be used for acceleration. */ + void setCurve(const std::vector<AccelerationCurveSegment>& curve); + + void setAccelerationEnabled(bool enabled); + +protected: + virtual void scaleDeltas(float* deltaX, float* deltaY) override; + +private: + const AccelerationCurveSegment& segmentForSpeed(float speedMmPerS); + + bool mAccelerationEnabled = true; + std::vector<AccelerationCurveSegment> mCurveSegments; +}; + } // namespace android diff --git a/include/input/VirtualInputDevice.h b/include/input/VirtualInputDevice.h index 21a28770b6..222dac8557 100644 --- a/include/input/VirtualInputDevice.h +++ b/include/input/VirtualInputDevice.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,6 +64,8 @@ public: class VirtualMouse : public VirtualInputDevice { public: + // Expose to share with VirtualStylus. + static const std::map<int, UinputAction> BUTTON_ACTION_MAPPING; VirtualMouse(android::base::unique_fd fd); virtual ~VirtualMouse() override; bool writeButtonEvent(int32_t androidButtonCode, int32_t androidAction, @@ -74,12 +76,13 @@ public: std::chrono::nanoseconds eventTime); private: - static const std::map<int, UinputAction> BUTTON_ACTION_MAPPING; static const std::map<int, int> BUTTON_CODE_MAPPING; }; class VirtualTouchscreen : public VirtualInputDevice { public: + // Expose to share with VirtualStylus. + static const std::map<int, UinputAction> TOUCH_ACTION_MAPPING; VirtualTouchscreen(android::base::unique_fd fd); virtual ~VirtualTouchscreen() override; // TODO(b/259554911): changing float parameters to int32_t. @@ -88,9 +91,7 @@ public: std::chrono::nanoseconds eventTime); 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 @@ -101,4 +102,24 @@ private: bool handleTouchDown(int32_t pointerId, std::chrono::nanoseconds eventTime); bool handleTouchUp(int32_t pointerId, std::chrono::nanoseconds eventTime); }; + +class VirtualStylus : public VirtualInputDevice { +public: + VirtualStylus(android::base::unique_fd fd); + ~VirtualStylus() override; + bool writeMotionEvent(int32_t toolType, int32_t action, int32_t locationX, int32_t locationY, + int32_t pressure, int32_t tiltX, int32_t tiltY, + std::chrono::nanoseconds eventTime); + bool writeButtonEvent(int32_t androidButtonCode, int32_t androidAction, + std::chrono::nanoseconds eventTime); + +private: + static const std::map<int, int> TOOL_TYPE_MAPPING; + static const std::map<int, int> BUTTON_CODE_MAPPING; + // True if the stylus is touching or hovering on the screen. + bool mIsStylusDown; + bool handleStylusDown(uint16_t tool, std::chrono::nanoseconds eventTime); + bool handleStylusUp(uint16_t tool, std::chrono::nanoseconds eventTime); +}; + } // namespace android diff --git a/include/powermanager/PowerHalController.h b/include/powermanager/PowerHalController.h index 9e426d3ea3..c50bc4a188 100644 --- a/include/powermanager/PowerHalController.h +++ b/include/powermanager/PowerHalController.h @@ -62,7 +62,15 @@ public: virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos) override; + virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> + createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, + int64_t durationNanos, + aidl::android::hardware::power::SessionTag tag, + aidl::android::hardware::power::SessionConfig* config) override; virtual HalResult<int64_t> getHintSessionPreferredRate() override; + virtual HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel( + int tgid, int uid) override; + virtual HalResult<void> closeSessionChannel(int tgid, int uid) override; private: std::mutex mConnectedHalMutex; @@ -75,7 +83,7 @@ private: std::shared_ptr<HalWrapper> initHal(); template <typename T> - HalResult<T> processHalResult(HalResult<T> result, const char* functionName); + HalResult<T> processHalResult(HalResult<T>&& result, const char* functionName); }; // ------------------------------------------------------------------------------------------------- diff --git a/include/powermanager/PowerHalWrapper.h b/include/powermanager/PowerHalWrapper.h index 4e4a1b000d..e2da014606 100644 --- a/include/powermanager/PowerHalWrapper.h +++ b/include/powermanager/PowerHalWrapper.h @@ -14,19 +14,22 @@ * limitations under the License. */ -#ifndef ANDROID_POWERHALWRAPPER_H -#define ANDROID_POWERHALWRAPPER_H +#pragma once #include <aidl/android/hardware/power/Boost.h> +#include <aidl/android/hardware/power/ChannelConfig.h> #include <aidl/android/hardware/power/IPower.h> #include <aidl/android/hardware/power/IPowerHintSession.h> #include <aidl/android/hardware/power/Mode.h> +#include <aidl/android/hardware/power/SessionConfig.h> #include <android-base/thread_annotations.h> #include <android/hardware/power/1.1/IPower.h> #include <android/hardware/power/1.2/IPower.h> #include <android/hardware/power/1.3/IPower.h> #include <binder/Status.h> +#include <utility> + namespace android { namespace power { @@ -42,44 +45,63 @@ enum class HalSupport { template <typename T> class HalResult { public: - static HalResult<T> ok(T value) { return HalResult(value); } - static HalResult<T> failed(std::string msg) { - return HalResult(std::move(msg), /* unsupported= */ false); - } + static HalResult<T> ok(T&& value) { return HalResult(std::forward<T>(value)); } + static HalResult<T> ok(T& value) { return HalResult<T>::ok(T{value}); } + static HalResult<T> failed(std::string msg) { return HalResult(msg, /* unsupported= */ false); } static HalResult<T> unsupported() { return HalResult("", /* unsupported= */ true); } - static HalResult<T> fromStatus(const binder::Status& status, T data) { + static HalResult<T> fromStatus(const binder::Status& status, T&& data) { if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) { return HalResult<T>::unsupported(); } if (status.isOk()) { - return HalResult<T>::ok(data); + return HalResult<T>::ok(std::forward<T>(data)); } return HalResult<T>::failed(std::string(status.toString8().c_str())); } - static HalResult<T> fromStatus(const ndk::ScopedAStatus& status, T data) { + static HalResult<T> fromStatus(const binder::Status& status, T& data) { + return HalResult<T>::fromStatus(status, T{data}); + } + + static HalResult<T> fromStatus(const ndk::ScopedAStatus& status, T&& data) { if (status.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) { return HalResult<T>::unsupported(); } if (status.isOk()) { - return HalResult<T>::ok(data); + return HalResult<T>::ok(std::forward<T>(data)); } return HalResult<T>::failed(std::string(status.getDescription())); } + static HalResult<T> fromStatus(const ndk::ScopedAStatus& status, T& data) { + return HalResult<T>::fromStatus(status, T{data}); + } + template <typename R> - static HalResult<T> fromReturn(hardware::Return<R>& ret, T data) { - return ret.isOk() ? HalResult<T>::ok(data) : HalResult<T>::failed(ret.description()); + static HalResult<T> fromReturn(hardware::Return<R>& ret, T&& data) { + return ret.isOk() ? HalResult<T>::ok(std::forward<T>(data)) + : HalResult<T>::failed(ret.description()); + } + + template <typename R> + static HalResult<T> fromReturn(hardware::Return<R>& ret, T& data) { + return HalResult<T>::fromReturn(ret, T{data}); } template <typename R> static HalResult<T> fromReturn(hardware::Return<R>& ret, hardware::power::V1_0::Status status, - T data) { - return ret.isOk() ? HalResult<T>::fromStatus(status, data) + T&& data) { + return ret.isOk() ? HalResult<T>::fromStatus(status, std::forward<T>(data)) : HalResult<T>::failed(ret.description()); } + template <typename R> + static HalResult<T> fromReturn(hardware::Return<R>& ret, hardware::power::V1_0::Status status, + T& data) { + return HalResult<T>::fromReturn(ret, status, T{data}); + } + // This will throw std::bad_optional_access if this result is not ok. const T& value() const { return mValue.value(); } bool isOk() const { return !mUnsupported && mValue.has_value(); } @@ -92,8 +114,8 @@ private: std::string mErrorMessage; bool mUnsupported; - explicit HalResult(T value) - : mValue(std::make_optional(value)), mErrorMessage(), mUnsupported(false) {} + explicit HalResult(T&& value) + : mValue{std::move(value)}, mErrorMessage(), mUnsupported(false) {} explicit HalResult(std::string errorMessage, bool unsupported) : mValue(), mErrorMessage(std::move(errorMessage)), mUnsupported(unsupported) {} }; @@ -158,7 +180,15 @@ public: virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos) = 0; + virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> + createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, + int64_t durationNanos, + aidl::android::hardware::power::SessionTag tag, + aidl::android::hardware::power::SessionConfig* config) = 0; virtual HalResult<int64_t> getHintSessionPreferredRate() = 0; + virtual HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid, + int uid) = 0; + virtual HalResult<void> closeSessionChannel(int tgid, int uid) = 0; }; // Empty Power HAL wrapper that ignores all api calls. @@ -173,11 +203,22 @@ public: HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession( int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos) override; + HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> + createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, + int64_t durationNanos, + aidl::android::hardware::power::SessionTag tag, + aidl::android::hardware::power::SessionConfig* config) override; HalResult<int64_t> getHintSessionPreferredRate() override; + HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid, + int uid) override; + HalResult<void> closeSessionChannel(int tgid, int uid) override; + +protected: + virtual const char* getUnsupportedMessage(); }; // Wrapper for the HIDL Power HAL v1.0. -class HidlHalWrapperV1_0 : public HalWrapper { +class HidlHalWrapperV1_0 : public EmptyHalWrapper { public: explicit HidlHalWrapperV1_0(sp<hardware::power::V1_0::IPower> handleV1_0) : mHandleV1_0(std::move(handleV1_0)) {} @@ -186,14 +227,11 @@ public: HalResult<void> setBoost(aidl::android::hardware::power::Boost boost, int32_t durationMs) override; HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override; - HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession( - int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, - int64_t durationNanos) override; - HalResult<int64_t> getHintSessionPreferredRate() override; protected: const sp<hardware::power::V1_0::IPower> mHandleV1_0; virtual HalResult<void> sendPowerHint(hardware::power::V1_3::PowerHint hintId, uint32_t data); + const char* getUnsupportedMessage(); private: HalResult<void> setInteractive(bool enabled); @@ -238,7 +276,7 @@ protected: }; // Wrapper for the AIDL Power HAL. -class AidlHalWrapper : public HalWrapper { +class AidlHalWrapper : public EmptyHalWrapper { public: explicit AidlHalWrapper(std::shared_ptr<aidl::android::hardware::power::IPower> handle) : mHandle(std::move(handle)) {} @@ -250,7 +288,19 @@ public: HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession( int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos) override; + HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> + createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, + int64_t durationNanos, + aidl::android::hardware::power::SessionTag tag, + aidl::android::hardware::power::SessionConfig* config) override; + HalResult<int64_t> getHintSessionPreferredRate() override; + HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid, + int uid) override; + HalResult<void> closeSessionChannel(int tgid, int uid) override; + +protected: + const char* getUnsupportedMessage() override; private: // Control access to the boost and mode supported arrays. @@ -274,5 +324,3 @@ private: }; // namespace power }; // namespace android - -#endif // ANDROID_POWERHALWRAPPER_H diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h index d50c5f846e..d8f9db4882 100644 --- a/include/private/performance_hint_private.h +++ b/include/private/performance_hint_private.h @@ -53,6 +53,26 @@ enum SessionHint: int32_t { * CPU resources to what was used previously, and must wake up if inactive. */ CPU_LOAD_RESUME = 3, + + /** + * This hint indicates an increase in GPU workload intensity. It means that + * this hint session needs extra GPU resources to meet the target duration. + * This hint must be sent before reporting the actual duration to the session. + */ + GPU_LOAD_UP = 5, + + /** + * This hint indicates a decrease in GPU workload intensity. It means that + * this hint session can reduce GPU resources and still meet the target duration. + */ + GPU_LOAD_DOWN = 6, + + /* + * This hint indicates an upcoming GPU workload that is completely changed and + * unknown. It means that the hint session should reset GPU resources to a known + * baseline to prepare for an arbitrary load, and must wake up if inactive. + */ + GPU_LOAD_RESET = 7, }; /** diff --git a/libs/battery/MultiStateCounter.h b/libs/battery/MultiStateCounter.h index ce9cd1cebe..7da8d51ccd 100644 --- a/libs/battery/MultiStateCounter.h +++ b/libs/battery/MultiStateCounter.h @@ -31,6 +31,8 @@ namespace android { namespace battery { +#define REPORTED_INVALID_TIMESTAMP_DELTA_MS 60000 + typedef uint16_t state_t; template <class T> @@ -171,8 +173,12 @@ void MultiStateCounter<T>::setState(state_t state, time_t timestamp) { if (timestamp >= lastStateChangeTimestamp) { states[currentState].timeInStateSinceUpdate += timestamp - lastStateChangeTimestamp; } else { - ALOGE("setState is called with an earlier timestamp: %lu, previous timestamp: %lu\n", - (unsigned long)timestamp, (unsigned long)lastStateChangeTimestamp); + if (timestamp < lastStateChangeTimestamp - REPORTED_INVALID_TIMESTAMP_DELTA_MS) { + ALOGE("setState is called with an earlier timestamp: %lu, " + "previous timestamp: %lu\n", + (unsigned long)timestamp, (unsigned long)lastStateChangeTimestamp); + } + // The accumulated durations have become unreliable. For example, if the timestamp // sequence was 1000, 2000, 1000, 3000, if we accumulated the positive deltas, // we would get 4000, which is greater than (last - first). This could lead to @@ -232,8 +238,10 @@ const T& MultiStateCounter<T>::updateValue(const T& value, time_t timestamp) { } } } else if (timestamp < lastUpdateTimestamp) { - ALOGE("updateValue is called with an earlier timestamp: %lu, previous: %lu\n", - (unsigned long)timestamp, (unsigned long)lastUpdateTimestamp); + if (timestamp < lastUpdateTimestamp - REPORTED_INVALID_TIMESTAMP_DELTA_MS) { + ALOGE("updateValue is called with an earlier timestamp: %lu, previous: %lu\n", + (unsigned long)timestamp, (unsigned long)lastUpdateTimestamp); + } for (int i = 0; i < stateCount; i++) { states[i].timeInStateSinceUpdate = 0; diff --git a/libs/bufferstreams/Android.bp b/libs/bufferstreams/Android.bp index 365fc457d1..6c2a980f71 100644 --- a/libs/bufferstreams/Android.bp +++ b/libs/bufferstreams/Android.bp @@ -19,6 +19,7 @@ package { aconfig_declarations { name: "bufferstreams_flags", package: "com.android.graphics.bufferstreams.flags", + container: "system", srcs: [ "aconfig/bufferstreams_flags.aconfig", ], diff --git a/libs/bufferstreams/aconfig/bufferstreams_flags.aconfig b/libs/bufferstreams/aconfig/bufferstreams_flags.aconfig index e258725e3d..d0f7812d21 100644 --- a/libs/bufferstreams/aconfig/bufferstreams_flags.aconfig +++ b/libs/bufferstreams/aconfig/bufferstreams_flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.graphics.bufferstreams.flags" +container: "system" flag { name: "bufferstreams_steel_thread" diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp index 918680d6a7..5ac965f566 100644 --- a/libs/ftl/Android.bp +++ b/libs/ftl/Android.bp @@ -10,11 +10,15 @@ package { cc_test { name: "ftl_test", test_suites: ["device-tests"], + header_libs: [ + "libbase_headers", + ], srcs: [ "algorithm_test.cpp", "cast_test.cpp", "concat_test.cpp", "enum_test.cpp", + "expected_test.cpp", "fake_guard_test.cpp", "flags_test.cpp", "function_test.cpp", diff --git a/libs/ftl/expected_test.cpp b/libs/ftl/expected_test.cpp new file mode 100644 index 0000000000..8cb07e4696 --- /dev/null +++ b/libs/ftl/expected_test.cpp @@ -0,0 +1,77 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <ftl/expected.h> +#include <gtest/gtest.h> + +#include <string> +#include <system_error> + +namespace android::test { + +using IntExp = ftl::Expected<int, std::errc>; +using StringExp = ftl::Expected<std::string, std::errc>; + +using namespace std::string_literals; + +TEST(Expected, Construct) { + // Default value. + EXPECT_TRUE(IntExp().has_value()); + EXPECT_EQ(IntExp(), IntExp(0)); + + EXPECT_TRUE(StringExp().has_value()); + EXPECT_EQ(StringExp(), StringExp("")); + + // Value. + ASSERT_TRUE(IntExp(42).has_value()); + EXPECT_EQ(42, IntExp(42).value()); + + ASSERT_TRUE(StringExp("test").has_value()); + EXPECT_EQ("test"s, StringExp("test").value()); + + // Error. + const auto exp = StringExp(ftl::Unexpected(std::errc::invalid_argument)); + ASSERT_FALSE(exp.has_value()); + EXPECT_EQ(std::errc::invalid_argument, exp.error()); +} + +TEST(Expected, HasError) { + EXPECT_FALSE(IntExp(123).has_error([](auto) { return true; })); + EXPECT_FALSE(IntExp(ftl::Unexpected(std::errc::io_error)).has_error([](auto) { return false; })); + + EXPECT_TRUE(StringExp(ftl::Unexpected(std::errc::permission_denied)).has_error([](auto e) { + return e == std::errc::permission_denied; + })); +} + +TEST(Expected, ValueOpt) { + EXPECT_EQ(ftl::Optional(-1), IntExp(-1).value_opt()); + EXPECT_EQ(std::nullopt, IntExp(ftl::Unexpected(std::errc::broken_pipe)).value_opt()); + + { + const StringExp exp("foo"s); + EXPECT_EQ(ftl::Optional('f'), + exp.value_opt().transform([](const auto& s) { return s.front(); })); + EXPECT_EQ("foo"s, exp.value()); + } + { + StringExp exp("foobar"s); + EXPECT_EQ(ftl::Optional(6), std::move(exp).value_opt().transform(&std::string::length)); + EXPECT_TRUE(exp.value().empty()); + } +} + +} // namespace android::test diff --git a/libs/ftl/future_test.cpp b/libs/ftl/future_test.cpp index 5a245b681c..1140639c87 100644 --- a/libs/ftl/future_test.cpp +++ b/libs/ftl/future_test.cpp @@ -102,4 +102,42 @@ TEST(Future, Chain) { decrement_thread.join(); } +TEST(Future, WaitFor) { + using namespace std::chrono_literals; + { + auto future = ftl::yield(42); + // Check that we can wait_for multiple times without invalidating the future + EXPECT_EQ(future.wait_for(1s), std::future_status::ready); + EXPECT_EQ(future.wait_for(1s), std::future_status::ready); + EXPECT_EQ(future.get(), 42); + } + + { + std::condition_variable cv; + std::mutex m; + bool ready = false; + + std::packaged_task<int32_t()> get_int([&] { + std::unique_lock lk(m); + cv.wait(lk, [&] { return ready; }); + return 24; + }); + + auto get_future = ftl::Future(get_int.get_future()); + std::thread get_thread(std::move(get_int)); + + EXPECT_EQ(get_future.wait_for(0s), std::future_status::timeout); + { + std::unique_lock lk(m); + ready = true; + } + cv.notify_one(); + + EXPECT_EQ(get_future.wait_for(1s), std::future_status::ready); + EXPECT_EQ(get_future.get(), 24); + + get_thread.join(); + } +} + } // namespace android::test diff --git a/libs/ftl/small_map_test.cpp b/libs/ftl/small_map_test.cpp index 634877f672..e96d70d8ad 100644 --- a/libs/ftl/small_map_test.cpp +++ b/libs/ftl/small_map_test.cpp @@ -189,9 +189,20 @@ TEST(SmallMap, Get) { } } -TEST(SmallMap, TryEmplace) { - SmallMap<int, std::string, 3> map; - using Pair = decltype(map)::value_type; +template <typename Capacity> +struct SmallMapTest : testing::Test { + static constexpr std::size_t kCapacity = Capacity{}(); +}; + +template <std::size_t N> +using Capacity = std::integral_constant<std::size_t, N>; + +using Capacities = testing::Types<Capacity<3>, Capacity<0>>; +TYPED_TEST_SUITE(SmallMapTest, Capacities, ); + +TYPED_TEST(SmallMapTest, TryEmplace) { + SmallMap<int, std::string, TestFixture::kCapacity> map; + using Pair = typename decltype(map)::value_type; { const auto [it, ok] = map.try_emplace(123, "abc"); @@ -207,14 +218,22 @@ TEST(SmallMap, TryEmplace) { const auto [it, ok] = map.try_emplace(-1); ASSERT_TRUE(ok); EXPECT_EQ(*it, Pair(-1, std::string())); - EXPECT_FALSE(map.dynamic()); + if constexpr (map.static_capacity() > 0) { + EXPECT_FALSE(map.dynamic()); + } else { + EXPECT_TRUE(map.dynamic()); + } } { // Insertion fails if mapping exists. const auto [it, ok] = map.try_emplace(42, "!!!"); EXPECT_FALSE(ok); EXPECT_EQ(*it, Pair(42, "???")); - EXPECT_FALSE(map.dynamic()); + if constexpr (map.static_capacity() > 0) { + EXPECT_FALSE(map.dynamic()); + } else { + EXPECT_TRUE(map.dynamic()); + } } { // Insertion at capacity promotes the map. @@ -240,9 +259,9 @@ struct String { } // namespace -TEST(SmallMap, TryReplace) { - SmallMap<int, String, 3> map = ftl::init::map(1, "a")(2, "B"); - using Pair = decltype(map)::value_type; +TYPED_TEST(SmallMapTest, TryReplace) { + SmallMap<int, String, TestFixture::kCapacity> map = ftl::init::map(1, "a")(2, "B"); + using Pair = typename decltype(map)::value_type; { // Replacing fails unless mapping exists. @@ -260,7 +279,12 @@ TEST(SmallMap, TryReplace) { EXPECT_EQ(*it, Pair(2, "b")); } - EXPECT_FALSE(map.dynamic()); + if constexpr (map.static_capacity() > 0) { + EXPECT_FALSE(map.dynamic()); + } else { + EXPECT_TRUE(map.dynamic()); + } + EXPECT_TRUE(map.try_emplace(3, "abc").second); EXPECT_TRUE(map.try_emplace(4, "d").second); EXPECT_TRUE(map.dynamic()); @@ -284,9 +308,9 @@ TEST(SmallMap, TryReplace) { EXPECT_EQ(map, SmallMap(ftl::init::map(4, "d"s)(3, "c"s)(2, "b"s)(1, "a"s))); } -TEST(SmallMap, EmplaceOrReplace) { - SmallMap<int, String, 3> map = ftl::init::map(1, "a")(2, "B"); - using Pair = decltype(map)::value_type; +TYPED_TEST(SmallMapTest, EmplaceOrReplace) { + SmallMap<int, String, TestFixture::kCapacity> map = ftl::init::map(1, "a")(2, "B"); + using Pair = typename decltype(map)::value_type; { // New mapping is emplaced. @@ -305,7 +329,12 @@ TEST(SmallMap, EmplaceOrReplace) { EXPECT_EQ(*it, Pair(2, "b")); } - EXPECT_FALSE(map.dynamic()); + if constexpr (map.static_capacity() > 0) { + EXPECT_FALSE(map.dynamic()); + } else { + EXPECT_TRUE(map.dynamic()); + } + EXPECT_FALSE(map.emplace_or_replace(3, "abc").second); // Replace. EXPECT_TRUE(map.emplace_or_replace(4, "d").second); // Emplace. EXPECT_TRUE(map.dynamic()); diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index eb4d3df21d..4c3cc6cc3d 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -23,6 +23,7 @@ package { aconfig_declarations { name: "libgui_flags", package: "com.android.graphics.libgui.flags", + container: "system", srcs: ["libgui_flags.aconfig"], } diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp index 19693e37cf..fb69fda32d 100644 --- a/libs/gui/BufferQueueProducer.cpp +++ b/libs/gui/BufferQueueProducer.cpp @@ -887,6 +887,9 @@ status_t BufferQueueProducer::queueBuffer(int slot, int callbackTicket = 0; uint64_t currentFrameNumber = 0; BufferItem item; + int connectedApi; + sp<Fence> lastQueuedFence; + { // Autolock scope std::lock_guard<std::mutex> lock(mCore->mMutex); @@ -1056,6 +1059,13 @@ status_t BufferQueueProducer::queueBuffer(int slot, callbackTicket = mNextCallbackTicket++; VALIDATE_CONSISTENCY(); + + connectedApi = mCore->mConnectedApi; + lastQueuedFence = std::move(mLastQueueBufferFence); + + mLastQueueBufferFence = std::move(acquireFence); + mLastQueuedCrop = item.mCrop; + mLastQueuedTransform = item.mTransform; } // Autolock scope // It is okay not to clear the GraphicBuffer when the consumer is SurfaceFlinger because @@ -1079,9 +1089,6 @@ status_t BufferQueueProducer::queueBuffer(int slot, // Call back without the main BufferQueue lock held, but with the callback // lock held so we can ensure that callbacks occur in order - int connectedApi; - sp<Fence> lastQueuedFence; - { // scope for the lock std::unique_lock<std::mutex> lock(mCallbackMutex); while (callbackTicket != mCurrentCallbackTicket) { @@ -1094,13 +1101,6 @@ status_t BufferQueueProducer::queueBuffer(int slot, frameReplacedListener->onFrameReplaced(item); } - connectedApi = mCore->mConnectedApi; - lastQueuedFence = std::move(mLastQueueBufferFence); - - mLastQueueBufferFence = std::move(acquireFence); - mLastQueuedCrop = item.mCrop; - mLastQueuedTransform = item.mTransform; - ++mCurrentCallbackTicket; mCallbackCondition.notify_all(); } @@ -1653,9 +1653,10 @@ status_t BufferQueueProducer::setLegacyBufferDrop(bool drop) { status_t BufferQueueProducer::getLastQueuedBuffer(sp<GraphicBuffer>* outBuffer, sp<Fence>* outFence, float outTransformMatrix[16]) { ATRACE_CALL(); - BQ_LOGV("getLastQueuedBuffer"); std::lock_guard<std::mutex> lock(mCore->mMutex); + BQ_LOGV("getLastQueuedBuffer, slot=%d", mCore->mLastQueuedSlot); + if (mCore->mLastQueuedSlot == BufferItem::INVALID_BUFFER_SLOT) { *outBuffer = nullptr; *outFence = Fence::NO_FENCE; @@ -1679,10 +1680,11 @@ status_t BufferQueueProducer::getLastQueuedBuffer(sp<GraphicBuffer>* outBuffer, status_t BufferQueueProducer::getLastQueuedBuffer(sp<GraphicBuffer>* outBuffer, sp<Fence>* outFence, Rect* outRect, uint32_t* outTransform) { ATRACE_CALL(); - BQ_LOGV("getLastQueuedBuffer"); std::lock_guard<std::mutex> lock(mCore->mMutex); - if (mCore->mLastQueuedSlot == BufferItem::INVALID_BUFFER_SLOT) { + BQ_LOGV("getLastQueuedBuffer, slot=%d", mCore->mLastQueuedSlot); + if (mCore->mLastQueuedSlot == BufferItem::INVALID_BUFFER_SLOT || + mSlots[mCore->mLastQueuedSlot].mBufferState.isDequeued()) { *outBuffer = nullptr; *outFence = Fence::NO_FENCE; return NO_ERROR; diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp index 79c5fd1006..4518b67d4c 100644 --- a/libs/gui/Choreographer.cpp +++ b/libs/gui/Choreographer.cpp @@ -344,6 +344,13 @@ void Choreographer::dispatchNullEvent(nsecs_t, PhysicalDisplayId) { handleRefreshRateUpdates(); } +void Choreographer::dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel, + int32_t maxLevel) { + ALOGV("choreographer %p ~ received hdcp levels change event (displayId=%s, connectedLevel=%d, " + "maxLevel=%d), ignoring.", + this, to_string(displayId).c_str(), connectedLevel, maxLevel); +} + void Choreographer::handleMessage(const Message& message) { switch (message.what) { case MSG_SCHEDULE_CALLBACKS: diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp index 5dd058cf9f..f3de96d2cd 100644 --- a/libs/gui/DisplayEventDispatcher.cpp +++ b/libs/gui/DisplayEventDispatcher.cpp @@ -195,6 +195,11 @@ bool DisplayEventDispatcher::processPendingEvents(nsecs_t* outTimestamp, dispatchFrameRateOverrides(ev.header.timestamp, ev.header.displayId, std::move(mFrameRateOverrides)); break; + case DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE: + dispatchHdcpLevelsChanged(ev.header.displayId, + ev.hdcpLevelsChange.connectedLevel, + ev.hdcpLevelsChange.maxLevel); + break; default: ALOGW("dispatcher %p ~ ignoring unknown event type %#x", this, ev.header.type); break; diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 38fab9cdaa..1e0aacddab 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -199,7 +199,7 @@ status_t layer_state_t::write(Parcel& output) const SAFE_PARCEL(output.writeParcelable, trustedPresentationListener); SAFE_PARCEL(output.writeFloat, currentHdrSdrRatio); SAFE_PARCEL(output.writeFloat, desiredHdrSdrRatio); - SAFE_PARCEL(output.writeInt32, static_cast<int32_t>(cachingHint)) + SAFE_PARCEL(output.writeInt32, static_cast<int32_t>(cachingHint)); return NO_ERROR; } @@ -484,6 +484,12 @@ void layer_state_t::sanitize(int32_t permissions) { flags &= ~eLayerIsDisplayDecoration; ALOGE("Stripped attempt to set LayerIsDisplayDecoration in sanitize"); } + if ((mask & eCanOccludePresentation) && + !(permissions & Permission::ACCESS_SURFACE_FLINGER)) { + flags &= ~eCanOccludePresentation; + mask &= ~eCanOccludePresentation; + ALOGE("Stripped attempt to set eCanOccludePresentation in sanitize"); + } } if (what & layer_state_t::eInputInfoChanged) { @@ -605,6 +611,10 @@ void layer_state_t::merge(const layer_state_t& other) { desiredHdrSdrRatio = other.desiredHdrSdrRatio; currentHdrSdrRatio = other.currentHdrSdrRatio; } + if (other.what & eDesiredHdrHeadroomChanged) { + what |= eDesiredHdrHeadroomChanged; + desiredHdrSdrRatio = other.desiredHdrSdrRatio; + } if (other.what & eCachingHintChanged) { what |= eCachingHintChanged; cachingHint = other.cachingHint; @@ -768,6 +778,7 @@ uint64_t layer_state_t::diff(const layer_state_t& other) const { CHECK_DIFF(diff, eDataspaceChanged, other, dataspace); CHECK_DIFF2(diff, eExtendedRangeBrightnessChanged, other, currentHdrSdrRatio, desiredHdrSdrRatio); + CHECK_DIFF(diff, eDesiredHdrHeadroomChanged, other, desiredHdrSdrRatio); CHECK_DIFF(diff, eCachingHintChanged, other, cachingHint); CHECK_DIFF(diff, eHdrMetadataChanged, other, hdrMetadata); if (other.what & eSurfaceDamageRegionChanged && diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index 07a0cfed63..086544e48a 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -342,12 +342,23 @@ status_t Surface::getFrameTimestamps(uint64_t frameNumber, getFrameTimestamp(outRequestedPresentTime, events->requestedPresentTime); getFrameTimestamp(outLatchTime, events->latchTime); - getFrameTimestamp(outFirstRefreshStartTime, events->firstRefreshStartTime); + + nsecs_t firstRefreshStartTime = NATIVE_WINDOW_TIMESTAMP_INVALID; + getFrameTimestamp(&firstRefreshStartTime, events->firstRefreshStartTime); + if (outFirstRefreshStartTime) { + *outFirstRefreshStartTime = firstRefreshStartTime; + } + getFrameTimestamp(outLastRefreshStartTime, events->lastRefreshStartTime); getFrameTimestamp(outDequeueReadyTime, events->dequeueReadyTime); - getFrameTimestampFence(outAcquireTime, events->acquireFence, + nsecs_t acquireTime = NATIVE_WINDOW_TIMESTAMP_INVALID; + getFrameTimestampFence(&acquireTime, events->acquireFence, events->hasAcquireInfo()); + if (outAcquireTime != nullptr) { + *outAcquireTime = acquireTime; + } + getFrameTimestampFence(outGpuCompositionDoneTime, events->gpuCompositionDoneFence, events->hasGpuCompositionDoneInfo()); @@ -356,6 +367,16 @@ status_t Surface::getFrameTimestamps(uint64_t frameNumber, getFrameTimestampFence(outReleaseTime, events->releaseFence, events->hasReleaseInfo()); + // Fix up the GPU completion fence at this layer -- eglGetFrameTimestampsANDROID() expects + // that EGL_FIRST_COMPOSITION_GPU_FINISHED_TIME_ANDROID > EGL_RENDERING_COMPLETE_TIME_ANDROID. + // This is typically true, but SurfaceFlinger may opt to cache prior GPU composition results, + // which breaks that assumption, so zero out GPU composition time. + if (outGpuCompositionDoneTime != nullptr + && *outGpuCompositionDoneTime > 0 && (acquireTime > 0 || firstRefreshStartTime > 0) + && *outGpuCompositionDoneTime <= std::max(acquireTime, firstRefreshStartTime)) { + *outGpuCompositionDoneTime = 0; + } + return NO_ERROR; } diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 8b6f2023dc..4f1356bebb 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -56,6 +56,7 @@ #include <android-base/thread_annotations.h> #include <gui/LayerStatePermissions.h> +#include <gui/ScreenCaptureResults.h> #include <private/gui/ComposerService.h> #include <private/gui/ComposerServiceAIDL.h> @@ -1807,6 +1808,20 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setExten return *this; } +SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDesiredHdrHeadroom( + const sp<SurfaceControl>& sc, float desiredRatio) { + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + s->what |= layer_state_t::eDesiredHdrHeadroomChanged; + s->desiredHdrSdrRatio = desiredRatio; + + registerSurfaceControlForCallback(sc); + return *this; +} + SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setCachingHint( const sp<SurfaceControl>& sc, gui::CachingHint cachingHint) { layer_state_t* s = getLayerState(sc); @@ -3117,7 +3132,6 @@ status_t SurfaceComposerClient::removeWindowInfosListener( ->removeWindowInfosListener(windowInfosListener, ComposerServiceAIDL::getComposerService()); } - // ---------------------------------------------------------------------------- status_t ScreenshotClient::captureDisplay(const DisplayCaptureArgs& captureArgs, @@ -3139,11 +3153,19 @@ status_t ScreenshotClient::captureDisplay(DisplayId displayId, const gui::Captur } status_t ScreenshotClient::captureLayers(const LayerCaptureArgs& captureArgs, - const sp<IScreenCaptureListener>& captureListener) { + const sp<IScreenCaptureListener>& captureListener, + bool sync) { sp<gui::ISurfaceComposer> s(ComposerServiceAIDL::getComposerService()); if (s == nullptr) return NO_INIT; - binder::Status status = s->captureLayers(captureArgs, captureListener); + binder::Status status; + if (sync) { + gui::ScreenCaptureResults captureResults; + status = s->captureLayersSync(captureArgs, &captureResults); + captureListener->onScreenCaptureCompleted(captureResults); + } else { + status = s->captureLayers(captureArgs, captureListener); + } return statusTFromBinderStatus(status); } diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp index ba1d196e9c..9429d2cc15 100644 --- a/libs/gui/WindowInfo.cpp +++ b/libs/gui/WindowInfo.cpp @@ -109,7 +109,8 @@ bool WindowInfo::operator==(const WindowInfo& info) const { info.inputConfig == inputConfig && info.displayId == displayId && info.replaceTouchableRegionWithCrop == replaceTouchableRegionWithCrop && info.applicationInfo == applicationInfo && info.layoutParamsType == layoutParamsType && - info.layoutParamsFlags == layoutParamsFlags; + info.layoutParamsFlags == layoutParamsFlags && + info.canOccludePresentation == canOccludePresentation; } status_t WindowInfo::writeToParcel(android::Parcel* parcel) const { @@ -158,8 +159,9 @@ status_t WindowInfo::writeToParcel(android::Parcel* parcel) const { parcel->write(touchableRegion) ?: parcel->writeBool(replaceTouchableRegionWithCrop) ?: parcel->writeStrongBinder(touchableRegionCropHandle.promote()) ?: - parcel->writeStrongBinder(windowToken); - parcel->writeStrongBinder(focusTransferTarget); + parcel->writeStrongBinder(windowToken) ?: + parcel->writeStrongBinder(focusTransferTarget) ?: + parcel->writeBool(canOccludePresentation); // clang-format on return status; } @@ -210,7 +212,8 @@ status_t WindowInfo::readFromParcel(const android::Parcel* parcel) { parcel->readBool(&replaceTouchableRegionWithCrop) ?: parcel->readNullableStrongBinder(&touchableRegionCropHandleSp) ?: parcel->readNullableStrongBinder(&windowToken) ?: - parcel->readNullableStrongBinder(&focusTransferTarget); + parcel->readNullableStrongBinder(&focusTransferTarget) ?: + parcel->readBool(&canOccludePresentation); // clang-format on @@ -258,8 +261,7 @@ void WindowInfoHandle::updateFrom(sp<WindowInfoHandle> handle) { mInfo = handle->mInfo; } -std::ostream& operator<<(std::ostream& out, const WindowInfoHandle& window) { - const WindowInfo& info = *window.getInfo(); +std::ostream& operator<<(std::ostream& out, const WindowInfo& info) { std::string transform; info.transform.dump(transform, "transform", " "); out << "name=" << info.name << ", id=" << info.id << ", displayId=" << info.displayId @@ -274,6 +276,13 @@ std::ostream& operator<<(std::ostream& out, const WindowInfoHandle& window) { << "ms, token=" << info.token.get() << ", touchOcclusionMode=" << ftl::enum_string(info.touchOcclusionMode) << "\n" << transform; + if (info.canOccludePresentation) out << " canOccludePresentation"; + return out; +} + +std::ostream& operator<<(std::ostream& out, const WindowInfoHandle& window) { + const WindowInfo& info = *window.getInfo(); + out << info; return out; } diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index e3122bc300..51e01930d3 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -46,6 +46,7 @@ import android.gui.LayerCaptureArgs; import android.gui.LayerDebugInfo; import android.gui.OverlayProperties; import android.gui.PullAtomData; +import android.gui.ScreenCaptureResults; import android.gui.ARect; import android.gui.SchedulingPolicy; import android.gui.StalledTransactionInfo; @@ -245,6 +246,16 @@ interface ISurfaceComposer { /** * Capture a subtree of the layer hierarchy, potentially ignoring the root node. * This requires READ_FRAME_BUFFER permission. This function will fail if there + * is a secure window on screen. This is a blocking call and will return the + * ScreenCaptureResults, including the captured buffer. Because this is blocking, the + * caller doesn't care about the fence and the binder thread in SurfaceFlinger will wait + * on the fence to fire before returning the results. + */ + ScreenCaptureResults captureLayersSync(in LayerCaptureArgs args); + + /** + * Capture a subtree of the layer hierarchy, potentially ignoring the root node. + * This requires READ_FRAME_BUFFER permission. This function will fail if there * is a secure window on screen */ oneway void captureLayers(in LayerCaptureArgs args, IScreenCaptureListener listener); diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h index 9933680c4b..2bdbd43233 100644 --- a/libs/gui/fuzzer/libgui_fuzzer_utils.h +++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h @@ -104,6 +104,8 @@ public: (int64_t, const gui::CaptureArgs&, const sp<IScreenCaptureListener>&), (override)); MOCK_METHOD(binder::Status, captureLayers, (const LayerCaptureArgs&, const sp<IScreenCaptureListener>&), (override)); + MOCK_METHOD(binder::Status, captureLayersSync, + (const LayerCaptureArgs&, gui::ScreenCaptureResults*), (override)); MOCK_METHOD(binder::Status, clearAnimationFrameStats, (), (override)); MOCK_METHOD(binder::Status, getAnimationFrameStats, (gui::FrameStats*), (override)); MOCK_METHOD(binder::Status, overrideHdrTypes, (const sp<IBinder>&, const std::vector<int32_t>&), @@ -207,6 +209,7 @@ public: MOCK_METHOD2(dispatchNullEvent, void(nsecs_t, PhysicalDisplayId)); MOCK_METHOD3(dispatchFrameRateOverrides, void(nsecs_t, PhysicalDisplayId, std::vector<FrameRateOverride>)); + MOCK_METHOD3(dispatchHdcpLevelsChanged, void(PhysicalDisplayId, int32_t, int32_t)); }; } // namespace android diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h index 9fef512b64..55a7aa7ddc 100644 --- a/libs/gui/include/gui/Choreographer.h +++ b/libs/gui/include/gui/Choreographer.h @@ -116,6 +116,8 @@ private: void dispatchNullEvent(nsecs_t, PhysicalDisplayId) override; void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId, std::vector<FrameRateOverride> overrides) override; + void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel, + int32_t maxLevel) override; void scheduleCallbacks(); diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h index fe2dd206ed..82cd50c7bd 100644 --- a/libs/gui/include/gui/DisplayEventDispatcher.h +++ b/libs/gui/include/gui/DisplayEventDispatcher.h @@ -65,6 +65,9 @@ private: virtual void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId, std::vector<FrameRateOverride> overrides) = 0; + virtual void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel, + int32_t maxLevel) = 0; + bool processPendingEvents(nsecs_t* outTimestamp, PhysicalDisplayId* outDisplayId, uint32_t* outCount, VsyncEventData* outVsyncEventData); diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h index 79582ce685..8c1103bfc2 100644 --- a/libs/gui/include/gui/DisplayEventReceiver.h +++ b/libs/gui/include/gui/DisplayEventReceiver.h @@ -58,7 +58,6 @@ static inline constexpr uint32_t fourcc(char c1, char c2, char c3, char c4) { // ---------------------------------------------------------------------------- class DisplayEventReceiver { public: - enum { DISPLAY_EVENT_VSYNC = fourcc('v', 's', 'y', 'n'), DISPLAY_EVENT_HOTPLUG = fourcc('p', 'l', 'u', 'g'), @@ -66,6 +65,7 @@ public: DISPLAY_EVENT_NULL = fourcc('n', 'u', 'l', 'l'), DISPLAY_EVENT_FRAME_RATE_OVERRIDE = fourcc('r', 'a', 't', 'e'), DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH = fourcc('f', 'l', 's', 'h'), + DISPLAY_EVENT_HDCP_LEVELS_CHANGE = fourcc('h', 'd', 'c', 'p'), }; struct Event { @@ -101,12 +101,22 @@ public: float frameRateHz __attribute__((aligned(8))); }; + /* + * The values are defined in aidl: + * hardware/interfaces/drm/aidl/android/hardware/drm/HdcpLevel.aidl + */ + struct HdcpLevelsChange { + int32_t connectedLevel; + int32_t maxLevel; + }; + Header header; union { VSync vsync; Hotplug hotplug; ModeChange modeChange; FrameRateOverride frameRateOverride; + HdcpLevelsChange hdcpLevelsChange; }; }; diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index e1dc7911a1..0fedea7b9e 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -161,6 +161,9 @@ struct layer_state_t { // See SurfaceView scaling behavior for more details. eIgnoreDestinationFrame = 0x400, eLayerIsRefreshRateIndicator = 0x800, // REFRESH_RATE_INDICATOR + // Sets a property on this layer indicating that its visible region should be considered + // when computing TrustedPresentation Thresholds. + eCanOccludePresentation = 0x1000, }; enum { @@ -206,7 +209,7 @@ struct layer_state_t { eBackgroundBlurRadiusChanged = 0x80'00000000, eProducerDisconnect = 0x100'00000000, eFixedTransformHintChanged = 0x200'00000000, - /* unused 0x400'00000000, */ + eDesiredHdrHeadroomChanged = 0x400'00000000, eBlurRegionsChanged = 0x800'00000000, eAutoRefreshChanged = 0x1000'00000000, eStretchChanged = 0x2000'00000000, @@ -245,7 +248,8 @@ struct layer_state_t { layer_state_t::eSidebandStreamChanged | layer_state_t::eSurfaceDamageRegionChanged | layer_state_t::eTransformToDisplayInverseChanged | layer_state_t::eTransparentRegionChanged | - layer_state_t::eExtendedRangeBrightnessChanged; + layer_state_t::eExtendedRangeBrightnessChanged | + layer_state_t::eDesiredHdrHeadroomChanged; // Content updates. static constexpr uint64_t CONTENT_CHANGES = layer_state_t::BUFFER_CHANGES | @@ -416,26 +420,36 @@ public: }; struct DisplayState { - enum { + enum : uint32_t { eSurfaceChanged = 0x01, eLayerStackChanged = 0x02, eDisplayProjectionChanged = 0x04, eDisplaySizeChanged = 0x08, - eFlagsChanged = 0x10 + eFlagsChanged = 0x10, + + eAllChanged = ~0u }; + // Not for direct use. Prefer constructor below for new displays. DisplayState(); + + DisplayState(sp<IBinder> token, ui::LayerStack layerStack) + : what(eAllChanged), + token(std::move(token)), + layerStack(layerStack), + layerStackSpaceRect(Rect::INVALID_RECT), + orientedDisplaySpaceRect(Rect::INVALID_RECT) {} + void merge(const DisplayState& other); void sanitize(int32_t permissions); uint32_t what = 0; uint32_t flags = 0; sp<IBinder> token; - sp<IGraphicBufferProducer> surface; ui::LayerStack layerStack = ui::DEFAULT_LAYER_STACK; - // These states define how layers are projected onto the physical display. + // These states define how layers are projected onto the physical or virtual display. // // Layers are first clipped to `layerStackSpaceRect'. They are then translated and // scaled from `layerStackSpaceRect' to `orientedDisplaySpaceRect'. Finally, they are rotated @@ -446,10 +460,17 @@ struct DisplayState { // will be scaled by a factor of 2 and translated by (20, 10). When orientation is 1, layers // will be additionally rotated by 90 degrees around the origin clockwise and translated by (W, // 0). + // + // Rect::INVALID_RECT sizes the space to the active resolution of the physical display, or the + // default dimensions of the virtual display surface. + // ui::Rotation orientation = ui::ROTATION_0; Rect layerStackSpaceRect = Rect::EMPTY_RECT; Rect orientedDisplaySpaceRect = Rect::EMPTY_RECT; + // Exclusive to virtual displays: The sink surface into which the virtual display is rendered, + // and an optional resolution that overrides its default dimensions. + sp<IGraphicBufferProducer> surface; uint32_t width = 0; uint32_t height = 0; diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 14e3dd583e..288882695a 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -594,6 +594,7 @@ public: Transaction& setDataspace(const sp<SurfaceControl>& sc, ui::Dataspace dataspace); Transaction& setExtendedRangeBrightness(const sp<SurfaceControl>& sc, float currentBufferRatio, float desiredRatio); + Transaction& setDesiredHdrHeadroom(const sp<SurfaceControl>& sc, 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, @@ -849,7 +850,8 @@ public: static status_t captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&); static status_t captureDisplay(DisplayId, const gui::CaptureArgs&, const sp<IScreenCaptureListener>&); - static status_t captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&); + static status_t captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&, + bool sync); [[deprecated]] static status_t captureDisplay(DisplayId id, const sp<IScreenCaptureListener>& listener) { diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h index 4d4c5e4394..32d60be612 100644 --- a/libs/gui/include/gui/WindowInfo.h +++ b/libs/gui/include/gui/WindowInfo.h @@ -176,6 +176,8 @@ struct WindowInfo : public Parcelable { static_cast<uint32_t>(os::InputConfig::INTERCEPTS_STYLUS), CLONE = static_cast<uint32_t>(os::InputConfig::CLONE), + GLOBAL_STYLUS_BLOCKS_TOUCH = + static_cast<uint32_t>(os::InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH), // clang-format on }; @@ -244,6 +246,10 @@ struct WindowInfo : public Parcelable { // any other window. sp<IBinder> focusTransferTarget; + // Sets a property on this window indicating that its visible region should be considered when + // computing TrustedPresentation Thresholds. + bool canOccludePresentation = false; + void setInputConfig(ftl::Flags<InputConfig> config, bool value); void addTouchableRegion(const Rect& region); @@ -267,6 +273,8 @@ struct WindowInfo : public Parcelable { status_t readFromParcel(const android::Parcel* parcel) override; }; +std::ostream& operator<<(std::ostream& out, const WindowInfo& window); + /* * Handle for a window that can receive input. * diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig index b081030c9f..38646992da 100644 --- a/libs/gui/libgui_flags.aconfig +++ b/libs/gui/libgui_flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.graphics.libgui.flags" +container: "system" flag { name: "bq_setframerate" diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp index d4b8dbeeb9..a9d6e8d3bf 100644 --- a/libs/gui/tests/EndToEndNativeInputTest.cpp +++ b/libs/gui/tests/EndToEndNativeInputTest.cpp @@ -63,8 +63,7 @@ namespace android::test { using Transaction = SurfaceComposerClient::Transaction; sp<IInputFlinger> getInputFlinger() { - sp<IBinder> input(defaultServiceManager()->getService( - String16("inputflinger"))); + sp<IBinder> input(defaultServiceManager()->waitForService(String16("inputflinger"))); if (input == nullptr) { ALOGE("Failed to link to input service"); } else { ALOGE("Linked to input"); } @@ -104,8 +103,13 @@ public: if (noInputChannel) { mInputInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, true); } else { - mClientChannel = std::make_shared<InputChannel>(); - mInputFlinger->createInputChannel("testchannels", mClientChannel.get()); + android::os::InputChannelCore tempChannel; + android::binder::Status result = + mInputFlinger->createInputChannel("testchannels", &tempChannel); + if (!result.isOk()) { + ADD_FAILURE() << "binder call to createInputChannel failed"; + } + mClientChannel = InputChannel::create(std::move(tempChannel)); mInputInfo.token = mClientChannel->getConnectionToken(); mInputConsumer = new InputConsumer(mClientChannel); } @@ -168,8 +172,8 @@ public: return std::make_unique<InputSurface>(surfaceControl, width, height); } - InputEvent *consumeEvent(int timeoutMs = 3000) { - waitForEventAvailable(timeoutMs); + InputEvent* consumeEvent(std::chrono::milliseconds timeout = 3000ms) { + mClientChannel->waitForMessage(timeout); InputEvent *ev; uint32_t seqId; @@ -302,15 +306,6 @@ public: t.apply(true); } -private: - void waitForEventAvailable(int timeoutMs) { - struct pollfd fd; - - fd.fd = mClientChannel->getFd(); - fd.events = POLLIN; - poll(&fd, 1, timeoutMs); - } - public: sp<SurfaceControl> mSurfaceControl; std::shared_ptr<InputChannel> mClientChannel; @@ -615,7 +610,7 @@ TEST_F(InputSurfacesTest, touchable_region) { // A tap within the surface but outside the touchable region should not be sent to the surface. injectTap(20, 30); - EXPECT_EQ(surface->consumeEvent(200 /*timeoutMs*/), nullptr); + EXPECT_EQ(surface->consumeEvent(/*timeout=*/200ms), nullptr); injectTap(31, 52); surface->expectTap(20, 30); @@ -981,12 +976,12 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_obscured_window) { obscuringSurface->mInputInfo.ownerUid = gui::Uid{22222}; obscuringSurface->showAt(100, 100); injectTap(101, 101); - EXPECT_EQ(surface->consumeEvent(100), nullptr); + EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); surface->requestFocus(); surface->assertFocusChange(true); injectKey(AKEYCODE_V); - EXPECT_EQ(surface->consumeEvent(100), nullptr); + EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); } TEST_F(InputSurfacesTest, strict_unobscured_input_partially_obscured_window) { @@ -1002,12 +997,12 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_partially_obscured_window) { injectTap(101, 101); - EXPECT_EQ(surface->consumeEvent(100), nullptr); + EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); surface->requestFocus(); surface->assertFocusChange(true); injectKey(AKEYCODE_V); - EXPECT_EQ(surface->consumeEvent(100), nullptr); + EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); } TEST_F(InputSurfacesTest, strict_unobscured_input_alpha_window) { @@ -1024,12 +1019,12 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_alpha_window) { injectTap(101, 101); - EXPECT_EQ(surface->consumeEvent(100), nullptr); + EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); surface->requestFocus(); surface->assertFocusChange(true); injectKey(AKEYCODE_V); - EXPECT_EQ(surface->consumeEvent(100), nullptr); + EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); } TEST_F(InputSurfacesTest, strict_unobscured_input_cropped_window) { @@ -1046,12 +1041,12 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_cropped_window) { injectTap(111, 111); - EXPECT_EQ(surface->consumeEvent(100), nullptr); + EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); surface->requestFocus(); surface->assertFocusChange(true); injectKey(AKEYCODE_V); - EXPECT_EQ(surface->consumeEvent(100), nullptr); + EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); } TEST_F(InputSurfacesTest, ignore_touch_region_with_zero_sized_blast) { @@ -1076,12 +1071,12 @@ TEST_F(InputSurfacesTest, drop_input_policy) { injectTap(101, 101); - EXPECT_EQ(surface->consumeEvent(100), nullptr); + EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); surface->requestFocus(); surface->assertFocusChange(true); injectKey(AKEYCODE_V); - EXPECT_EQ(surface->consumeEvent(100), nullptr); + EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); } TEST_F(InputSurfacesTest, layer_with_valid_crop_can_be_focused) { @@ -1116,7 +1111,7 @@ TEST_F(InputSurfacesTest, cropped_container_replaces_touchable_region_with_null_ // Does not receive events outside its crop injectTap(26, 26); - EXPECT_EQ(containerSurface->consumeEvent(100), nullptr); + EXPECT_EQ(containerSurface->consumeEvent(/*timeout=*/100ms), nullptr); } /** @@ -1141,7 +1136,7 @@ TEST_F(InputSurfacesTest, uncropped_container_replaces_touchable_region_with_nul // Does not receive events outside parent bounds injectTap(31, 31); - EXPECT_EQ(containerSurface->consumeEvent(100), nullptr); + EXPECT_EQ(containerSurface->consumeEvent(/*timeout=*/100ms), nullptr); } /** @@ -1167,7 +1162,7 @@ TEST_F(InputSurfacesTest, replace_touchable_region_with_crop) { // Does not receive events outside crop layer bounds injectTap(21, 21); injectTap(71, 71); - EXPECT_EQ(containerSurface->consumeEvent(100), nullptr); + EXPECT_EQ(containerSurface->consumeEvent(/*timeout=*/100ms), nullptr); } TEST_F(InputSurfacesTest, child_container_with_no_input_channel_blocks_parent) { @@ -1184,7 +1179,7 @@ TEST_F(InputSurfacesTest, child_container_with_no_input_channel_blocks_parent) { [&](auto &t, auto &sc) { t.reparent(sc, parent->mSurfaceControl); }); injectTap(101, 101); - EXPECT_EQ(parent->consumeEvent(100), nullptr); + EXPECT_EQ(parent->consumeEvent(/*timeout=*/100ms), nullptr); } class MultiDisplayTests : public InputSurfacesTest { @@ -1233,7 +1228,7 @@ TEST_F(MultiDisplayTests, drop_touch_if_layer_on_invalid_display) { // Touches should be dropped if the layer is on an invalid display. injectTapOnDisplay(101, 101, layerStack.id); - EXPECT_EQ(surface->consumeEvent(100), nullptr); + EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); // However, we still let the window be focused and receive keys. surface->requestFocus(layerStack.id); @@ -1271,12 +1266,12 @@ TEST_F(MultiDisplayTests, drop_input_for_secure_layer_on_nonsecure_display) { injectTapOnDisplay(101, 101, layerStack.id); - EXPECT_EQ(surface->consumeEvent(100), nullptr); + EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); surface->requestFocus(layerStack.id); surface->assertFocusChange(true); injectKeyOnDisplay(AKEYCODE_V, layerStack.id); - EXPECT_EQ(surface->consumeEvent(100), nullptr); + EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); } TEST_F(MultiDisplayTests, dont_drop_input_for_secure_layer_on_secure_display) { diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index c6ea317949..577d2394c6 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -791,6 +791,10 @@ public: return binder::Status::ok(); } + binder::Status captureLayersSync(const LayerCaptureArgs&, ScreenCaptureResults*) override { + return binder::Status::ok(); + } + binder::Status captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&) override { return binder::Status::ok(); diff --git a/libs/input/AccelerationCurve.cpp b/libs/input/AccelerationCurve.cpp new file mode 100644 index 0000000000..0a92a71596 --- /dev/null +++ b/libs/input/AccelerationCurve.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <input/AccelerationCurve.h> + +#include <array> +#include <limits> + +#include <log/log_main.h> + +#define LOG_TAG "AccelerationCurve" + +namespace android { + +namespace { + +// The last segment must have an infinite maximum speed, so that all speeds are covered. +constexpr std::array<AccelerationCurveSegment, 4> kSegments = {{ + {32.002, 3.19, 0}, + {52.83, 4.79, -51.254}, + {119.124, 7.28, -182.737}, + {std::numeric_limits<double>::infinity(), 15.04, -1107.556}, +}}; + +static_assert(kSegments.back().maxPointerSpeedMmPerS == std::numeric_limits<double>::infinity()); + +constexpr std::array<double, 15> kSensitivityFactors = {1, 2, 4, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 16, 18, 20}; + +} // namespace + +std::vector<AccelerationCurveSegment> createAccelerationCurveForPointerSensitivity( + int32_t sensitivity) { + LOG_ALWAYS_FATAL_IF(sensitivity < -7 || sensitivity > 7, "Invalid pointer sensitivity value"); + std::vector<AccelerationCurveSegment> output; + output.reserve(kSegments.size()); + + // The curves we want to produce for different sensitivity values are actually the same curve, + // just scaled in the Y (gain) axis by a sensitivity factor and a couple of constants. + double commonFactor = 0.64 * kSensitivityFactors[sensitivity + 7] / 10; + for (AccelerationCurveSegment seg : kSegments) { + output.push_back(AccelerationCurveSegment{seg.maxPointerSpeedMmPerS, + commonFactor * seg.baseGain, + commonFactor * seg.reciprocal}); + } + + return output; +} + +} // namespace android
\ No newline at end of file diff --git a/libs/input/Android.bp b/libs/input/Android.bp index b74c3b2e95..8b693390d0 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -39,6 +39,7 @@ filegroup { aconfig_declarations { name: "com.android.input.flags-aconfig", package: "com.android.input.flags", + container: "system", srcs: ["input_flags.aconfig"], } @@ -175,6 +176,8 @@ cc_library { ], srcs: [ "android/os/IInputFlinger.aidl", + "android/os/InputChannelCore.aidl", + "AccelerationCurve.cpp", "Input.cpp", "InputDevice.cpp", "InputEventLabels.cpp", diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index bd5b67b1d0..1c713f9163 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -374,11 +374,16 @@ std::ostream& operator<<(std::ostream& out, const KeyEvent& event) { out << ", deviceId=" << event.getDeviceId(); out << ", source=" << inputEventSourceToString(event.getSource()); out << ", displayId=" << event.getDisplayId(); - out << ", eventId=" << event.getId(); + out << ", eventId=0x" << std::hex << event.getId() << std::dec; out << "}"; return out; } +std::ostream& operator<<(std::ostream& out, const PointerProperties& properties) { + out << "Pointer(id=" << properties.id << ", " << ftl::enum_string(properties.toolType) << ")"; + return out; +} + // --- PointerCoords --- float PointerCoords::getAxisValue(int32_t axis) const { @@ -1032,6 +1037,9 @@ std::ostream& operator<<(std::ostream& out, const MotionEvent& event) { if (event.getMetaState() != 0) { out << ", metaState=" << event.getMetaState(); } + if (event.getFlags() != 0) { + out << ", flags=0x" << std::hex << event.getFlags() << std::dec; + } if (event.getEdgeFlags() != 0) { out << ", edgeFlags=" << event.getEdgeFlags(); } @@ -1046,7 +1054,7 @@ std::ostream& operator<<(std::ostream& out, const MotionEvent& event) { out << ", deviceId=" << event.getDeviceId(); out << ", source=" << inputEventSourceToString(event.getSource()); out << ", displayId=" << event.getDisplayId(); - out << ", eventId=" << event.getId(); + out << ", eventId=0x" << std::hex << event.getId() << std::dec; out << "}"; return out; } diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp index 9c7c0c19ed..d4dbc45090 100644 --- a/libs/input/InputDevice.cpp +++ b/libs/input/InputDevice.cpp @@ -190,14 +190,16 @@ InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) mHasSensor(other.mHasSensor), mMotionRanges(other.mMotionRanges), mSensors(other.mSensors), - mLights(other.mLights) {} + mLights(other.mLights), + mViewBehavior(other.mViewBehavior) {} InputDeviceInfo::~InputDeviceInfo() { } void InputDeviceInfo::initialize(int32_t id, int32_t generation, int32_t controllerNumber, const InputDeviceIdentifier& identifier, const std::string& alias, - bool isExternal, bool hasMic, int32_t associatedDisplayId) { + bool isExternal, bool hasMic, int32_t associatedDisplayId, + InputDeviceViewBehavior viewBehavior) { mId = id; mGeneration = generation; mControllerNumber = controllerNumber; @@ -212,6 +214,7 @@ void InputDeviceInfo::initialize(int32_t id, int32_t generation, int32_t control mHasBattery = false; mHasButtonUnderPad = false; mHasSensor = false; + mViewBehavior = viewBehavior; mUsiVersion.reset(); mMotionRanges.clear(); mSensors.clear(); diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp index 0e627e56fd..8db0ca588b 100644 --- a/libs/input/InputEventLabels.cpp +++ b/libs/input/InputEventLabels.cpp @@ -348,7 +348,9 @@ namespace android { DEFINE_KEYCODE(MACRO_1), \ DEFINE_KEYCODE(MACRO_2), \ DEFINE_KEYCODE(MACRO_3), \ - DEFINE_KEYCODE(MACRO_4) + DEFINE_KEYCODE(MACRO_4), \ + DEFINE_KEYCODE(EMOJI_PICKER), \ + DEFINE_KEYCODE(SCREENSHOT) // NOTE: If you add a new axis here you must also add it to several other files. // Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list. diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp index 09e98d0515..e49f4eb6f6 100644 --- a/libs/input/InputTransport.cpp +++ b/libs/input/InputTransport.cpp @@ -10,6 +10,7 @@ #include <fcntl.h> #include <inttypes.h> #include <math.h> +#include <poll.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> @@ -94,6 +95,21 @@ bool debugResampling() { return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO); } +android::base::unique_fd dupChannelFd(int fd) { + android::base::unique_fd newFd(::dup(fd)); + if (!newFd.ok()) { + ALOGE("Could not duplicate fd %i : %s", fd, strerror(errno)); + const bool hitFdLimit = errno == EMFILE || errno == ENFILE; + // If this process is out of file descriptors, then throwing that might end up exploding + // on the other side of a binder call, which isn't really helpful. + // Better to just crash here and hope that the FD leak is slow. + // Other failures could be client errors, so we still propagate those back to the caller. + LOG_ALWAYS_FATAL_IF(hitFdLimit, "Too many open files, could not duplicate input channel"); + return {}; + } + return newFd; +} + } // namespace using android::base::Result; @@ -394,15 +410,23 @@ std::unique_ptr<InputChannel> InputChannel::create(const std::string& name, return std::unique_ptr<InputChannel>(new InputChannel(name, std::move(fd), token)); } -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)) { +std::unique_ptr<InputChannel> InputChannel::create( + android::os::InputChannelCore&& parceledChannel) { + return InputChannel::create(parceledChannel.name, parceledChannel.fd.release(), + parceledChannel.token); +} + +InputChannel::InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token) { + this->name = std::move(name); + this->fd.reset(std::move(fd)); + this->token = std::move(token); ALOGD_IF(DEBUG_CHANNEL_LIFECYCLE, "Input channel constructed: name='%s', fd=%d", - getName().c_str(), getFd().get()); + getName().c_str(), getFd()); } InputChannel::~InputChannel() { ALOGD_IF(DEBUG_CHANNEL_LIFECYCLE, "Input channel destroyed: name='%s', fd=%d", - getName().c_str(), getFd().get()); + getName().c_str(), getFd()); } status_t InputChannel::openInputChannelPair(const std::string& name, @@ -440,19 +464,19 @@ status_t InputChannel::sendMessage(const InputMessage* msg) { ATRACE_NAME_IF(ATRACE_ENABLED(), StringPrintf("sendMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=0x%" PRIx32 ")", - mName.c_str(), msg->header.seq, msg->header.type)); + name.c_str(), msg->header.seq, msg->header.type)); const size_t msgLength = msg->size(); InputMessage cleanMsg; msg->getSanitizedCopy(&cleanMsg); ssize_t nWrite; do { - nWrite = ::send(getFd().get(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL); + nWrite = ::send(getFd(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL); } while (nWrite == -1 && errno == EINTR); if (nWrite < 0) { int error = errno; 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)); + name.c_str(), ftl::enum_string(msg->header.type).c_str(), strerror(error)); if (error == EAGAIN || error == EWOULDBLOCK) { return WOULD_BLOCK; } @@ -464,12 +488,12 @@ status_t InputChannel::sendMessage(const InputMessage* msg) { if (size_t(nWrite) != msgLength) { ALOGD_IF(DEBUG_CHANNEL_MESSAGES, - "channel '%s' ~ error sending message type %s, send was incomplete", mName.c_str(), + "channel '%s' ~ error sending message type %s, send was incomplete", name.c_str(), ftl::enum_string(msg->header.type).c_str()); return DEAD_OBJECT; } - ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ sent message of type %s", mName.c_str(), + ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ sent message of type %s", name.c_str(), ftl::enum_string(msg->header.type).c_str()); return OK; @@ -478,13 +502,13 @@ status_t InputChannel::sendMessage(const InputMessage* msg) { status_t InputChannel::receiveMessage(InputMessage* msg) { ssize_t nRead; do { - nRead = ::recv(getFd().get(), msg, sizeof(InputMessage), MSG_DONTWAIT); + nRead = ::recv(getFd(), msg, sizeof(InputMessage), MSG_DONTWAIT); } while (nRead == -1 && errno == EINTR); if (nRead < 0) { int error = errno; ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ receive message failed, errno=%d", - mName.c_str(), errno); + name.c_str(), errno); if (error == EAGAIN || error == EWOULDBLOCK) { return WOULD_BLOCK; } @@ -496,81 +520,85 @@ status_t InputChannel::receiveMessage(InputMessage* msg) { if (nRead == 0) { // check for EOF ALOGD_IF(DEBUG_CHANNEL_MESSAGES, - "channel '%s' ~ receive message failed because peer was closed", mName.c_str()); + "channel '%s' ~ receive message failed because peer was closed", name.c_str()); return DEAD_OBJECT; } if (!msg->isValid(nRead)) { - ALOGE("channel '%s' ~ received invalid message of size %zd", mName.c_str(), nRead); + ALOGE("channel '%s' ~ received invalid message of size %zd", name.c_str(), nRead); return BAD_VALUE; } - ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ received message of type %s", mName.c_str(), + ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ received message of type %s", name.c_str(), ftl::enum_string(msg->header.type).c_str()); if (ATRACE_ENABLED()) { // Add an additional trace point to include data about the received message. std::string message = StringPrintf("receiveMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=0x%" PRIx32 ")", - mName.c_str(), msg->header.seq, msg->header.type); + name.c_str(), msg->header.seq, msg->header.type); ATRACE_NAME(message.c_str()); } return OK; } -std::unique_ptr<InputChannel> InputChannel::dup() const { - base::unique_fd newFd(dupFd()); - return InputChannel::create(getName(), std::move(newFd), getConnectionToken()); +bool InputChannel::probablyHasInput() const { + struct pollfd pfds = {.fd = fd.get(), .events = POLLIN}; + if (::poll(&pfds, /*nfds=*/1, /*timeout=*/0) <= 0) { + // This can be a false negative because EINTR and ENOMEM are not handled. The latter should + // be extremely rare. The EINTR is also unlikely because it happens only when the signal + // arrives while the syscall is executed, and the syscall is quick. Hitting EINTR too often + // would be a sign of having too many signals, which is a bigger performance problem. A + // common tradition is to repeat the syscall on each EINTR, but it is not necessary here. + // In other words, the missing one liner is replaced by a multiline explanation. + return false; + } + // From poll(2): The bits returned in |revents| can include any of those specified in |events|, + // or one of the values POLLERR, POLLHUP, or POLLNVAL. + return (pfds.revents & POLLIN) != 0; } -void InputChannel::copyTo(InputChannel& outChannel) const { - outChannel.mName = getName(); - outChannel.mFd = dupFd(); - outChannel.mToken = getConnectionToken(); +void InputChannel::waitForMessage(std::chrono::milliseconds timeout) const { + if (timeout < 0ms) { + LOG(FATAL) << "Timeout cannot be negative, received " << timeout.count(); + } + struct pollfd pfds = {.fd = fd.get(), .events = POLLIN}; + int ret; + std::chrono::time_point<std::chrono::steady_clock> stopTime = + std::chrono::steady_clock::now() + timeout; + std::chrono::milliseconds remaining = timeout; + do { + ret = ::poll(&pfds, /*nfds=*/1, /*timeout=*/remaining.count()); + remaining = std::chrono::duration_cast<std::chrono::milliseconds>( + stopTime - std::chrono::steady_clock::now()); + } while (ret == -1 && errno == EINTR && remaining > 0ms); } -status_t InputChannel::writeToParcel(android::Parcel* parcel) const { - if (parcel == nullptr) { - ALOGE("%s: Null parcel", __func__); - return BAD_VALUE; - } - return parcel->writeStrongBinder(mToken) - ?: parcel->writeUtf8AsUtf16(mName) ?: parcel->writeUniqueFileDescriptor(mFd); +std::unique_ptr<InputChannel> InputChannel::dup() const { + base::unique_fd newFd(dupChannelFd(fd.get())); + return InputChannel::create(getName(), std::move(newFd), getConnectionToken()); } -status_t InputChannel::readFromParcel(const android::Parcel* parcel) { - if (parcel == nullptr) { - ALOGE("%s: Null parcel", __func__); - return BAD_VALUE; - } - mToken = parcel->readStrongBinder(); - return parcel->readUtf8FromUtf16(&mName) ?: parcel->readUniqueFileDescriptor(&mFd); +void InputChannel::copyTo(android::os::InputChannelCore& outChannel) const { + outChannel.name = getName(); + outChannel.fd.reset(dupChannelFd(fd.get())); + outChannel.token = getConnectionToken(); } -sp<IBinder> InputChannel::getConnectionToken() const { - return mToken; +void InputChannel::moveChannel(std::unique_ptr<InputChannel> from, + android::os::InputChannelCore& outChannel) { + outChannel.name = from->getName(); + outChannel.fd = android::os::ParcelFileDescriptor(std::move(from->fd)); + outChannel.token = from->getConnectionToken(); } -base::unique_fd InputChannel::dupFd() const { - base::unique_fd newFd(::dup(getFd().get())); - if (!newFd.ok()) { - ALOGE("Could not duplicate fd %i for channel %s: %s", getFd().get(), getName().c_str(), - strerror(errno)); - const bool hitFdLimit = errno == EMFILE || errno == ENFILE; - // If this process is out of file descriptors, then throwing that might end up exploding - // on the other side of a binder call, which isn't really helpful. - // Better to just crash here and hope that the FD leak is slow. - // Other failures could be client errors, so we still propagate those back to the caller. - LOG_ALWAYS_FATAL_IF(hitFdLimit, "Too many open files, could not duplicate input channel %s", - getName().c_str()); - return {}; - } - return newFd; +sp<IBinder> InputChannel::getConnectionToken() const { + return token; } // --- InputPublisher --- InputPublisher::InputPublisher(const std::shared_ptr<InputChannel>& channel) - : mChannel(channel), mInputVerifier(channel->getName()) {} + : mChannel(channel), mInputVerifier(mChannel->getName()) {} InputPublisher::~InputPublisher() { } @@ -646,7 +674,7 @@ status_t InputPublisher::publishMotionEvent( "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", + "pointerCount=%" PRIu32 "\n%s", mChannel->getName().c_str(), __func__, seq, eventId, deviceId, inputEventSourceToString(source).c_str(), displayId, MotionEvent::actionToString(action).c_str(), actionButton, flags, edgeFlags, @@ -850,6 +878,9 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum mConsumeTimes.emplace(mMsg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC)); LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32, mMsg.header.seq); + + // Trace the event processing timeline - event was just read from the socket + ATRACE_ASYNC_BEGIN("InputConsumer processing", /*cookie=*/mMsg.header.seq); } if (result) { // Consume the next batched event unless batches are being held for later. @@ -1388,6 +1419,9 @@ status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) // message anymore. If the socket write did not succeed, we will try again and will still // need consume time. popConsumeTime(seq); + + // Trace the event processing timeline - event was just finished + ATRACE_ASYNC_END("InputConsumer processing", /*cookie=*/seq); } return result; } @@ -1406,6 +1440,10 @@ int32_t InputConsumer::getPendingBatchSource() const { return head.body.motion.source; } +bool InputConsumer::probablyHasInput() const { + return hasPendingBatch() || mChannel->probablyHasInput(); +} + ssize_t InputConsumer::findBatch(int32_t deviceId, int32_t source) const { for (size_t i = 0; i < mBatches.size(); i++) { const Batch& batch = mBatches[i]; diff --git a/libs/input/KeyLayoutMap.cpp b/libs/input/KeyLayoutMap.cpp index ab8c341b15..508818852e 100644 --- a/libs/input/KeyLayoutMap.cpp +++ b/libs/input/KeyLayoutMap.cpp @@ -97,6 +97,10 @@ static const std::unordered_map<std::string_view, InputDeviceSensorType> SENSOR_ bool kernelConfigsArePresent(const std::set<std::string>& configs) { #if defined(__ANDROID__) + if (configs.empty()) { + return true; + } + std::map<std::string, std::string> kernelConfigs; const status_t result = android::kernelconfigs::LoadKernelConfigs(&kernelConfigs); LOG_ALWAYS_FATAL_IF(result != OK, "Kernel configs could not be fetched"); diff --git a/libs/input/VelocityControl.cpp b/libs/input/VelocityControl.cpp index c835a081a5..edd31e91d3 100644 --- a/libs/input/VelocityControl.cpp +++ b/libs/input/VelocityControl.cpp @@ -15,7 +15,6 @@ */ #define LOG_TAG "VelocityControl" -//#define LOG_NDEBUG 0 // Log debug messages about acceleration. static constexpr bool DEBUG_ACCELERATION = false; @@ -23,6 +22,7 @@ static constexpr bool DEBUG_ACCELERATION = false; #include <math.h> #include <limits.h> +#include <android-base/logging.h> #include <input/VelocityControl.h> #include <utils/BitSet.h> #include <utils/Timers.h> @@ -37,15 +37,6 @@ VelocityControl::VelocityControl() { reset(); } -const VelocityControlParameters& VelocityControl::getParameters() const{ - return mParameters; -} - -void VelocityControl::setParameters(const VelocityControlParameters& parameters) { - mParameters = parameters; - reset(); -} - void VelocityControl::reset() { mLastMovementTime = LLONG_MIN; mRawPositionX = 0; @@ -54,65 +45,156 @@ void VelocityControl::reset() { } void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) { - if ((deltaX && *deltaX) || (deltaY && *deltaY)) { - if (eventTime >= mLastMovementTime + STOP_TIME) { - if (DEBUG_ACCELERATION && mLastMovementTime != LLONG_MIN) { - ALOGD("VelocityControl: stopped, last movement was %0.3fms ago", - (eventTime - mLastMovementTime) * 0.000001f); - } - reset(); - } + if ((deltaX == nullptr || *deltaX == 0) && (deltaY == nullptr || *deltaY == 0)) { + return; + } + if (eventTime >= mLastMovementTime + STOP_TIME) { + ALOGD_IF(DEBUG_ACCELERATION && mLastMovementTime != LLONG_MIN, + "VelocityControl: stopped, last movement was %0.3fms ago", + (eventTime - mLastMovementTime) * 0.000001f); + reset(); + } - mLastMovementTime = eventTime; - if (deltaX) { - mRawPositionX += *deltaX; - } - if (deltaY) { - mRawPositionY += *deltaY; - } - mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_X, - mRawPositionX); - mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_Y, - mRawPositionY); - - std::optional<float> vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0); - std::optional<float> vy = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, 0); - float scale = mParameters.scale; - if (vx && vy) { - float speed = hypotf(*vx, *vy) * scale; - if (speed >= mParameters.highThreshold) { - // Apply full acceleration above the high speed threshold. - scale *= mParameters.acceleration; - } else if (speed > mParameters.lowThreshold) { - // Linearly interpolate the acceleration to apply between the low and high - // speed thresholds. - scale *= 1 + (speed - mParameters.lowThreshold) - / (mParameters.highThreshold - mParameters.lowThreshold) - * (mParameters.acceleration - 1); - } - - if (DEBUG_ACCELERATION) { - ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): " - "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f", - mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold, - mParameters.acceleration, *vx, *vy, speed, scale / mParameters.scale); - } - - } else { - if (DEBUG_ACCELERATION) { - ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): unknown velocity", - mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold, - mParameters.acceleration); - } - } + mLastMovementTime = eventTime; + if (deltaX) { + mRawPositionX += *deltaX; + } + if (deltaY) { + mRawPositionY += *deltaY; + } + mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_X, mRawPositionX); + mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_Y, mRawPositionY); + scaleDeltas(deltaX, deltaY); +} + +// --- SimpleVelocityControl --- + +const VelocityControlParameters& SimpleVelocityControl::getParameters() const { + return mParameters; +} - if (deltaX) { - *deltaX *= scale; +void SimpleVelocityControl::setParameters(const VelocityControlParameters& parameters) { + mParameters = parameters; + reset(); +} + +void SimpleVelocityControl::scaleDeltas(float* deltaX, float* deltaY) { + std::optional<float> vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0); + std::optional<float> vy = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, 0); + float scale = mParameters.scale; + if (vx.has_value() && vy.has_value()) { + float speed = hypotf(*vx, *vy) * scale; + if (speed >= mParameters.highThreshold) { + // Apply full acceleration above the high speed threshold. + scale *= mParameters.acceleration; + } else if (speed > mParameters.lowThreshold) { + // Linearly interpolate the acceleration to apply between the low and high + // speed thresholds. + scale *= 1 + + (speed - mParameters.lowThreshold) / + (mParameters.highThreshold - mParameters.lowThreshold) * + (mParameters.acceleration - 1); } - if (deltaY) { - *deltaY *= scale; + + ALOGD_IF(DEBUG_ACCELERATION, + "SimpleVelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): " + "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f", + mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold, + mParameters.acceleration, *vx, *vy, speed, scale / mParameters.scale); + + } else { + ALOGD_IF(DEBUG_ACCELERATION, + "SimpleVelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): unknown velocity", + mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold, + mParameters.acceleration); + } + + if (deltaX != nullptr) { + *deltaX *= scale; + } + if (deltaY != nullptr) { + *deltaY *= scale; + } +} + +// --- CurvedVelocityControl --- + +namespace { + +/** + * The resolution that we assume a mouse to have, in counts per inch. + * + * Mouse resolutions vary wildly, but 800 CPI is probably the most common. There should be enough + * range in the available sensitivity settings to accommodate users of mice with other resolutions. + */ +constexpr int32_t MOUSE_CPI = 800; + +float countsToMm(float counts) { + return counts / MOUSE_CPI * 25.4; +} + +} // namespace + +CurvedVelocityControl::CurvedVelocityControl() + : mCurveSegments(createAccelerationCurveForPointerSensitivity(0)) {} + +void CurvedVelocityControl::setCurve(const std::vector<AccelerationCurveSegment>& curve) { + mCurveSegments = curve; +} + +void CurvedVelocityControl::setAccelerationEnabled(bool enabled) { + mAccelerationEnabled = enabled; +} + +void CurvedVelocityControl::scaleDeltas(float* deltaX, float* deltaY) { + if (!mAccelerationEnabled) { + ALOGD_IF(DEBUG_ACCELERATION, "CurvedVelocityControl: acceleration disabled"); + return; + } + + std::optional<float> vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0); + std::optional<float> vy = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, 0); + + float ratio; + if (vx.has_value() && vy.has_value()) { + float vxMmPerS = countsToMm(*vx); + float vyMmPerS = countsToMm(*vy); + float speedMmPerS = sqrtf(vxMmPerS * vxMmPerS + vyMmPerS * vyMmPerS); + + const AccelerationCurveSegment& seg = segmentForSpeed(speedMmPerS); + ratio = seg.baseGain + seg.reciprocal / speedMmPerS; + ALOGD_IF(DEBUG_ACCELERATION, + "CurvedVelocityControl: velocities (%0.3f, %0.3f) → speed %0.3f → ratio %0.3f", + vxMmPerS, vyMmPerS, speedMmPerS, ratio); + } else { + // We don't have enough data to compute a velocity yet. This happens early in the movement, + // when the speed is presumably low, so use the base gain of the first segment of the curve. + // (This would behave oddly for curves with a reciprocal term on the first segment, but we + // don't have any of those, and they'd be very strange at velocities close to zero anyway.) + ratio = mCurveSegments[0].baseGain; + ALOGD_IF(DEBUG_ACCELERATION, + "CurvedVelocityControl: unknown velocity, using base gain of first segment (%.3f)", + ratio); + } + + if (deltaX != nullptr) { + *deltaX *= ratio; + } + if (deltaY != nullptr) { + *deltaY *= ratio; + } +} + +const AccelerationCurveSegment& CurvedVelocityControl::segmentForSpeed(float speedMmPerS) { + for (const AccelerationCurveSegment& seg : mCurveSegments) { + if (speedMmPerS <= seg.maxPointerSpeedMmPerS) { + return seg; } } + ALOGE("CurvedVelocityControl: No segment found for speed %.3f; last segment should always have " + "a max speed of infinity.", + speedMmPerS); + return mCurveSegments.back(); } } // namespace android diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp index db7031ab03..eea06f1720 100644 --- a/libs/input/VirtualInputDevice.cpp +++ b/libs/input/VirtualInputDevice.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,7 +39,9 @@ static bool isDebug() { } namespace android { + VirtualInputDevice::VirtualInputDevice(unique_fd fd) : mFd(std::move(fd)) {} + VirtualInputDevice::~VirtualInputDevice() { ioctl(mFd, UI_DEV_DESTROY); } @@ -56,7 +58,7 @@ bool VirtualInputDevice::writeInputEvent(uint16_t type, uint16_t code, int32_t v return TEMP_FAILURE_RETRY(write(mFd, &ev, sizeof(struct input_event))) == sizeof(ev); } -/** Utility method to write keyboard key events or mouse button events. */ +/** Utility method to write keyboard key events or mouse/stylus button events. */ bool VirtualInputDevice::writeEvKeyEvent(int32_t androidCode, int32_t androidAction, const std::map<int, int>& evKeyCodeMapping, const std::map<int, UinputAction>& actionMapping, @@ -68,13 +70,17 @@ bool VirtualInputDevice::writeEvKeyEvent(int32_t androidCode, int32_t androidAct } auto actionIterator = actionMapping.find(androidAction); if (actionIterator == actionMapping.end()) { + ALOGE("Unsupported native action for android action %d", androidAction); return false; } - if (!writeInputEvent(EV_KEY, static_cast<uint16_t>(evKeyCodeIterator->second), - static_cast<int32_t>(actionIterator->second), eventTime)) { + int32_t action = static_cast<int32_t>(actionIterator->second); + uint16_t evKeyCode = static_cast<uint16_t>(evKeyCodeIterator->second); + if (!writeInputEvent(EV_KEY, evKeyCode, action, eventTime)) { + ALOGE("Failed to write native action %d and EV keycode %u.", action, evKeyCode); return false; } if (!writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime)) { + ALOGE("Failed to write SYN_REPORT for EV_KEY event."); return false; } return true; @@ -85,6 +91,7 @@ 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}, @@ -195,7 +202,9 @@ const std::map<int, int> VirtualKeyboard::KEY_CODE_MAPPING = { {AKEYCODE_NUMPAD_COMMA, KEY_KPCOMMA}, {AKEYCODE_LANGUAGE_SWITCH, KEY_LANGUAGE}, }; + VirtualKeyboard::VirtualKeyboard(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} + VirtualKeyboard::~VirtualKeyboard() {} bool VirtualKeyboard::writeKeyEvent(int32_t androidKeyCode, int32_t androidAction, @@ -275,6 +284,7 @@ const std::map<int, UinputAction> VirtualTouchscreen::TOUCH_ACTION_MAPPING = { {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}, @@ -393,4 +403,110 @@ bool VirtualTouchscreen::handleTouchDown(int32_t pointerId, std::chrono::nanosec return true; } +// --- VirtualStylus --- +const std::map<int, int> VirtualStylus::TOOL_TYPE_MAPPING = { + {AMOTION_EVENT_TOOL_TYPE_STYLUS, BTN_TOOL_PEN}, + {AMOTION_EVENT_TOOL_TYPE_ERASER, BTN_TOOL_RUBBER}, +}; + +// Button code mapping from https://source.android.com/devices/input/touch-devices +const std::map<int, int> VirtualStylus::BUTTON_CODE_MAPPING = { + {AMOTION_EVENT_BUTTON_STYLUS_PRIMARY, BTN_STYLUS}, + {AMOTION_EVENT_BUTTON_STYLUS_SECONDARY, BTN_STYLUS2}, +}; + +VirtualStylus::VirtualStylus(unique_fd fd) + : VirtualInputDevice(std::move(fd)), mIsStylusDown(false) {} + +VirtualStylus::~VirtualStylus() {} + +bool VirtualStylus::writeMotionEvent(int32_t toolType, int32_t action, int32_t locationX, + int32_t locationY, int32_t pressure, int32_t tiltX, + int32_t tiltY, std::chrono::nanoseconds eventTime) { + auto actionIterator = VirtualTouchscreen::TOUCH_ACTION_MAPPING.find(action); + if (actionIterator == VirtualTouchscreen::TOUCH_ACTION_MAPPING.end()) { + ALOGE("Unsupported action passed for stylus: %d.", action); + return false; + } + UinputAction uinputAction = actionIterator->second; + auto toolTypeIterator = TOOL_TYPE_MAPPING.find(toolType); + if (toolTypeIterator == TOOL_TYPE_MAPPING.end()) { + ALOGE("Unsupported tool type passed for stylus: %d.", toolType); + return false; + } + uint16_t tool = static_cast<uint16_t>(toolTypeIterator->second); + if (uinputAction == UinputAction::PRESS && !handleStylusDown(tool, eventTime)) { + return false; + } + if (!mIsStylusDown) { + ALOGE("Action UP or MOVE received with no prior action DOWN for stylus %d.", mFd.get()); + return false; + } + if (uinputAction == UinputAction::RELEASE && !handleStylusUp(tool, eventTime)) { + return false; + } + if (!writeInputEvent(EV_ABS, ABS_X, locationX, eventTime)) { + ALOGE("Unsupported x-axis location passed for stylus: %d.", locationX); + return false; + } + if (!writeInputEvent(EV_ABS, ABS_Y, locationY, eventTime)) { + ALOGE("Unsupported y-axis location passed for stylus: %d.", locationY); + return false; + } + if (!writeInputEvent(EV_ABS, ABS_TILT_X, tiltX, eventTime)) { + ALOGE("Unsupported x-axis tilt passed for stylus: %d.", tiltX); + return false; + } + if (!writeInputEvent(EV_ABS, ABS_TILT_Y, tiltY, eventTime)) { + ALOGE("Unsupported y-axis tilt passed for stylus: %d.", tiltY); + return false; + } + if (!writeInputEvent(EV_ABS, ABS_PRESSURE, pressure, eventTime)) { + ALOGE("Unsupported pressure passed for stylus: %d.", pressure); + return false; + } + if (!writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime)) { + ALOGE("Failed to write SYN_REPORT for stylus motion event."); + return false; + } + return true; +} + +bool VirtualStylus::writeButtonEvent(int32_t androidButtonCode, int32_t androidAction, + std::chrono::nanoseconds eventTime) { + return writeEvKeyEvent(androidButtonCode, androidAction, BUTTON_CODE_MAPPING, + VirtualMouse::BUTTON_ACTION_MAPPING, eventTime); +} + +bool VirtualStylus::handleStylusDown(uint16_t tool, std::chrono::nanoseconds eventTime) { + if (mIsStylusDown) { + ALOGE("Repetitive action DOWN event received for a stylus that is already down."); + return false; + } + if (!writeInputEvent(EV_KEY, tool, static_cast<int32_t>(UinputAction::PRESS), eventTime)) { + ALOGE("Failed to write EV_KEY for stylus tool type: %u.", tool); + return false; + } + if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::PRESS), eventTime)) { + ALOGE("Failed to write BTN_TOUCH for stylus press."); + return false; + } + mIsStylusDown = true; + return true; +} + +bool VirtualStylus::handleStylusUp(uint16_t tool, std::chrono::nanoseconds eventTime) { + if (!writeInputEvent(EV_KEY, tool, static_cast<int32_t>(UinputAction::RELEASE), eventTime)) { + ALOGE("Failed to write EV_KEY for stylus tool type: %u.", tool); + return false; + } + if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::RELEASE), + eventTime)) { + ALOGE("Failed to write BTN_TOUCH for stylus release."); + return false; + } + mIsStylusDown = false; + return true; +} + } // namespace android diff --git a/libs/input/android/os/IInputFlinger.aidl b/libs/input/android/os/IInputFlinger.aidl index 00ebd4d34f..c1aacfbc53 100644 --- a/libs/input/android/os/IInputFlinger.aidl +++ b/libs/input/android/os/IInputFlinger.aidl @@ -16,14 +16,13 @@ package android.os; -import android.InputChannel; +import android.os.InputChannelCore; import android.gui.FocusRequest; -import android.gui.WindowInfo; /** @hide */ interface IInputFlinger { - InputChannel createInputChannel(in @utf8InCpp String name); + InputChannelCore createInputChannel(in @utf8InCpp String name); void removeInputChannel(in IBinder connectionToken); /** * Sets focus to the window identified by the token. This must be called diff --git a/libs/input/android/InputChannel.aidl b/libs/input/android/os/InputChannelCore.aidl index c2d1112dd3..888a553268 100644 --- a/libs/input/android/InputChannel.aidl +++ b/libs/input/android/os/InputChannelCore.aidl @@ -15,6 +15,16 @@ ** limitations under the License. */ -package android; +package android.os; -parcelable InputChannel cpp_header "input/InputTransport.h"; +import android.os.ParcelFileDescriptor; + +/** + * Input channel struct for sending InputChannel between processes. + * @hide + */ +parcelable InputChannelCore { + @utf8InCpp String name; + ParcelFileDescriptor fd; + IBinder token; +} diff --git a/libs/input/android/os/InputConfig.aidl b/libs/input/android/os/InputConfig.aidl index 4e644fff06..5d391551c2 100644 --- a/libs/input/android/os/InputConfig.aidl +++ b/libs/input/android/os/InputConfig.aidl @@ -150,4 +150,11 @@ enum InputConfig { * likely a duplicate window with the same client token, but different bounds. */ CLONE = 1 << 16, + + /** + * If the stylus is currently down *anywhere* on the screen, new touches will not be delivered + * to the window with this flag. This helps prevent unexpected clicks on some system windows, + * like StatusBar and TaskBar. + */ + GLOBAL_STYLUS_BLOCKS_TOUCH = 1 << 17, } diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig index 54eeb39935..5af48550c6 100644 --- a/libs/input/input_flags.aconfig +++ b/libs/input/input_flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.input.flags" +container: "system" flag { name: "enable_outbound_event_verification" @@ -26,6 +27,13 @@ flag { namespace: "input" description: "Set to true to enable timer support for the touchpad Gestures library" bug: "297192727" + } + + flag { + name: "enable_input_event_tracing" + namespace: "input" + description: "Set to true to enable input event tracing, including always-on tracing on non-user builds" + bug: "210460522" } flag { @@ -52,15 +60,15 @@ flag { flag { name: "enable_touchpad_typing_palm_rejection" namespace: "input" - description: "Enable additional palm rejection on touchpad while typing" + description: "Enabling additional touchpad palm rejection will disable the tap to click while the user is typing on a physical keyboard" bug: "301055381" } flag { - name: "remove_app_switch_drops" + name: "enable_v2_touchpad_typing_palm_rejection" namespace: "input" - description: "Remove the logic of dropping events due to pending app switch" - bug: "284808102" + description: "In addition to touchpad palm rejection v1, v2 will also cancel ongoing move gestures while typing and add delay in re-enabling the tap to click." + bug: "301055381" } flag { @@ -90,3 +98,24 @@ flag { description: "Remove pointer event tracking in WM after the Pointer Icon Refactor" bug: "315321016" } + +flag { + name: "enable_new_mouse_pointer_ballistics" + namespace: "input" + description: "Change the acceleration curves for mouse pointer movements to match the touchpad ones" + bug: "315313622" +} + +flag { + name: "rate_limit_user_activity_poke_in_dispatcher" + namespace: "input" + description: "Move user-activity poke rate-limiting from PowerManagerService to InputDispatcher." + bug: "320499729" +} + +flag { + name: "input_device_view_behavior_api" + namespace: "input" + description: "Controls the API to provide InputDevice view behavior." + bug: "246946631" +} diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index 13cfb491b5..0485ff6e1e 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -25,6 +25,7 @@ cc_test { "TfLiteMotionPredictor_test.cpp", "TouchResampling_test.cpp", "TouchVideoFrame_test.cpp", + "VelocityControl_test.cpp", "VelocityTracker_test.cpp", "VerifiedInputEvent_test.cpp", ], diff --git a/libs/input/tests/InputChannel_test.cpp b/libs/input/tests/InputChannel_test.cpp index 0661261003..60feb53dcc 100644 --- a/libs/input/tests/InputChannel_test.cpp +++ b/libs/input/tests/InputChannel_test.cpp @@ -32,37 +32,31 @@ namespace android { +namespace { +bool operator==(const InputChannel& left, const InputChannel& right) { + struct stat lhs, rhs; + if (fstat(left.getFd(), &lhs) != 0) { + return false; + } + if (fstat(right.getFd(), &rhs) != 0) { + return false; + } + // If file descriptors are pointing to same inode they are duplicated fds. + return left.getName() == right.getName() && + left.getConnectionToken() == right.getConnectionToken() && lhs.st_ino == rhs.st_ino; +} +} // namespace + class InputChannelTest : public testing::Test { }; +TEST_F(InputChannelTest, ClientAndServerTokensMatch) { + std::unique_ptr<InputChannel> serverChannel, clientChannel; -TEST_F(InputChannelTest, ConstructorAndDestructor_TakesOwnershipOfFileDescriptors) { - // Our purpose here is to verify that the input channel destructor closes the - // file descriptor provided to it. One easy way is to provide it with one end - // of a pipe and to check for EPIPE on the other end after the channel is destroyed. - Pipe pipe; - - android::base::unique_fd sendFd(pipe.sendFd); - - std::unique_ptr<InputChannel> inputChannel = - InputChannel::create("channel name", std::move(sendFd), new BBinder()); - - EXPECT_NE(inputChannel, nullptr) << "channel should be successfully created"; - EXPECT_STREQ("channel name", inputChannel->getName().c_str()) - << "channel should have provided name"; - EXPECT_NE(-1, inputChannel->getFd()) << "channel should have valid fd"; - - // InputChannel should be the owner of the file descriptor now - ASSERT_FALSE(sendFd.ok()); -} - -TEST_F(InputChannelTest, SetAndGetToken) { - Pipe pipe; - sp<IBinder> token = new BBinder(); - std::unique_ptr<InputChannel> channel = - InputChannel::create("test channel", android::base::unique_fd(pipe.sendFd), token); - - EXPECT_EQ(token, channel->getConnectionToken()); + status_t result = + InputChannel::openInputChannelPair("channel name", serverChannel, clientChannel); + ASSERT_EQ(OK, result) << "should have successfully opened a channel pair"; + EXPECT_EQ(serverChannel->getConnectionToken(), clientChannel->getConnectionToken()); } TEST_F(InputChannelTest, OpenInputChannelPair_ReturnsAPairOfConnectedChannels) { @@ -71,8 +65,7 @@ TEST_F(InputChannelTest, OpenInputChannelPair_ReturnsAPairOfConnectedChannels) { status_t result = InputChannel::openInputChannelPair("channel name", serverChannel, clientChannel); - ASSERT_EQ(OK, result) - << "should have successfully opened a channel pair"; + ASSERT_EQ(OK, result) << "should have successfully opened a channel pair"; // Name EXPECT_STREQ("channel name (server)", serverChannel->getName().c_str()) @@ -81,8 +74,7 @@ TEST_F(InputChannelTest, OpenInputChannelPair_ReturnsAPairOfConnectedChannels) { << "client channel should have suffixed name"; // Server->Client communication - InputMessage serverMsg; - memset(&serverMsg, 0, sizeof(InputMessage)); + InputMessage serverMsg = {}; serverMsg.header.type = InputMessage::Type::KEY; serverMsg.body.key.action = AKEY_EVENT_ACTION_DOWN; EXPECT_EQ(OK, serverChannel->sendMessage(&serverMsg)) @@ -97,8 +89,7 @@ TEST_F(InputChannelTest, OpenInputChannelPair_ReturnsAPairOfConnectedChannels) { << "client channel should receive the correct message from server channel"; // Client->Server communication - InputMessage clientReply; - memset(&clientReply, 0, sizeof(InputMessage)); + InputMessage clientReply = {}; clientReply.header.type = InputMessage::Type::FINISHED; clientReply.header.seq = 0x11223344; clientReply.body.finished.handled = true; @@ -116,6 +107,48 @@ TEST_F(InputChannelTest, OpenInputChannelPair_ReturnsAPairOfConnectedChannels) { << "server channel should receive the correct message from client channel"; } +TEST_F(InputChannelTest, ProbablyHasInput) { + std::unique_ptr<InputChannel> senderChannel, receiverChannel; + + // Open a pair of channels. + status_t result = + InputChannel::openInputChannelPair("channel name", senderChannel, receiverChannel); + ASSERT_EQ(OK, result) << "should have successfully opened a channel pair"; + + ASSERT_FALSE(receiverChannel->probablyHasInput()); + + // Send one message. + InputMessage serverMsg = {}; + serverMsg.header.type = InputMessage::Type::KEY; + serverMsg.body.key.action = AKEY_EVENT_ACTION_DOWN; + EXPECT_EQ(OK, senderChannel->sendMessage(&serverMsg)) + << "server channel should be able to send message to client channel"; + + // Verify input is available. + bool hasInput = false; + do { + // The probablyHasInput() can return false positive under rare circumstances uncontrollable + // by the tests. Re-request the availability in this case. Returning |false| for a long + // time is not intended, and would cause a test timeout. + hasInput = receiverChannel->probablyHasInput(); + } while (!hasInput); + EXPECT_TRUE(hasInput) + << "client channel should observe that message is available before receiving it"; + + // Receive (consume) the message. + InputMessage clientMsg; + EXPECT_EQ(OK, receiverChannel->receiveMessage(&clientMsg)) + << "client channel should be able to receive message from server channel"; + EXPECT_EQ(serverMsg.header.type, clientMsg.header.type) + << "client channel should receive the correct message from server channel"; + EXPECT_EQ(serverMsg.body.key.action, clientMsg.body.key.action) + << "client channel should receive the correct message from server channel"; + + // Verify input is not available. + EXPECT_FALSE(receiverChannel->probablyHasInput()) + << "client should not observe any more messages after receiving the single one"; +} + TEST_F(InputChannelTest, ReceiveSignal_WhenNoSignalPresent_ReturnsAnError) { std::unique_ptr<InputChannel> serverChannel, clientChannel; @@ -195,25 +228,6 @@ TEST_F(InputChannelTest, SendAndReceive_MotionClassification) { } } -TEST_F(InputChannelTest, InputChannelParcelAndUnparcel) { - std::unique_ptr<InputChannel> serverChannel, clientChannel; - - status_t result = - InputChannel::openInputChannelPair("channel parceling", serverChannel, clientChannel); - - ASSERT_EQ(OK, result) << "should have successfully opened a channel pair"; - - InputChannel chan; - Parcel parcel; - ASSERT_EQ(OK, serverChannel->writeToParcel(&parcel)); - parcel.setDataPosition(0); - chan.readFromParcel(&parcel); - - EXPECT_EQ(chan == *serverChannel, true) - << "inputchannel should be equal after parceling and unparceling.\n" - << "name " << chan.getName() << " name " << serverChannel->getName(); -} - TEST_F(InputChannelTest, DuplicateChannelAndAssertEqual) { std::unique_ptr<InputChannel> serverChannel, clientChannel; diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp index 06b841be0d..35430207f9 100644 --- a/libs/input/tests/InputPublisherAndConsumer_test.cpp +++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp @@ -40,11 +40,186 @@ struct Pointer { bool isResampled = false; }; +// A collection of arguments to be sent as publishMotionEvent(). The saved members of this struct +// allow to check the expectations against the event acquired from the InputReceiver. To help +// simplify expectation checking it carries members not present in MotionEvent, like |rawXScale|. +struct PublishMotionArgs { + const int32_t action; + const nsecs_t downTime; + const uint32_t seq; + const int32_t eventId; + const int32_t deviceId = 1; + const uint32_t source = AINPUT_SOURCE_TOUCHSCREEN; + const int32_t displayId = ADISPLAY_ID_DEFAULT; + const int32_t actionButton = 0; + const int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP; + const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON; + const int32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY; + const MotionClassification classification = MotionClassification::AMBIGUOUS_GESTURE; + const float xScale = 2; + const float yScale = 3; + const float xOffset = -10; + const float yOffset = -20; + const float rawXScale = 4; + const float rawYScale = -5; + const float rawXOffset = -11; + const float rawYOffset = 42; + const float xPrecision = 0.25; + const float yPrecision = 0.5; + const float xCursorPosition = 1.3; + const float yCursorPosition = 50.6; + std::array<uint8_t, 32> hmac; + int32_t flags; + ui::Transform transform; + ui::Transform rawTransform; + const nsecs_t eventTime; + size_t pointerCount; + std::vector<PointerProperties> pointerProperties; + std::vector<PointerCoords> pointerCoords; + + PublishMotionArgs(int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers, + const uint32_t seq); +}; + +PublishMotionArgs::PublishMotionArgs(int32_t inAction, nsecs_t inDownTime, + const std::vector<Pointer>& pointers, const uint32_t inSeq) + : action(inAction), + downTime(inDownTime), + seq(inSeq), + eventId(InputEvent::nextId()), + eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) { + hmac = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + + flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; + if (action == AMOTION_EVENT_ACTION_CANCEL) { + flags |= AMOTION_EVENT_FLAG_CANCELED; + } + pointerCount = pointers.size(); + for (size_t i = 0; i < pointerCount; i++) { + pointerProperties.push_back({}); + pointerProperties[i].clear(); + pointerProperties[i].id = pointers[i].id; + pointerProperties[i].toolType = ToolType::FINGER; + + pointerCoords.push_back({}); + pointerCoords[i].clear(); + pointerCoords[i].isResampled = pointers[i].isResampled; + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, pointers[i].x); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, pointers[i].y); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.5 * i); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.7 * i); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 1.5 * i); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 1.7 * i); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.5 * i); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.7 * i); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 3.5 * i); + } + transform.set({xScale, 0, xOffset, 0, yScale, yOffset, 0, 0, 1}); + rawTransform.set({rawXScale, 0, rawXOffset, 0, rawYScale, rawYOffset, 0, 0, 1}); +} + +// Checks expectations against |motionEvent| acquired from an InputConsumer. Floating point +// comparisons limit precision to EPSILON. +void verifyArgsEqualToEvent(const PublishMotionArgs& args, const MotionEvent& motionEvent) { + EXPECT_EQ(args.eventId, motionEvent.getId()); + EXPECT_EQ(args.deviceId, motionEvent.getDeviceId()); + EXPECT_EQ(args.source, motionEvent.getSource()); + EXPECT_EQ(args.displayId, motionEvent.getDisplayId()); + EXPECT_EQ(args.hmac, motionEvent.getHmac()); + EXPECT_EQ(args.action, motionEvent.getAction()); + EXPECT_EQ(args.downTime, motionEvent.getDownTime()); + EXPECT_EQ(args.flags, motionEvent.getFlags()); + EXPECT_EQ(args.edgeFlags, motionEvent.getEdgeFlags()); + EXPECT_EQ(args.metaState, motionEvent.getMetaState()); + EXPECT_EQ(args.buttonState, motionEvent.getButtonState()); + EXPECT_EQ(args.classification, motionEvent.getClassification()); + EXPECT_EQ(args.transform, motionEvent.getTransform()); + EXPECT_EQ(args.xOffset, motionEvent.getXOffset()); + EXPECT_EQ(args.yOffset, motionEvent.getYOffset()); + EXPECT_EQ(args.xPrecision, motionEvent.getXPrecision()); + EXPECT_EQ(args.yPrecision, motionEvent.getYPrecision()); + EXPECT_NEAR(args.xCursorPosition, motionEvent.getRawXCursorPosition(), EPSILON); + EXPECT_NEAR(args.yCursorPosition, motionEvent.getRawYCursorPosition(), EPSILON); + EXPECT_NEAR(args.xCursorPosition * args.xScale + args.xOffset, motionEvent.getXCursorPosition(), + EPSILON); + EXPECT_NEAR(args.yCursorPosition * args.yScale + args.yOffset, motionEvent.getYCursorPosition(), + EPSILON); + EXPECT_EQ(args.rawTransform, motionEvent.getRawTransform()); + EXPECT_EQ(args.eventTime, motionEvent.getEventTime()); + EXPECT_EQ(args.pointerCount, motionEvent.getPointerCount()); + EXPECT_EQ(0U, motionEvent.getHistorySize()); + + for (size_t i = 0; i < args.pointerCount; i++) { + SCOPED_TRACE(i); + EXPECT_EQ(args.pointerProperties[i].id, motionEvent.getPointerId(i)); + EXPECT_EQ(args.pointerProperties[i].toolType, motionEvent.getToolType(i)); + + const auto& pc = args.pointerCoords[i]; + EXPECT_EQ(pc, motionEvent.getSamplePointerCoords()[i]); + + EXPECT_NEAR(pc.getX() * args.rawXScale + args.rawXOffset, motionEvent.getRawX(i), EPSILON); + EXPECT_NEAR(pc.getY() * args.rawYScale + args.rawYOffset, motionEvent.getRawY(i), EPSILON); + EXPECT_NEAR(pc.getX() * args.xScale + args.xOffset, motionEvent.getX(i), EPSILON); + EXPECT_NEAR(pc.getY() * args.yScale + args.yOffset, motionEvent.getY(i), EPSILON); + EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), motionEvent.getPressure(i)); + EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_SIZE), motionEvent.getSize(i)); + EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), motionEvent.getTouchMajor(i)); + EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), motionEvent.getTouchMinor(i)); + EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), motionEvent.getToolMajor(i)); + EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), motionEvent.getToolMinor(i)); + + // Calculate the orientation after scaling, keeping in mind that an orientation of 0 is + // "up", and the positive y direction is "down". + const float unscaledOrientation = pc.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION); + const float x = sinf(unscaledOrientation) * args.xScale; + const float y = -cosf(unscaledOrientation) * args.yScale; + EXPECT_EQ(atan2f(x, -y), motionEvent.getOrientation(i)); + } +} + +void publishMotionEvent(InputPublisher& publisher, const PublishMotionArgs& a) { + status_t status = + publisher.publishMotionEvent(a.seq, a.eventId, a.deviceId, a.source, a.displayId, + a.hmac, a.action, a.actionButton, a.flags, a.edgeFlags, + a.metaState, a.buttonState, a.classification, a.transform, + a.xPrecision, a.yPrecision, a.xCursorPosition, + a.yCursorPosition, a.rawTransform, a.downTime, a.eventTime, + a.pointerCount, a.pointerProperties.data(), + a.pointerCoords.data()); + ASSERT_EQ(OK, status) << "publisher publishMotionEvent should return OK"; +} + +void sendAndVerifyFinishedSignal(InputConsumer& consumer, InputPublisher& publisher, uint32_t seq, + nsecs_t publishTime) { + status_t status = consumer.sendFinishedSignal(seq, false); + ASSERT_EQ(OK, status) << "consumer sendFinishedSignal should return OK"; + Result<InputPublisher::ConsumerResponse> result = publisher.receiveConsumerResponse(); + ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK"; + ASSERT_TRUE(std::holds_alternative<InputPublisher::Finished>(*result)); + const InputPublisher::Finished& finish = std::get<InputPublisher::Finished>(*result); + ASSERT_EQ(seq, finish.seq) + << "receiveConsumerResponse should have returned the original sequence number"; + ASSERT_FALSE(finish.handled) + << "receiveConsumerResponse should have set handled to consumer's reply"; + ASSERT_GE(finish.consumeTime, publishTime) + << "finished signal's consume time should be greater than publish time"; +} + +void waitUntilInputAvailable(const InputConsumer& inputConsumer) { + bool hasInput; + do { + // The probablyHasInput() can return false positive under rare circumstances uncontrollable + // by the tests. Re-request the availability in this case. Returning |false| for a long + // time is not intended, and would cause a test timeout. + hasInput = inputConsumer.probablyHasInput(); + } while (!hasInput); +} + } // namespace class InputPublisherAndConsumerTest : public testing::Test { protected: - std::shared_ptr<InputChannel> mServerChannel, mClientChannel; std::unique_ptr<InputPublisher> mPublisher; std::unique_ptr<InputConsumer> mConsumer; PreallocatedInputEventFactory mEventFactory; @@ -54,15 +229,15 @@ protected: status_t result = InputChannel::openInputChannelPair("channel name", serverChannel, clientChannel); ASSERT_EQ(OK, result); - mServerChannel = std::move(serverChannel); - mClientChannel = std::move(clientChannel); - mPublisher = std::make_unique<InputPublisher>(mServerChannel); - mConsumer = std::make_unique<InputConsumer>(mClientChannel); + mPublisher = std::make_unique<InputPublisher>(std::move(serverChannel)); + mConsumer = std::make_unique<InputConsumer>(std::move(clientChannel)); } void publishAndConsumeKeyEvent(); void publishAndConsumeMotionStream(); + void publishAndConsumeMotionDown(nsecs_t downTime); + void publishAndConsumeBatchedMotionMove(nsecs_t downTime); void publishAndConsumeFocusEvent(); void publishAndConsumeCaptureEvent(); void publishAndConsumeDragEvent(); @@ -73,24 +248,10 @@ protected: private: // The sequence number to use when publishing the next event uint32_t mSeq = 1; - - void publishAndConsumeMotionEvent( - int32_t deviceId, uint32_t source, int32_t displayId, std::array<uint8_t, 32> hmac, - int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags, - int32_t metaState, int32_t buttonState, MotionClassification classification, - float xScale, float yScale, float xOffset, float yOffset, float xPrecision, - float yPrecision, float xCursorPosition, float yCursorPosition, float rawXScale, - float rawYScale, float rawXOffset, float rawYOffset, nsecs_t downTime, - nsecs_t eventTime, const std::vector<PointerProperties>& pointerProperties, - const std::vector<PointerCoords>& pointerCoords); }; TEST_F(InputPublisherAndConsumerTest, GetChannel_ReturnsTheChannel) { - ASSERT_NE(nullptr, mPublisher->getChannel()); - ASSERT_NE(nullptr, mConsumer->getChannel()); - EXPECT_EQ(mServerChannel.get(), mPublisher->getChannel().get()); - EXPECT_EQ(mClientChannel.get(), mConsumer->getChannel().get()); - ASSERT_EQ(mPublisher->getChannel()->getConnectionToken(), + ASSERT_EQ(mPublisher->getChannel().getConnectionToken(), mConsumer->getChannel()->getConnectionToken()); } @@ -121,11 +282,14 @@ void InputPublisherAndConsumerTest::publishAndConsumeKeyEvent() { ASSERT_EQ(OK, status) << "publisher publishKeyEvent should return OK"; + waitUntilInputAvailable(*mConsumer); uint32_t consumeSeq; InputEvent* event; status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event); ASSERT_EQ(OK, status) << "consumer consume should return OK"; + EXPECT_FALSE(mConsumer->probablyHasInput()) + << "no events should be waiting after being consumed"; ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event"; @@ -185,176 +349,51 @@ void InputPublisherAndConsumerTest::publishAndConsumeMotionStream() { Pointer{.id = 2, .x = 300, .y = 400}}); } -void InputPublisherAndConsumerTest::publishAndConsumeMotionEvent( - int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers) { - constexpr int32_t deviceId = 1; - constexpr uint32_t source = AINPUT_SOURCE_TOUCHSCREEN; - constexpr int32_t displayId = ADISPLAY_ID_DEFAULT; - constexpr std::array<uint8_t, 32> hmac = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, - 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - constexpr int32_t actionButton = 0; - int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; - - if (action == AMOTION_EVENT_ACTION_CANCEL) { - flags |= AMOTION_EVENT_FLAG_CANCELED; - } - const size_t pointerCount = pointers.size(); - constexpr int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP; - constexpr int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON; - constexpr int32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY; - constexpr MotionClassification classification = MotionClassification::AMBIGUOUS_GESTURE; - constexpr float xScale = 2; - constexpr float yScale = 3; - constexpr float xOffset = -10; - constexpr float yOffset = -20; - constexpr float rawXScale = 4; - constexpr float rawYScale = -5; - constexpr float rawXOffset = -11; - constexpr float rawYOffset = 42; - constexpr float xPrecision = 0.25; - constexpr float yPrecision = 0.5; - constexpr float xCursorPosition = 1.3; - constexpr float yCursorPosition = 50.6; - - const nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC); - std::vector<PointerProperties> pointerProperties; - std::vector<PointerCoords> pointerCoords; - for (size_t i = 0; i < pointerCount; i++) { - pointerProperties.push_back({}); - pointerProperties[i].clear(); - pointerProperties[i].id = pointers[i].id; - pointerProperties[i].toolType = ToolType::FINGER; +void InputPublisherAndConsumerTest::publishAndConsumeMotionDown(nsecs_t downTime) { + publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime, + {Pointer{.id = 0, .x = 20, .y = 30}}); +} - pointerCoords.push_back({}); - pointerCoords[i].clear(); - pointerCoords[i].isResampled = pointers[i].isResampled; - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, pointers[i].x); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, pointers[i].y); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.5 * i); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.7 * i); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 1.5 * i); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 1.7 * i); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.5 * i); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.7 * i); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 3.5 * i); - } +void InputPublisherAndConsumerTest::publishAndConsumeBatchedMotionMove(nsecs_t downTime) { + uint32_t seq = mSeq++; + const std::vector<Pointer> pointers = {Pointer{.id = 0, .x = 20, .y = 30}}; + PublishMotionArgs args(AMOTION_EVENT_ACTION_MOVE, downTime, pointers, seq); + const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC); + publishMotionEvent(*mPublisher, args); - publishAndConsumeMotionEvent(deviceId, source, displayId, hmac, action, actionButton, flags, - edgeFlags, metaState, buttonState, classification, xScale, yScale, - xOffset, yOffset, xPrecision, yPrecision, xCursorPosition, - yCursorPosition, rawXScale, rawYScale, rawXOffset, rawYOffset, - downTime, eventTime, pointerProperties, pointerCoords); + // Consume leaving a batch behind. + uint32_t consumeSeq; + InputEvent* event; + status_t status = mConsumer->consume(&mEventFactory, + /*consumeBatches=*/false, -1, &consumeSeq, &event); + ASSERT_EQ(WOULD_BLOCK, status) + << "consumer consume should return WOULD_BLOCK when a new batch is started"; + ASSERT_TRUE(mConsumer->hasPendingBatch()) << "consume should have created a batch"; + EXPECT_TRUE(mConsumer->probablyHasInput()) + << "should deterministically have input because there is a batch"; + sendAndVerifyFinishedSignal(*mConsumer, *mPublisher, seq, publishTime); } void InputPublisherAndConsumerTest::publishAndConsumeMotionEvent( - int32_t deviceId, uint32_t source, int32_t displayId, std::array<uint8_t, 32> hmac, - int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags, int32_t metaState, - int32_t buttonState, MotionClassification classification, float xScale, float yScale, - float xOffset, float yOffset, float xPrecision, float yPrecision, float xCursorPosition, - float yCursorPosition, float rawXScale, float rawYScale, float rawXOffset, float rawYOffset, - nsecs_t downTime, nsecs_t eventTime, - const std::vector<PointerProperties>& pointerProperties, - const std::vector<PointerCoords>& pointerCoords) { - const uint32_t seq = mSeq++; - const int32_t eventId = InputEvent::nextId(); - ui::Transform transform; - transform.set({xScale, 0, xOffset, 0, yScale, yOffset, 0, 0, 1}); - ui::Transform rawTransform; - rawTransform.set({rawXScale, 0, rawXOffset, 0, rawYScale, rawYOffset, 0, 0, 1}); - - status_t status; - ASSERT_EQ(pointerProperties.size(), pointerCoords.size()); - const size_t pointerCount = pointerProperties.size(); - const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC); - status = mPublisher->publishMotionEvent(seq, eventId, deviceId, source, displayId, hmac, action, - actionButton, flags, edgeFlags, metaState, buttonState, - classification, transform, xPrecision, yPrecision, - xCursorPosition, yCursorPosition, rawTransform, - downTime, eventTime, pointerCount, - pointerProperties.data(), pointerCoords.data()); - ASSERT_EQ(OK, status) << "publisher publishMotionEvent should return OK"; + int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers) { + uint32_t seq = mSeq++; + PublishMotionArgs args(action, downTime, pointers, seq); + nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC); + publishMotionEvent(*mPublisher, args); uint32_t consumeSeq; InputEvent* event; - status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event); - ASSERT_EQ(OK, status) - << "consumer consume should return OK"; - + status_t status = + mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event); + ASSERT_EQ(OK, status) << "consumer consume should return OK"; ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event"; ASSERT_EQ(InputEventType::MOTION, event->getType()) << "consumer should have returned a motion event"; - - MotionEvent* motionEvent = static_cast<MotionEvent*>(event); EXPECT_EQ(seq, consumeSeq); - EXPECT_EQ(eventId, motionEvent->getId()); - EXPECT_EQ(deviceId, motionEvent->getDeviceId()); - EXPECT_EQ(source, motionEvent->getSource()); - EXPECT_EQ(displayId, motionEvent->getDisplayId()); - EXPECT_EQ(hmac, motionEvent->getHmac()); - EXPECT_EQ(action, motionEvent->getAction()); - EXPECT_EQ(flags, motionEvent->getFlags()); - EXPECT_EQ(edgeFlags, motionEvent->getEdgeFlags()); - EXPECT_EQ(metaState, motionEvent->getMetaState()); - EXPECT_EQ(buttonState, motionEvent->getButtonState()); - EXPECT_EQ(classification, motionEvent->getClassification()); - EXPECT_EQ(transform, motionEvent->getTransform()); - EXPECT_EQ(xOffset, motionEvent->getXOffset()); - EXPECT_EQ(yOffset, motionEvent->getYOffset()); - EXPECT_EQ(xPrecision, motionEvent->getXPrecision()); - EXPECT_EQ(yPrecision, motionEvent->getYPrecision()); - EXPECT_NEAR(xCursorPosition, motionEvent->getRawXCursorPosition(), EPSILON); - EXPECT_NEAR(yCursorPosition, motionEvent->getRawYCursorPosition(), EPSILON); - EXPECT_NEAR(xCursorPosition * xScale + xOffset, motionEvent->getXCursorPosition(), EPSILON); - EXPECT_NEAR(yCursorPosition * yScale + yOffset, motionEvent->getYCursorPosition(), EPSILON); - EXPECT_EQ(rawTransform, motionEvent->getRawTransform()); - EXPECT_EQ(downTime, motionEvent->getDownTime()); - EXPECT_EQ(eventTime, motionEvent->getEventTime()); - EXPECT_EQ(pointerCount, motionEvent->getPointerCount()); - EXPECT_EQ(0U, motionEvent->getHistorySize()); - for (size_t i = 0; i < pointerCount; i++) { - SCOPED_TRACE(i); - EXPECT_EQ(pointerProperties[i].id, motionEvent->getPointerId(i)); - EXPECT_EQ(pointerProperties[i].toolType, motionEvent->getToolType(i)); - - const auto& pc = pointerCoords[i]; - EXPECT_EQ(pc, motionEvent->getSamplePointerCoords()[i]); - - EXPECT_NEAR(pc.getX() * rawXScale + rawXOffset, motionEvent->getRawX(i), EPSILON); - EXPECT_NEAR(pc.getY() * rawYScale + rawYOffset, motionEvent->getRawY(i), EPSILON); - EXPECT_NEAR(pc.getX() * xScale + xOffset, motionEvent->getX(i), EPSILON); - EXPECT_NEAR(pc.getY() * yScale + yOffset, motionEvent->getY(i), EPSILON); - EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), motionEvent->getPressure(i)); - EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_SIZE), motionEvent->getSize(i)); - EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), motionEvent->getTouchMajor(i)); - EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), motionEvent->getTouchMinor(i)); - EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), motionEvent->getToolMajor(i)); - EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), motionEvent->getToolMinor(i)); - - // Calculate the orientation after scaling, keeping in mind that an orientation of 0 is - // "up", and the positive y direction is "down". - const float unscaledOrientation = pc.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION); - const float x = sinf(unscaledOrientation) * xScale; - const float y = -cosf(unscaledOrientation) * yScale; - EXPECT_EQ(atan2f(x, -y), motionEvent->getOrientation(i)); - } - - status = mConsumer->sendFinishedSignal(seq, false); - ASSERT_EQ(OK, status) - << "consumer sendFinishedSignal should return OK"; - - Result<InputPublisher::ConsumerResponse> result = mPublisher->receiveConsumerResponse(); - ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK"; - ASSERT_TRUE(std::holds_alternative<InputPublisher::Finished>(*result)); - const InputPublisher::Finished& finish = std::get<InputPublisher::Finished>(*result); - ASSERT_EQ(seq, finish.seq) - << "receiveConsumerResponse should have returned the original sequence number"; - ASSERT_FALSE(finish.handled) - << "receiveConsumerResponse should have set handled to consumer's reply"; - ASSERT_GE(finish.consumeTime, publishTime) - << "finished signal's consume time should be greater than publish time"; + verifyArgsEqualToEvent(args, static_cast<const MotionEvent&>(*event)); + sendAndVerifyFinishedSignal(*mConsumer, *mPublisher, seq, publishTime); } void InputPublisherAndConsumerTest::publishAndConsumeFocusEvent() { @@ -546,6 +585,15 @@ TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_EndToEnd) { ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionStream()); } +TEST_F(InputPublisherAndConsumerTest, PublishMotionMoveEvent_EndToEnd) { + // Publish a DOWN event before MOVE to pass the InputVerifier checks. + const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC); + ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionDown(downTime)); + + // Publish the MOVE event and check expectations. + ASSERT_NO_FATAL_FAILURE(publishAndConsumeBatchedMotionMove(downTime)); +} + TEST_F(InputPublisherAndConsumerTest, PublishFocusEvent_EndToEnd) { ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent()); } diff --git a/libs/input/tests/VelocityControl_test.cpp b/libs/input/tests/VelocityControl_test.cpp new file mode 100644 index 0000000000..63d64c6048 --- /dev/null +++ b/libs/input/tests/VelocityControl_test.cpp @@ -0,0 +1,129 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <input/VelocityControl.h> + +#include <limits> + +#include <gtest/gtest.h> +#include <input/AccelerationCurve.h> +#include <utils/Timers.h> + +namespace android { + +namespace { + +constexpr float EPSILON = 0.001; +constexpr float COUNTS_PER_MM = 800 / 25.4; + +} // namespace + +class CurvedVelocityControlTest : public testing::Test { +protected: + CurvedVelocityControl mCtrl; + + void moveWithoutCheckingResult(nsecs_t eventTime, float deltaX, float deltaY) { + mCtrl.move(eventTime, &deltaX, &deltaY); + } + + void moveAndCheckRatio(nsecs_t eventTime, const float deltaX, const float deltaY, + float expectedRatio) { + float newDeltaX = deltaX, newDeltaY = deltaY; + mCtrl.move(eventTime, &newDeltaX, &newDeltaY); + ASSERT_NEAR(expectedRatio * deltaX, newDeltaX, EPSILON) + << "Expected ratio of " << expectedRatio << " in X, but actual ratio was " + << newDeltaX / deltaX; + ASSERT_NEAR(expectedRatio * deltaY, newDeltaY, EPSILON) + << "Expected ratio of " << expectedRatio << " in Y, but actual ratio was " + << newDeltaY / deltaY; + } +}; + +TEST_F(CurvedVelocityControlTest, SegmentSelection) { + // To make the maths simple, use a "curve" that's actually just a sequence of steps. + mCtrl.setCurve({ + {10, 2, 0}, + {20, 3, 0}, + {30, 4, 0}, + {std::numeric_limits<double>::infinity(), 5, 0}, + }); + + // Establish a velocity of 16 mm/s. + moveWithoutCheckingResult(0, 0, 0); + moveWithoutCheckingResult(10'000'000, 0.16 * COUNTS_PER_MM, 0); + moveWithoutCheckingResult(20'000'000, 0.16 * COUNTS_PER_MM, 0); + moveWithoutCheckingResult(30'000'000, 0.16 * COUNTS_PER_MM, 0); + ASSERT_NO_FATAL_FAILURE( + moveAndCheckRatio(40'000'000, 0.16 * COUNTS_PER_MM, 0, /*expectedRatio=*/3)); + + // Establish a velocity of 50 mm/s. + mCtrl.reset(); + moveWithoutCheckingResult(100'000'000, 0, 0); + moveWithoutCheckingResult(110'000'000, 0.50 * COUNTS_PER_MM, 0); + moveWithoutCheckingResult(120'000'000, 0.50 * COUNTS_PER_MM, 0); + moveWithoutCheckingResult(130'000'000, 0.50 * COUNTS_PER_MM, 0); + ASSERT_NO_FATAL_FAILURE( + moveAndCheckRatio(140'000'000, 0.50 * COUNTS_PER_MM, 0, /*expectedRatio=*/5)); +} + +TEST_F(CurvedVelocityControlTest, RatioDefaultsToFirstSegmentWhenVelocityIsUnknown) { + mCtrl.setCurve({ + {10, 3, 0}, + {20, 2, 0}, + {std::numeric_limits<double>::infinity(), 4, 0}, + }); + + // Only send two moves, which won't be enough for VelocityTracker to calculate a velocity from. + moveWithoutCheckingResult(0, 0, 0); + ASSERT_NO_FATAL_FAILURE( + moveAndCheckRatio(10'000'000, 0.25 * COUNTS_PER_MM, 0, /*expectedRatio=*/3)); +} + +TEST_F(CurvedVelocityControlTest, VelocityCalculatedUsingBothAxes) { + mCtrl.setCurve({ + {8.0, 3, 0}, + {8.1, 2, 0}, + {std::numeric_limits<double>::infinity(), 4, 0}, + }); + + // Establish a velocity of 8.06 (= √65 = √(7²+4²)) mm/s between the two axes. + moveWithoutCheckingResult(0, 0, 0); + moveWithoutCheckingResult(10'000'000, 0.07 * COUNTS_PER_MM, 0.04 * COUNTS_PER_MM); + moveWithoutCheckingResult(20'000'000, 0.07 * COUNTS_PER_MM, 0.04 * COUNTS_PER_MM); + moveWithoutCheckingResult(30'000'000, 0.07 * COUNTS_PER_MM, 0.04 * COUNTS_PER_MM); + ASSERT_NO_FATAL_FAILURE(moveAndCheckRatio(40'000'000, 0.07 * COUNTS_PER_MM, + 0.04 * COUNTS_PER_MM, + /*expectedRatio=*/2)); +} + +TEST_F(CurvedVelocityControlTest, ReciprocalTerm) { + mCtrl.setCurve({ + {10, 2, 0}, + {20, 3, -10}, + {std::numeric_limits<double>::infinity(), 3, 0}, + }); + + // Establish a velocity of 15 mm/s. + moveWithoutCheckingResult(0, 0, 0); + moveWithoutCheckingResult(10'000'000, 0, 0.15 * COUNTS_PER_MM); + moveWithoutCheckingResult(20'000'000, 0, 0.15 * COUNTS_PER_MM); + moveWithoutCheckingResult(30'000'000, 0, 0.15 * COUNTS_PER_MM); + // Expected ratio is 3 - 10 / 15 = 2.33333... + ASSERT_NO_FATAL_FAILURE( + moveAndCheckRatio(40'000'000, 0, 0.15 * COUNTS_PER_MM, /*expectedRatio=*/2.33333)); +} + +} // namespace android
\ No newline at end of file diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp index e7b2195056..52612870ab 100644 --- a/libs/nativewindow/AHardwareBuffer.cpp +++ b/libs/nativewindow/AHardwareBuffer.cpp @@ -113,6 +113,22 @@ static_assert( AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM, "HAL and AHardwareBuffer pixel format don't match"); +static enum AHardwareBufferStatus filterStatus(status_t status) { + switch (status) { + case STATUS_OK: + return AHARDWAREBUFFER_STATUS_OK; + case STATUS_NO_MEMORY: + return AHARDWAREBUFFER_STATUS_NO_MEMORY; + case STATUS_BAD_VALUE: + return AHARDWAREBUFFER_STATUS_BAD_VALUE; + case STATUS_UNKNOWN_TRANSACTION: + case STATUS_INVALID_OPERATION: + return AHARDWAREBUFFER_STATUS_UNSUPPORTED; + default: + return AHARDWAREBUFFER_STATUS_UNKNOWN_ERROR; + } +} + // ---------------------------------------------------------------------------- // Public functions // ---------------------------------------------------------------------------- @@ -511,6 +527,24 @@ binder_status_t AHardwareBuffer_writeToParcel(const AHardwareBuffer* _Nonnull bu return AParcel_viewPlatformParcel(parcel)->write(*gb); } +ADataSpace AHardwareBuffer_getDataSpace(const AHardwareBuffer* _Nonnull buffer) { + const GraphicBuffer* gb = AHardwareBuffer_to_GraphicBuffer(buffer); + if (!gb) return ADATASPACE_UNKNOWN; + ui::Dataspace dataspace = ui::Dataspace::UNKNOWN; + status_t status = gb->getDataspace(&dataspace); + if (status != OK) { + return ADATASPACE_UNKNOWN; + } + return static_cast<ADataSpace>(dataspace); +} + +enum AHardwareBufferStatus AHardwareBuffer_setDataSpace(AHardwareBuffer* buffer, + ADataSpace dataspace) { + GraphicBuffer* gb = AHardwareBuffer_to_GraphicBuffer(buffer); + auto& mapper = GraphicBufferMapper::get(); + return filterStatus(mapper.setDataspace(gb->handle, static_cast<ui::Dataspace>(dataspace))); +} + // ---------------------------------------------------------------------------- // VNDK functions // ---------------------------------------------------------------------------- @@ -552,6 +586,56 @@ int AHardwareBuffer_createFromHandle(const AHardwareBuffer_Desc* desc, return NO_ERROR; } +enum AHardwareBufferStatus AHardwareBuffer_allocateWithOptions( + const AHardwareBuffer_Desc* desc, const AHardwareBufferLongOptions* additionalOptions, + size_t additionalOptionsSize, AHardwareBuffer** outBuffer) { + (void)additionalOptions; + (void)additionalOptionsSize; + if (!outBuffer || !desc) return AHARDWAREBUFFER_STATUS_BAD_VALUE; + if (!AHardwareBuffer_isValidDescription(desc, /*log=*/true)) { + return AHARDWAREBUFFER_STATUS_BAD_VALUE; + } + + int format = AHardwareBuffer_convertToPixelFormat(desc->format); + uint64_t usage = AHardwareBuffer_convertToGrallocUsageBits(desc->usage); + + std::vector<GraphicBufferAllocator::AdditionalOptions> extras; + extras.reserve(additionalOptionsSize); + for (size_t i = 0; i < additionalOptionsSize; i++) { + extras.push_back(GraphicBufferAllocator::AdditionalOptions{additionalOptions[i].name, + additionalOptions[i].value}); + } + + const auto extrasCount = extras.size(); + auto gbuffer = sp<GraphicBuffer>::make(GraphicBufferAllocator::AllocationRequest{ + .importBuffer = true, + .width = desc->width, + .height = desc->height, + .format = format, + .layerCount = desc->layers, + .usage = usage, + .requestorName = std::string("AHardwareBuffer pid [") + std::to_string(getpid()) + "]", + .extras = std::move(extras), + }); + + status_t err = gbuffer->initCheck(); + if (err != 0 || gbuffer->handle == nullptr) { + if (err == NO_MEMORY) { + GraphicBuffer::dumpAllocationsToSystemLog(); + } + ALOGE("GraphicBuffer(w=%u, h=%u, lc=%u, extrasCount=%zd) failed (%s), handle=%p", + desc->width, desc->height, desc->layers, extrasCount, strerror(-err), + gbuffer->handle); + return filterStatus(err == 0 ? UNKNOWN_ERROR : err); + } + + *outBuffer = AHardwareBuffer_from_GraphicBuffer(gbuffer.get()); + + // Ensure the buffer doesn't get destroyed when the sp<> goes away. + AHardwareBuffer_acquire(*outBuffer); + return AHARDWAREBUFFER_STATUS_OK; +} + // ---------------------------------------------------------------------------- // Helpers implementation // ---------------------------------------------------------------------------- @@ -652,12 +736,9 @@ uint32_t AHardwareBuffer_convertToPixelFormat(uint32_t ahardwarebuffer_format) { return ahardwarebuffer_format; } +// TODO: Remove, this is just to make an overly aggressive ABI checker happy int32_t AHardwareBuffer_getDataSpace(AHardwareBuffer* buffer) { - GraphicBuffer* gb = AHardwareBuffer_to_GraphicBuffer(buffer); - auto& mapper = GraphicBufferMapper::get(); - ui::Dataspace dataspace = ui::Dataspace::UNKNOWN; - mapper.getDataspace(gb->handle, &dataspace); - return static_cast<int32_t>(dataspace); + return ::AHardwareBuffer_getDataSpace(buffer); } uint64_t AHardwareBuffer_convertToGrallocUsageBits(uint64_t usage) { diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp index dd5958de28..f97eed5db3 100644 --- a/libs/nativewindow/ANativeWindow.cpp +++ b/libs/nativewindow/ANativeWindow.cpp @@ -152,31 +152,56 @@ int32_t ANativeWindow_setBuffersTransform(ANativeWindow* window, int32_t transfo int32_t ANativeWindow_setBuffersDataSpace(ANativeWindow* window, int32_t dataSpace) { static_assert(static_cast<int>(ADATASPACE_UNKNOWN) == static_cast<int>(HAL_DATASPACE_UNKNOWN)); - static_assert(static_cast<int>(STANDARD_MASK) == static_cast<int>(HAL_DATASPACE_STANDARD_MASK)); - static_assert(static_cast<int>(STANDARD_UNSPECIFIED) == static_cast<int>(HAL_DATASPACE_STANDARD_UNSPECIFIED)); - static_assert(static_cast<int>(STANDARD_BT709) == static_cast<int>(HAL_DATASPACE_STANDARD_BT709)); - static_assert(static_cast<int>(STANDARD_BT601_625) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_625)); - static_assert(static_cast<int>(STANDARD_BT601_625_UNADJUSTED) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED)); - static_assert(static_cast<int>(STANDARD_BT601_525) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_525)); - static_assert(static_cast<int>(STANDARD_BT601_525_UNADJUSTED) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED)); - static_assert(static_cast<int>(STANDARD_BT470M) == static_cast<int>(HAL_DATASPACE_STANDARD_BT470M)); - static_assert(static_cast<int>(STANDARD_FILM) == static_cast<int>(HAL_DATASPACE_STANDARD_FILM)); - static_assert(static_cast<int>(STANDARD_DCI_P3) == static_cast<int>(HAL_DATASPACE_STANDARD_DCI_P3)); - static_assert(static_cast<int>(STANDARD_ADOBE_RGB) == static_cast<int>(HAL_DATASPACE_STANDARD_ADOBE_RGB)); - static_assert(static_cast<int>(TRANSFER_MASK) == static_cast<int>(HAL_DATASPACE_TRANSFER_MASK)); - static_assert(static_cast<int>(TRANSFER_UNSPECIFIED) == static_cast<int>(HAL_DATASPACE_TRANSFER_UNSPECIFIED)); - static_assert(static_cast<int>(TRANSFER_LINEAR) == static_cast<int>(HAL_DATASPACE_TRANSFER_LINEAR)); - static_assert(static_cast<int>(TRANSFER_SMPTE_170M) == static_cast<int>(HAL_DATASPACE_TRANSFER_SMPTE_170M)); - static_assert(static_cast<int>(TRANSFER_GAMMA2_2) == static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_2)); - static_assert(static_cast<int>(TRANSFER_GAMMA2_6) == static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_6)); - static_assert(static_cast<int>(TRANSFER_GAMMA2_8) == static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_8)); - static_assert(static_cast<int>(TRANSFER_ST2084) == static_cast<int>(HAL_DATASPACE_TRANSFER_ST2084)); - static_assert(static_cast<int>(TRANSFER_HLG) == static_cast<int>(HAL_DATASPACE_TRANSFER_HLG)); - static_assert(static_cast<int>(RANGE_MASK) == static_cast<int>(HAL_DATASPACE_RANGE_MASK)); - static_assert(static_cast<int>(RANGE_UNSPECIFIED) == static_cast<int>(HAL_DATASPACE_RANGE_UNSPECIFIED)); - static_assert(static_cast<int>(RANGE_FULL) == static_cast<int>(HAL_DATASPACE_RANGE_FULL)); - static_assert(static_cast<int>(RANGE_LIMITED) == static_cast<int>(HAL_DATASPACE_RANGE_LIMITED)); - static_assert(static_cast<int>(RANGE_EXTENDED) == static_cast<int>(HAL_DATASPACE_RANGE_EXTENDED)); + static_assert(static_cast<int>(ADATASPACE_STANDARD_MASK) == + static_cast<int>(HAL_DATASPACE_STANDARD_MASK)); + static_assert(static_cast<int>(ADATASPACE_STANDARD_UNSPECIFIED) == + static_cast<int>(HAL_DATASPACE_STANDARD_UNSPECIFIED)); + static_assert(static_cast<int>(ADATASPACE_STANDARD_BT709) == + static_cast<int>(HAL_DATASPACE_STANDARD_BT709)); + static_assert(static_cast<int>(ADATASPACE_STANDARD_BT601_625) == + static_cast<int>(HAL_DATASPACE_STANDARD_BT601_625)); + static_assert(static_cast<int>(ADATASPACE_STANDARD_BT601_625_UNADJUSTED) == + static_cast<int>(HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED)); + static_assert(static_cast<int>(ADATASPACE_STANDARD_BT601_525) == + static_cast<int>(HAL_DATASPACE_STANDARD_BT601_525)); + static_assert(static_cast<int>(ADATASPACE_STANDARD_BT601_525_UNADJUSTED) == + static_cast<int>(HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED)); + static_assert(static_cast<int>(ADATASPACE_STANDARD_BT470M) == + static_cast<int>(HAL_DATASPACE_STANDARD_BT470M)); + static_assert(static_cast<int>(ADATASPACE_STANDARD_FILM) == + static_cast<int>(HAL_DATASPACE_STANDARD_FILM)); + static_assert(static_cast<int>(ADATASPACE_STANDARD_DCI_P3) == + static_cast<int>(HAL_DATASPACE_STANDARD_DCI_P3)); + static_assert(static_cast<int>(ADATASPACE_STANDARD_ADOBE_RGB) == + static_cast<int>(HAL_DATASPACE_STANDARD_ADOBE_RGB)); + static_assert(static_cast<int>(ADATASPACE_TRANSFER_MASK) == + static_cast<int>(HAL_DATASPACE_TRANSFER_MASK)); + static_assert(static_cast<int>(ADATASPACE_TRANSFER_UNSPECIFIED) == + static_cast<int>(HAL_DATASPACE_TRANSFER_UNSPECIFIED)); + static_assert(static_cast<int>(ADATASPACE_TRANSFER_LINEAR) == + static_cast<int>(HAL_DATASPACE_TRANSFER_LINEAR)); + static_assert(static_cast<int>(ADATASPACE_TRANSFER_SMPTE_170M) == + static_cast<int>(HAL_DATASPACE_TRANSFER_SMPTE_170M)); + static_assert(static_cast<int>(ADATASPACE_TRANSFER_GAMMA2_2) == + static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_2)); + static_assert(static_cast<int>(ADATASPACE_TRANSFER_GAMMA2_6) == + static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_6)); + static_assert(static_cast<int>(ADATASPACE_TRANSFER_GAMMA2_8) == + static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_8)); + static_assert(static_cast<int>(ADATASPACE_TRANSFER_ST2084) == + static_cast<int>(HAL_DATASPACE_TRANSFER_ST2084)); + static_assert(static_cast<int>(ADATASPACE_TRANSFER_HLG) == + static_cast<int>(HAL_DATASPACE_TRANSFER_HLG)); + static_assert(static_cast<int>(ADATASPACE_RANGE_MASK) == + static_cast<int>(HAL_DATASPACE_RANGE_MASK)); + static_assert(static_cast<int>(ADATASPACE_RANGE_UNSPECIFIED) == + static_cast<int>(HAL_DATASPACE_RANGE_UNSPECIFIED)); + static_assert(static_cast<int>(ADATASPACE_RANGE_FULL) == + static_cast<int>(HAL_DATASPACE_RANGE_FULL)); + static_assert(static_cast<int>(ADATASPACE_RANGE_LIMITED) == + static_cast<int>(HAL_DATASPACE_RANGE_LIMITED)); + static_assert(static_cast<int>(ADATASPACE_RANGE_EXTENDED) == + static_cast<int>(HAL_DATASPACE_RANGE_EXTENDED)); static_assert(static_cast<int>(ADATASPACE_SRGB) == static_cast<int>(HAL_DATASPACE_V0_SRGB)); static_assert(static_cast<int>(ADATASPACE_SCRGB) == static_cast<int>(HAL_DATASPACE_V0_SCRGB)); static_assert(static_cast<int>(ADATASPACE_DISPLAY_P3) == static_cast<int>(HAL_DATASPACE_DISPLAY_P3)); diff --git a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h index 880c694934..f145a2f7c2 100644 --- a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h +++ b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h @@ -27,8 +27,8 @@ #include <stdint.h> -struct AHardwareBuffer; -struct AHardwareBuffer_Desc; +#include <vndk/hardware_buffer.h> + struct ANativeWindowBuffer; namespace android { @@ -46,11 +46,6 @@ uint32_t AHardwareBuffer_convertFromPixelFormat(uint32_t format); // convert HAL format to AHardwareBuffer format (note: this is a no-op) uint32_t AHardwareBuffer_convertToPixelFormat(uint32_t format); -// retrieves a dataspace from the AHardwareBuffer metadata, if the device -// support gralloc metadata. Returns UNKNOWN if gralloc metadata is not -// supported. -int32_t AHardwareBuffer_getDataSpace(AHardwareBuffer* buffer); - // convert AHardwareBuffer usage bits to HAL usage bits (note: this is a no-op) uint64_t AHardwareBuffer_convertFromGrallocUsageBits(uint64_t usage); diff --git a/libs/nativewindow/include/android/data_space.h b/libs/nativewindow/include/android/data_space.h index 9f8ae863db..8056d9ac4f 100644 --- a/libs/nativewindow/include/android/data_space.h +++ b/libs/nativewindow/include/android/data_space.h @@ -64,7 +64,7 @@ enum ADataSpace : int32_t { * Defines the chromaticity coordinates of the source primaries in terms of * the CIE 1931 definition of x and y specified in ISO 11664-1. */ - STANDARD_MASK = 63 << 16, + ADATASPACE_STANDARD_MASK = 63 << 16, /** * Chromacity coordinates are unknown or are determined by the application. @@ -79,7 +79,7 @@ enum ADataSpace : int32_t { * For all other formats standard is undefined, and implementations should use * an appropriate standard for the data represented. */ - STANDARD_UNSPECIFIED = 0 << 16, + ADATASPACE_STANDARD_UNSPECIFIED = 0 << 16, /** * <pre> @@ -92,7 +92,7 @@ enum ADataSpace : int32_t { * Use the unadjusted KR = 0.2126, KB = 0.0722 luminance interpretation * for RGB conversion. */ - STANDARD_BT709 = 1 << 16, + ADATASPACE_STANDARD_BT709 = 1 << 16, /** * <pre> @@ -107,7 +107,7 @@ enum ADataSpace : int32_t { * to minimize the color shift into RGB space that uses BT.709 * primaries. */ - STANDARD_BT601_625 = 2 << 16, + ADATASPACE_STANDARD_BT601_625 = 2 << 16, /** * <pre> @@ -120,7 +120,7 @@ enum ADataSpace : int32_t { * Use the unadjusted KR = 0.222, KB = 0.071 luminance interpretation * for RGB conversion. */ - STANDARD_BT601_625_UNADJUSTED = 3 << 16, + ADATASPACE_STANDARD_BT601_625_UNADJUSTED = 3 << 16, /** * <pre> @@ -135,7 +135,7 @@ enum ADataSpace : int32_t { * to minimize the color shift into RGB space that uses BT.709 * primaries. */ - STANDARD_BT601_525 = 4 << 16, + ADATASPACE_STANDARD_BT601_525 = 4 << 16, /** * <pre> @@ -148,7 +148,7 @@ enum ADataSpace : int32_t { * Use the unadjusted KR = 0.212, KB = 0.087 luminance interpretation * for RGB conversion (as in SMPTE 240M). */ - STANDARD_BT601_525_UNADJUSTED = 5 << 16, + ADATASPACE_STANDARD_BT601_525_UNADJUSTED = 5 << 16, /** * <pre> @@ -161,7 +161,7 @@ enum ADataSpace : int32_t { * Use the unadjusted KR = 0.2627, KB = 0.0593 luminance interpretation * for RGB conversion. */ - STANDARD_BT2020 = 6 << 16, + ADATASPACE_STANDARD_BT2020 = 6 << 16, /** * <pre> @@ -174,7 +174,7 @@ enum ADataSpace : int32_t { * Use the unadjusted KR = 0.2627, KB = 0.0593 luminance interpretation * for RGB conversion using the linear domain. */ - STANDARD_BT2020_CONSTANT_LUMINANCE = 7 << 16, + ADATASPACE_STANDARD_BT2020_CONSTANT_LUMINANCE = 7 << 16, /** * <pre> @@ -187,7 +187,7 @@ enum ADataSpace : int32_t { * Use the unadjusted KR = 0.30, KB = 0.11 luminance interpretation * for RGB conversion. */ - STANDARD_BT470M = 8 << 16, + ADATASPACE_STANDARD_BT470M = 8 << 16, /** * <pre> @@ -200,7 +200,7 @@ enum ADataSpace : int32_t { * Use the unadjusted KR = 0.254, KB = 0.068 luminance interpretation * for RGB conversion. */ - STANDARD_FILM = 9 << 16, + ADATASPACE_STANDARD_FILM = 9 << 16, /** * SMPTE EG 432-1 and SMPTE RP 431-2. (DCI-P3) @@ -211,7 +211,7 @@ enum ADataSpace : int32_t { * red 0.680 0.320 * white (D65) 0.3127 0.3290</pre> */ - STANDARD_DCI_P3 = 10 << 16, + ADATASPACE_STANDARD_DCI_P3 = 10 << 16, /** * Adobe RGB @@ -222,7 +222,7 @@ enum ADataSpace : int32_t { * red 0.640 0.330 * white (D65) 0.3127 0.3290</pre> */ - STANDARD_ADOBE_RGB = 11 << 16, + ADATASPACE_STANDARD_ADOBE_RGB = 11 << 16, /** * Transfer aspect @@ -237,7 +237,7 @@ enum ADataSpace : int32_t { * component. Implementation may apply the transfer function in RGB space * for all pixel formats if desired. */ - TRANSFER_MASK = 31 << 22, + ADATASPACE_TRANSFER_MASK = 31 << 22, /** * Transfer characteristics are unknown or are determined by the @@ -245,13 +245,13 @@ enum ADataSpace : int32_t { * * Implementations should use the following transfer functions: * - * For YCbCr formats: use TRANSFER_SMPTE_170M - * For RGB formats: use TRANSFER_SRGB + * For YCbCr formats: use ADATASPACE_TRANSFER_SMPTE_170M + * For RGB formats: use ADATASPACE_TRANSFER_SRGB * * For all other formats transfer function is undefined, and implementations * should use an appropriate standard for the data represented. */ - TRANSFER_UNSPECIFIED = 0 << 22, + ADATASPACE_TRANSFER_UNSPECIFIED = 0 << 22, /** * Linear transfer. @@ -261,7 +261,7 @@ enum ADataSpace : int32_t { * L - luminance of image 0 <= L <= 1 for conventional colorimetry * E - corresponding electrical signal</pre> */ - TRANSFER_LINEAR = 1 << 22, + ADATASPACE_TRANSFER_LINEAR = 1 << 22, /** * sRGB transfer. @@ -272,7 +272,7 @@ enum ADataSpace : int32_t { * L - luminance of image 0 <= L <= 1 for conventional colorimetry * E - corresponding electrical signal</pre> */ - TRANSFER_SRGB = 2 << 22, + ADATASPACE_TRANSFER_SRGB = 2 << 22, /** * SMPTE 170M transfer. @@ -283,7 +283,7 @@ enum ADataSpace : int32_t { * L - luminance of image 0 <= L <= 1 for conventional colorimetry * E - corresponding electrical signal</pre> */ - TRANSFER_SMPTE_170M = 3 << 22, + ADATASPACE_TRANSFER_SMPTE_170M = 3 << 22, /** * Display gamma 2.2. @@ -293,7 +293,7 @@ enum ADataSpace : int32_t { * L - luminance of image 0 <= L <= 1 for conventional colorimetry * E - corresponding electrical signal</pre> */ - TRANSFER_GAMMA2_2 = 4 << 22, + ADATASPACE_TRANSFER_GAMMA2_2 = 4 << 22, /** * Display gamma 2.6. @@ -303,7 +303,7 @@ enum ADataSpace : int32_t { * L - luminance of image 0 <= L <= 1 for conventional colorimetry * E - corresponding electrical signal</pre> */ - TRANSFER_GAMMA2_6 = 5 << 22, + ADATASPACE_TRANSFER_GAMMA2_6 = 5 << 22, /** * Display gamma 2.8. @@ -313,7 +313,7 @@ enum ADataSpace : int32_t { * L - luminance of image 0 <= L <= 1 for conventional colorimetry * E - corresponding electrical signal</pre> */ - TRANSFER_GAMMA2_8 = 6 << 22, + ADATASPACE_TRANSFER_GAMMA2_8 = 6 << 22, /** * SMPTE ST 2084 (Dolby Perceptual Quantizer). @@ -329,7 +329,7 @@ enum ADataSpace : int32_t { * L = 1 corresponds to 10000 cd/m2 * E - corresponding electrical signal</pre> */ - TRANSFER_ST2084 = 7 << 22, + ADATASPACE_TRANSFER_ST2084 = 7 << 22, /** * ARIB STD-B67 Hybrid Log Gamma. @@ -345,7 +345,7 @@ enum ADataSpace : int32_t { * to reference white level of 100 cd/m2 * E - corresponding electrical signal</pre> */ - TRANSFER_HLG = 8 << 22, + ADATASPACE_TRANSFER_HLG = 8 << 22, /** * Range aspect @@ -353,7 +353,7 @@ enum ADataSpace : int32_t { * Defines the range of values corresponding to the unit range of 0-1. * This is defined for YCbCr only, but can be expanded to RGB space. */ - RANGE_MASK = 7 << 27, + ADATASPACE_RANGE_MASK = 7 << 27, /** * Range is unknown or are determined by the application. Implementations @@ -366,13 +366,13 @@ enum ADataSpace : int32_t { * For all other formats range is undefined, and implementations should use * an appropriate range for the data represented. */ - RANGE_UNSPECIFIED = 0 << 27, + ADATASPACE_RANGE_UNSPECIFIED = 0 << 27, /** * Full range uses all values for Y, Cb and Cr from * 0 to 2^b-1, where b is the bit depth of the color format. */ - RANGE_FULL = 1 << 27, + ADATASPACE_RANGE_FULL = 1 << 27, /** * Limited range uses values 16/256*2^b to 235/256*2^b for Y, and @@ -387,7 +387,7 @@ enum ADataSpace : int32_t { * Luma (Y) samples should range from 64 to 940, inclusive * Chroma (Cb, Cr) samples should range from 64 to 960, inclusive */ - RANGE_LIMITED = 2 << 27, + ADATASPACE_RANGE_LIMITED = 2 << 27, /** * Extended range is used for scRGB. Intended for use with @@ -396,7 +396,7 @@ enum ADataSpace : int32_t { * color outside the sRGB gamut. * Used to blend / merge multiple dataspaces on a single display. */ - RANGE_EXTENDED = 3 << 27, + ADATASPACE_RANGE_EXTENDED = 3 << 27, /** * scRGB linear encoding @@ -411,7 +411,8 @@ enum ADataSpace : int32_t { * * Uses extended range, linear transfer and BT.709 standard. */ - ADATASPACE_SCRGB_LINEAR = 406913024, // STANDARD_BT709 | TRANSFER_LINEAR | RANGE_EXTENDED + ADATASPACE_SCRGB_LINEAR = 406913024, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_LINEAR | + // ADATASPACE_RANGE_EXTENDED /** * sRGB gamma encoding @@ -426,7 +427,8 @@ enum ADataSpace : int32_t { * * Uses full range, sRGB transfer BT.709 standard. */ - ADATASPACE_SRGB = 142671872, // STANDARD_BT709 | TRANSFER_SRGB | RANGE_FULL + ADATASPACE_SRGB = 142671872, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_SRGB | + // ADATASPACE_RANGE_FULL /** * scRGB @@ -441,14 +443,16 @@ enum ADataSpace : int32_t { * * Uses extended range, sRGB transfer and BT.709 standard. */ - ADATASPACE_SCRGB = 411107328, // STANDARD_BT709 | TRANSFER_SRGB | RANGE_EXTENDED + ADATASPACE_SCRGB = 411107328, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_SRGB | + // ADATASPACE_RANGE_EXTENDED /** * Display P3 * * Uses full range, sRGB transfer and D65 DCI-P3 standard. */ - ADATASPACE_DISPLAY_P3 = 143261696, // STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_FULL + ADATASPACE_DISPLAY_P3 = 143261696, // ADATASPACE_STANDARD_DCI_P3 | ADATASPACE_TRANSFER_SRGB | + // ADATASPACE_RANGE_FULL /** * ITU-R Recommendation 2020 (BT.2020) @@ -457,7 +461,8 @@ enum ADataSpace : int32_t { * * Uses full range, SMPTE 2084 (PQ) transfer and BT2020 standard. */ - ADATASPACE_BT2020_PQ = 163971072, // STANDARD_BT2020 | TRANSFER_ST2084 | RANGE_FULL + ADATASPACE_BT2020_PQ = 163971072, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_ST2084 | + // ADATASPACE_RANGE_FULL /** * ITU-R Recommendation 2020 (BT.2020) @@ -466,7 +471,8 @@ enum ADataSpace : int32_t { * * Uses limited range, SMPTE 2084 (PQ) transfer and BT2020 standard. */ - ADATASPACE_BT2020_ITU_PQ = 298188800, // STANDARD_BT2020 | TRANSFER_ST2084 | RANGE_LIMITED + ADATASPACE_BT2020_ITU_PQ = 298188800, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_ST2084 + // | ADATASPACE_RANGE_LIMITED /** * Adobe RGB @@ -476,7 +482,8 @@ enum ADataSpace : int32_t { * Note: Application is responsible for gamma encoding the data as * a 2.2 gamma encoding is not supported in HW. */ - ADATASPACE_ADOBE_RGB = 151715840, // STANDARD_ADOBE_RGB | TRANSFER_GAMMA2_2 | RANGE_FULL + ADATASPACE_ADOBE_RGB = 151715840, // ADATASPACE_STANDARD_ADOBE_RGB | + // ADATASPACE_TRANSFER_GAMMA2_2 | ADATASPACE_RANGE_FULL /** * JPEG File Interchange Format (JFIF) @@ -485,7 +492,8 @@ enum ADataSpace : int32_t { * * Uses full range, SMPTE 170M transfer and BT.601_625 standard. */ - ADATASPACE_JFIF = 146931712, // STANDARD_BT601_625 | TRANSFER_SMPTE_170M | RANGE_FULL + ADATASPACE_JFIF = 146931712, // ADATASPACE_STANDARD_BT601_625 | ADATASPACE_TRANSFER_SMPTE_170M | + // ADATASPACE_RANGE_FULL /** * ITU-R Recommendation 601 (BT.601) - 625-line @@ -494,7 +502,8 @@ enum ADataSpace : int32_t { * * Uses limited range, SMPTE 170M transfer and BT.601_625 standard. */ - ADATASPACE_BT601_625 = 281149440, // STANDARD_BT601_625 | TRANSFER_SMPTE_170M | RANGE_LIMITED + ADATASPACE_BT601_625 = 281149440, // ADATASPACE_STANDARD_BT601_625 | + // ADATASPACE_TRANSFER_SMPTE_170M | ADATASPACE_RANGE_LIMITED /** * ITU-R Recommendation 601 (BT.601) - 525-line @@ -503,7 +512,8 @@ enum ADataSpace : int32_t { * * Uses limited range, SMPTE 170M transfer and BT.601_525 standard. */ - ADATASPACE_BT601_525 = 281280512, // STANDARD_BT601_525 | TRANSFER_SMPTE_170M | RANGE_LIMITED + ADATASPACE_BT601_525 = 281280512, // ADATASPACE_STANDARD_BT601_525 | + // ADATASPACE_TRANSFER_SMPTE_170M | ADATASPACE_RANGE_LIMITED /** * ITU-R Recommendation 2020 (BT.2020) @@ -512,7 +522,8 @@ enum ADataSpace : int32_t { * * Uses full range, SMPTE 170M transfer and BT2020 standard. */ - ADATASPACE_BT2020 = 147193856, // STANDARD_BT2020 | TRANSFER_SMPTE_170M | RANGE_FULL + ADATASPACE_BT2020 = 147193856, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_SMPTE_170M | + // ADATASPACE_RANGE_FULL /** * ITU-R Recommendation 709 (BT.709) @@ -521,7 +532,8 @@ enum ADataSpace : int32_t { * * Uses limited range, SMPTE 170M transfer and BT.709 standard. */ - ADATASPACE_BT709 = 281083904, // STANDARD_BT709 | TRANSFER_SMPTE_170M | RANGE_LIMITED + ADATASPACE_BT709 = 281083904, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_SMPTE_170M | + // ADATASPACE_RANGE_LIMITED /** * SMPTE EG 432-1 and SMPTE RP 431-2 @@ -533,7 +545,8 @@ enum ADataSpace : int32_t { * Note: Application is responsible for gamma encoding the data as * a 2.6 gamma encoding is not supported in HW. */ - ADATASPACE_DCI_P3 = 155844608, // STANDARD_DCI_P3 | TRANSFER_GAMMA2_6 | RANGE_FULL + ADATASPACE_DCI_P3 = 155844608, // ADATASPACE_STANDARD_DCI_P3 | ADATASPACE_TRANSFER_GAMMA2_6 | + // ADATASPACE_RANGE_FULL /** * sRGB linear encoding @@ -547,21 +560,24 @@ enum ADataSpace : int32_t { * * Uses full range, linear transfer and BT.709 standard. */ - ADATASPACE_SRGB_LINEAR = 138477568, // STANDARD_BT709 | TRANSFER_LINEAR | RANGE_FULL + ADATASPACE_SRGB_LINEAR = 138477568, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_LINEAR | + // ADATASPACE_RANGE_FULL /** * Hybrid Log Gamma encoding * * Uses full range, hybrid log gamma transfer and BT2020 standard. */ - ADATASPACE_BT2020_HLG = 168165376, // STANDARD_BT2020 | TRANSFER_HLG | RANGE_FULL + ADATASPACE_BT2020_HLG = 168165376, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_HLG | + // ADATASPACE_RANGE_FULL /** * ITU Hybrid Log Gamma encoding * * Uses limited range, hybrid log gamma transfer and BT2020 standard. */ - ADATASPACE_BT2020_ITU_HLG = 302383104, // STANDARD_BT2020 | TRANSFER_HLG | RANGE_LIMITED + ADATASPACE_BT2020_ITU_HLG = 302383104, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_HLG | + // ADATASPACE_RANGE_LIMITED /** * Depth @@ -575,7 +591,37 @@ enum ADataSpace : int32_t { * * Embedded depth metadata following the dynamic depth specification. */ - ADATASPACE_DYNAMIC_DEPTH = 4098 + ADATASPACE_DYNAMIC_DEPTH = 4098, + +#ifndef ADATASPACE_SKIP_LEGACY_DEFINES + STANDARD_MASK = ADATASPACE_STANDARD_MASK, + STANDARD_UNSPECIFIED = ADATASPACE_STANDARD_UNSPECIFIED, + STANDARD_BT709 = ADATASPACE_STANDARD_BT709, + STANDARD_BT601_625 = ADATASPACE_STANDARD_BT601_625, + STANDARD_BT601_625_UNADJUSTED = ADATASPACE_STANDARD_BT601_625_UNADJUSTED, + STANDARD_BT601_525 = ADATASPACE_STANDARD_BT601_525, + STANDARD_BT601_525_UNADJUSTED = ADATASPACE_STANDARD_BT601_525_UNADJUSTED, + STANDARD_BT470M = ADATASPACE_STANDARD_BT470M, + STANDARD_BT2020 = ADATASPACE_STANDARD_BT2020, + STANDARD_FILM = ADATASPACE_STANDARD_FILM, + STANDARD_DCI_P3 = ADATASPACE_STANDARD_DCI_P3, + STANDARD_ADOBE_RGB = ADATASPACE_STANDARD_ADOBE_RGB, + TRANSFER_MASK = ADATASPACE_TRANSFER_MASK, + TRANSFER_UNSPECIFIED = ADATASPACE_TRANSFER_UNSPECIFIED, + TRANSFER_LINEAR = ADATASPACE_TRANSFER_LINEAR, + TRANSFER_SMPTE_170M = ADATASPACE_TRANSFER_SMPTE_170M, + TRANSFER_GAMMA2_2 = ADATASPACE_TRANSFER_GAMMA2_2, + TRANSFER_GAMMA2_6 = ADATASPACE_TRANSFER_GAMMA2_6, + TRANSFER_GAMMA2_8 = ADATASPACE_TRANSFER_GAMMA2_8, + TRANSFER_SRGB = ADATASPACE_TRANSFER_SRGB, + TRANSFER_ST2084 = ADATASPACE_TRANSFER_ST2084, + TRANSFER_HLG = ADATASPACE_TRANSFER_HLG, + RANGE_MASK = ADATASPACE_RANGE_MASK, + RANGE_UNSPECIFIED = ADATASPACE_RANGE_UNSPECIFIED, + RANGE_FULL = ADATASPACE_RANGE_FULL, + RANGE_LIMITED = ADATASPACE_RANGE_LIMITED, + RANGE_EXTENDED = ADATASPACE_RANGE_EXTENDED, +#endif }; __END_DECLS diff --git a/libs/nativewindow/include/android/hardware_buffer.h b/libs/nativewindow/include/android/hardware_buffer.h index 21798d0e29..e0e30c3283 100644 --- a/libs/nativewindow/include/android/hardware_buffer.h +++ b/libs/nativewindow/include/android/hardware_buffer.h @@ -46,6 +46,9 @@ #define ANDROID_HARDWARE_BUFFER_H #include <android/rect.h> +#define ADATASPACE_SKIP_LEGACY_DEFINES +#include <android/data_space.h> +#undef ADATASPACE_SKIP_LEGACY_DEFINES #include <inttypes.h> #include <sys/cdefs.h> diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h index a98ea86073..969a5cff05 100644 --- a/libs/nativewindow/include/system/window.h +++ b/libs/nativewindow/include/system/window.h @@ -1095,10 +1095,19 @@ enum { ANATIVEWINDOW_FRAME_RATE_CATEGORY_NORMAL = 3, /** + * Indicates that, as a result of a user interaction, an animation is likely to start. + * This category is a signal that a user interaction heuristic determined the need of a + * high refresh rate, and is not an explicit request from the app. + * As opposed to FRAME_RATE_CATEGORY_HIGH, this vote may be ignored in favor of + * more explicit votes. + */ + ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH_HINT = 4, + + /** * Indicates a frame rate suitable for animations that require a high frame rate, which may * increase smoothness but may also increase power usage. */ - ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH = 4 + ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH = 5 }; /* diff --git a/libs/nativewindow/include/vndk/hardware_buffer.h b/libs/nativewindow/include/vndk/hardware_buffer.h index 21931bb553..bcfae10201 100644 --- a/libs/nativewindow/include/vndk/hardware_buffer.h +++ b/libs/nativewindow/include/vndk/hardware_buffer.h @@ -21,6 +21,7 @@ #include <android/hardware_buffer.h> #include <cutils/native_handle.h> +#include <errno.h> __BEGIN_DECLS @@ -105,6 +106,76 @@ enum { AHARDWAREBUFFER_USAGE_CAMERA_MASK = 6UL << 16, }; +/** + * Additional options for AHardwareBuffer_allocate2. These correspond to + * android.hardware.graphics.common.ExtendableType + */ +typedef struct { + const char* _Nonnull name; + int64_t value; +} AHardwareBufferLongOptions; + +enum AHardwareBufferStatus : int32_t { + /* Success, no error */ + AHARDWAREBUFFER_STATUS_OK = 0, + /* There's insufficient memory to satisfy the request */ + AHARDWAREBUFFER_STATUS_NO_MEMORY = -ENOMEM, + /* The given argument is invalid */ + AHARDWAREBUFFER_STATUS_BAD_VALUE = -EINVAL, + /* The requested operation is not supported by the device */ + AHARDWAREBUFFER_STATUS_UNSUPPORTED = -ENOSYS, + /* An unknown error occurred */ + AHARDWAREBUFFER_STATUS_UNKNOWN_ERROR = (-2147483647 - 1), +}; + +/** + * Allocates a buffer that matches the passed AHardwareBuffer_Desc with additional options + * + * If allocation succeeds, the buffer can be used according to the + * usage flags specified in its description. If a buffer is used in ways + * not compatible with its usage flags, the results are undefined and + * may include program termination. + * + * @param desc The AHardwareBuffer_Desc that describes the allocation to request. Note that `stride` + * is ignored. + * @param additionalOptions A pointer to an array of AHardwareBufferLongOptions with additional + * string key + long value options that may be specified. May be null if + * `additionalOptionsSize` is 0 + * @param additionalOptionsSize The number of additional options to pass + * @param outBuffer The resulting buffer allocation + * @return AHARDWAREBUFFER_STATUS_OK on success + * AHARDWAREBUFFER_STATUS_NO_MEMORY if there's insufficient resources for the allocation + * AHARDWAREBUFFER_STATUS_BAD_VALUE if the provided description & options are not supported + * by the device + * AHARDWAREBUFFER_STATUS_UNKNOWN_ERROR for any other error + * any reason. The returned buffer has a reference count of 1. + */ +enum AHardwareBufferStatus AHardwareBuffer_allocateWithOptions( + const AHardwareBuffer_Desc* _Nonnull desc, + const AHardwareBufferLongOptions* _Nullable additionalOptions, size_t additionalOptionsSize, + AHardwareBuffer* _Nullable* _Nonnull outBuffer) __INTRODUCED_IN(__ANDROID_API_V__); + +/** + * Queries the dataspace of the given AHardwareBuffer. + * + * @param buffer The non-null buffer for which to query the Dataspace + * @return The dataspace of the buffer, or ADATASPACE_UNKNOWN if one hasn't been set + */ +enum ADataSpace AHardwareBuffer_getDataSpace(const AHardwareBuffer* _Nonnull buffer) + __INTRODUCED_IN(__ANDROID_API_V__); + +/** + * Sets the dataspace of the given AHardwareBuffer + * @param buffer The non-null buffer for which to set the dataspace + * @param dataSpace The dataspace to set + * @return AHARDWAREBUFFER_STATUS_OK on success, + * AHARDWAREBUFFER_STATUS_UNSUPPORTED if the device doesn't support setting the dataspace, + * AHARDWAREBUFFER_STATUS_UNKNOWN_ERROR for any other failure. + */ +enum AHardwareBufferStatus AHardwareBuffer_setDataSpace(AHardwareBuffer* _Nonnull buffer, + enum ADataSpace dataSpace) + __INTRODUCED_IN(__ANDROID_API_V__); + __END_DECLS #endif /* ANDROID_VNDK_NATIVEWINDOW_AHARDWAREBUFFER_H */ diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt index 95fc920da1..e29d5a6bb4 100644 --- a/libs/nativewindow/libnativewindow.map.txt +++ b/libs/nativewindow/libnativewindow.map.txt @@ -2,6 +2,7 @@ LIBNATIVEWINDOW { global: AHardwareBuffer_acquire; AHardwareBuffer_allocate; + AHardwareBuffer_allocateWithOptions; # llndk systemapi AHardwareBuffer_createFromHandle; # llndk systemapi AHardwareBuffer_describe; AHardwareBuffer_getId; # introduced=31 @@ -16,6 +17,8 @@ LIBNATIVEWINDOW { AHardwareBuffer_unlock; AHardwareBuffer_readFromParcel; # introduced=34 AHardwareBuffer_writeToParcel; # introduced=34 + AHardwareBuffer_getDataSpace; # llndk systemapi + AHardwareBuffer_setDataSpace; # llndk systemapi ANativeWindowBuffer_getHardwareBuffer; # llndk ANativeWindow_OemStorageGet; # llndk ANativeWindow_OemStorageSet; # llndk diff --git a/libs/nativewindow/tests/AHardwareBufferTest.cpp b/libs/nativewindow/tests/AHardwareBufferTest.cpp index ef863b6d67..136395aac0 100644 --- a/libs/nativewindow/tests/AHardwareBufferTest.cpp +++ b/libs/nativewindow/tests/AHardwareBufferTest.cpp @@ -17,6 +17,8 @@ #define LOG_TAG "AHardwareBuffer_test" //#define LOG_NDEBUG 0 +#include <android-base/properties.h> +#include <android/data_space.h> #include <android/hardware/graphics/common/1.0/types.h> #include <gtest/gtest.h> #include <private/android/AHardwareBufferHelpers.h> @@ -26,6 +28,10 @@ using namespace android; using android::hardware::graphics::common::V1_0::BufferUsage; +static bool IsCuttlefish() { + return ::android::base::GetProperty("ro.product.board", "") == "cutf"; +} + static ::testing::AssertionResult BuildHexFailureMessage(uint64_t expected, uint64_t actual, const char* type) { std::ostringstream ss; @@ -170,3 +176,83 @@ TEST(AHardwareBufferTest, GetIdTest) { EXPECT_NE(id1, id2); } + +TEST(AHardwareBufferTest, Allocate2NoExtras) { + AHardwareBuffer_Desc desc{ + .width = 64, + .height = 1, + .layers = 1, + .format = AHARDWAREBUFFER_FORMAT_BLOB, + .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, + .stride = 0, + }; + + AHardwareBuffer* buffer = nullptr; + ASSERT_EQ(0, AHardwareBuffer_allocateWithOptions(&desc, nullptr, 0, &buffer)); + uint64_t id = 0; + EXPECT_EQ(0, AHardwareBuffer_getId(buffer, &id)); + EXPECT_NE(0, id); + AHardwareBuffer_Desc desc2{}; + AHardwareBuffer_describe(buffer, &desc2); + EXPECT_EQ(desc.width, desc2.width); + EXPECT_EQ(desc.height, desc2.height); + EXPECT_GE(desc2.stride, desc2.width); + + AHardwareBuffer_release(buffer); +} + +TEST(AHardwareBufferTest, Allocate2WithExtras) { + if (!IsCuttlefish()) { + GTEST_SKIP() << "Unknown gralloc HAL, cannot test extras"; + } + + AHardwareBuffer_Desc desc{ + .width = 64, + .height = 48, + .layers = 1, + .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, + .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, + .stride = 0, + }; + + AHardwareBuffer* buffer = nullptr; + std::array<AHardwareBufferLongOptions, 1> extras = {{ + {.name = "android.hardware.graphics.common.Dataspace", ADATASPACE_DISPLAY_P3}, + }}; + ASSERT_EQ(0, AHardwareBuffer_allocateWithOptions(&desc, extras.data(), extras.size(), &buffer)); + uint64_t id = 0; + EXPECT_EQ(0, AHardwareBuffer_getId(buffer, &id)); + EXPECT_NE(0, id); + AHardwareBuffer_Desc desc2{}; + AHardwareBuffer_describe(buffer, &desc2); + EXPECT_EQ(desc.width, desc2.width); + EXPECT_EQ(desc.height, desc2.height); + EXPECT_GE(desc2.stride, desc2.width); + + EXPECT_EQ(ADATASPACE_DISPLAY_P3, AHardwareBuffer_getDataSpace(buffer)); + + AHardwareBuffer_release(buffer); +} + +TEST(AHardwareBufferTest, GetSetDataspace) { + AHardwareBuffer_Desc desc{ + .width = 64, + .height = 48, + .layers = 1, + .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, + .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, + .stride = 0, + }; + + AHardwareBuffer* buffer = nullptr; + ASSERT_EQ(0, AHardwareBuffer_allocate(&desc, &buffer)); + + EXPECT_EQ(ADATASPACE_UNKNOWN, AHardwareBuffer_getDataSpace(buffer)); + AHardwareBufferStatus status = AHardwareBuffer_setDataSpace(buffer, ADATASPACE_DISPLAY_P3); + if (status != AHARDWAREBUFFER_STATUS_UNSUPPORTED) { + EXPECT_EQ(0, status); + EXPECT_EQ(ADATASPACE_DISPLAY_P3, AHardwareBuffer_getDataSpace(buffer)); + } + + AHardwareBuffer_release(buffer); +}
\ No newline at end of file diff --git a/libs/nativewindow/tests/Android.bp b/libs/nativewindow/tests/Android.bp index 30737c1bf6..d7c7eb3153 100644 --- a/libs/nativewindow/tests/Android.bp +++ b/libs/nativewindow/tests/Android.bp @@ -31,6 +31,7 @@ cc_test { "device-tests", ], shared_libs: [ + "libbase", "libgui", "liblog", "libnativewindow", @@ -44,5 +45,8 @@ cc_test { "ANativeWindowTest.cpp", "c_compatibility.c", ], - cflags: ["-Wall", "-Werror"], + cflags: [ + "-Wall", + "-Werror", + ], } diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp index ba2eb7d224..fd45840cf8 100644 --- a/libs/renderengine/Android.bp +++ b/libs/renderengine/Android.bp @@ -48,11 +48,19 @@ cc_defaults { static_libs: [ "libshaders", "libtonemap", + "libsurfaceflinger_common", ], local_include_dirs: ["include"], export_include_dirs: ["include"], } +// Needed by FlagManager to access a #define. +cc_library_static { + name: "librenderengine_includes", + local_include_dirs: ["include"], + export_include_dirs: ["include"], +} + filegroup { name: "librenderengine_sources", srcs: [ diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp index 3e1ac33d57..233134d2db 100644 --- a/libs/renderengine/RenderEngine.cpp +++ b/libs/renderengine/RenderEngine.cpp @@ -28,28 +28,28 @@ namespace android { namespace renderengine { std::unique_ptr<RenderEngine> RenderEngine::create(const RenderEngineCreationArgs& args) { - switch (args.renderEngineType) { - case RenderEngineType::SKIA_GL: + if (args.threaded == Threaded::YES) { + switch (args.graphicsApi) { + case GraphicsApi::GL: + ALOGD("Threaded RenderEngine with SkiaGL Backend"); + return renderengine::threaded::RenderEngineThreaded::create([args]() { + return android::renderengine::skia::SkiaGLRenderEngine::create(args); + }); + case GraphicsApi::VK: + ALOGD("Threaded RenderEngine with SkiaVK Backend"); + return renderengine::threaded::RenderEngineThreaded::create([args]() { + return android::renderengine::skia::SkiaVkRenderEngine::create(args); + }); + } + } + + switch (args.graphicsApi) { + case GraphicsApi::GL: ALOGD("RenderEngine with SkiaGL Backend"); return renderengine::skia::SkiaGLRenderEngine::create(args); - case RenderEngineType::SKIA_VK: + case GraphicsApi::VK: ALOGD("RenderEngine with SkiaVK Backend"); return renderengine::skia::SkiaVkRenderEngine::create(args); - case RenderEngineType::SKIA_GL_THREADED: { - ALOGD("Threaded RenderEngine with SkiaGL Backend"); - return renderengine::threaded::RenderEngineThreaded::create( - [args]() { - return android::renderengine::skia::SkiaGLRenderEngine::create(args); - }, - args.renderEngineType); - } - case RenderEngineType::SKIA_VK_THREADED: - ALOGD("Threaded RenderEngine with SkiaVK Backend"); - return renderengine::threaded::RenderEngineThreaded::create( - [args]() { - return android::renderengine::skia::SkiaVkRenderEngine::create(args); - }, - args.renderEngineType); } } diff --git a/libs/renderengine/benchmark/Android.bp b/libs/renderengine/benchmark/Android.bp index 55c34cd059..87e21c2d70 100644 --- a/libs/renderengine/benchmark/Android.bp +++ b/libs/renderengine/benchmark/Android.bp @@ -37,6 +37,7 @@ cc_benchmark { static_libs: [ "librenderengine", "libshaders", + "libsurfaceflinger_common", "libtonemap", ], cflags: [ @@ -54,6 +55,7 @@ cc_benchmark { "libsync", "libui", "libutils", + "server_configurable_flags", ], data: ["resources/*"], diff --git a/libs/renderengine/benchmark/RenderEngineBench.cpp b/libs/renderengine/benchmark/RenderEngineBench.cpp index a7f1df9a9b..101f519e55 100644 --- a/libs/renderengine/benchmark/RenderEngineBench.cpp +++ b/libs/renderengine/benchmark/RenderEngineBench.cpp @@ -30,46 +30,6 @@ using namespace android; using namespace android::renderengine; /////////////////////////////////////////////////////////////////////////////// -// Helpers for Benchmark::Apply -/////////////////////////////////////////////////////////////////////////////// - -std::string RenderEngineTypeName(RenderEngine::RenderEngineType type) { - switch (type) { - case RenderEngine::RenderEngineType::SKIA_GL_THREADED: - return "skiaglthreaded"; - case RenderEngine::RenderEngineType::SKIA_GL: - return "skiagl"; - case RenderEngine::RenderEngineType::SKIA_VK: - return "skiavk"; - case RenderEngine::RenderEngineType::SKIA_VK_THREADED: - return "skiavkthreaded"; - } -} - -/** - * Passed (indirectly - see RunSkiaGLThreaded) to Benchmark::Apply to create a - * Benchmark which specifies which RenderEngineType it uses. - * - * This simplifies calling ->Arg(type)->Arg(type) and provides strings to make - * it obvious which version is being run. - * - * @param b The benchmark family - * @param type The type of RenderEngine to use. - */ -static void AddRenderEngineType(benchmark::internal::Benchmark* b, - RenderEngine::RenderEngineType type) { - b->Arg(static_cast<int64_t>(type)); - b->ArgName(RenderEngineTypeName(type)); -} - -/** - * Run a benchmark once using SKIA_GL_THREADED. - */ -static void RunSkiaGLThreaded(benchmark::internal::Benchmark* b) { - AddRenderEngineType(b, RenderEngine::RenderEngineType::SKIA_GL_THREADED); -} - -/////////////////////////////////////////////////////////////////////////////// // Helpers for calling drawLayers /////////////////////////////////////////////////////////////////////////////// @@ -104,7 +64,8 @@ std::pair<uint32_t, uint32_t> getDisplaySize() { return std::pair<uint32_t, uint32_t>(width, height); } -static std::unique_ptr<RenderEngine> createRenderEngine(RenderEngine::RenderEngineType type) { +static std::unique_ptr<RenderEngine> createRenderEngine(RenderEngine::Threaded threaded, + RenderEngine::GraphicsApi graphicsApi) { auto args = RenderEngineCreationArgs::Builder() .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888)) .setImageCacheSize(1) @@ -112,7 +73,8 @@ static std::unique_ptr<RenderEngine> createRenderEngine(RenderEngine::RenderEngi .setPrecacheToneMapperShaderOnly(false) .setSupportsBackgroundBlur(true) .setContextPriority(RenderEngine::ContextPriority::REALTIME) - .setRenderEngineType(type) + .setThreaded(threaded) + .setGraphicsApi(graphicsApi) .build(); return RenderEngine::create(args); } @@ -214,8 +176,11 @@ static void benchDrawLayers(RenderEngine& re, const std::vector<LayerSettings>& // Benchmarks /////////////////////////////////////////////////////////////////////////////// -void BM_blur(benchmark::State& benchState) { - auto re = createRenderEngine(static_cast<RenderEngine::RenderEngineType>(benchState.range())); +template <class... Args> +void BM_blur(benchmark::State& benchState, Args&&... args) { + auto args_tuple = std::make_tuple(std::move(args)...); + auto re = createRenderEngine(static_cast<RenderEngine::Threaded>(std::get<0>(args_tuple)), + static_cast<RenderEngine::GraphicsApi>(std::get<1>(args_tuple))); // Initially use cpu access so we can decode into it with AImageDecoder. auto [width, height] = getDisplaySize(); @@ -259,4 +224,5 @@ void BM_blur(benchmark::State& benchState) { benchDrawLayers(*re, layers, benchState, "blurred"); } -BENCHMARK(BM_blur)->Apply(RunSkiaGLThreaded); +BENCHMARK_CAPTURE(BM_blur, SkiaGLThreaded, RenderEngine::Threaded::YES, + RenderEngine::GraphicsApi::GL); diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h index 818d0350c0..7047358e62 100644 --- a/libs/renderengine/include/renderengine/RenderEngine.h +++ b/libs/renderengine/include/renderengine/RenderEngine.h @@ -33,7 +33,7 @@ #include <memory> /** - * Allows to set RenderEngine backend to GLES (default) or SkiaGL (NOT yet supported). + * Allows to override the RenderEngine backend. */ #define PROPERTY_DEBUG_RENDERENGINE_BACKEND "debug.renderengine.backend" @@ -92,11 +92,14 @@ public: REALTIME = 4, }; - enum class RenderEngineType { - SKIA_GL = 3, - SKIA_GL_THREADED = 4, - SKIA_VK = 5, - SKIA_VK_THREADED = 6, + enum class Threaded { + NO, + YES, + }; + + enum class GraphicsApi { + GL, + VK, }; static std::unique_ptr<RenderEngine> create(const RenderEngineCreationArgs& args); @@ -176,10 +179,9 @@ public: // query is required to be thread safe. virtual bool supportsBackgroundBlur() = 0; - // Returns the current type of RenderEngine instance that was created. // TODO(b/180767535): This is only implemented to allow for backend-specific behavior, which // we should not allow in general, so remove this. - RenderEngineType getRenderEngineType() const { return mRenderEngineType; } + bool isThreaded() const { return mThreaded == Threaded::YES; } static void validateInputBufferUsage(const sp<GraphicBuffer>&); static void validateOutputBufferUsage(const sp<GraphicBuffer>&); @@ -191,9 +193,9 @@ public: virtual void setEnableTracing(bool /*tracingEnabled*/) {} protected: - RenderEngine() : RenderEngine(RenderEngineType::SKIA_GL) {} + RenderEngine() : RenderEngine(Threaded::NO) {} - RenderEngine(RenderEngineType type) : mRenderEngineType(type) {} + RenderEngine(Threaded threaded) : mThreaded(threaded) {} // Maps GPU resources for this buffer. // Note that work may be deferred to an additional thread, i.e. this call @@ -228,7 +230,7 @@ protected: friend class impl::ExternalTexture; friend class threaded::RenderEngineThreaded; friend class RenderEngineTest_cleanupPostRender_cleansUpOnce_Test; - const RenderEngineType mRenderEngineType; + const Threaded mThreaded; // Update protectedContext mode depending on whether or not any layer has a protected buffer. void updateProtectedContext(const std::vector<LayerSettings>&, @@ -251,7 +253,8 @@ struct RenderEngineCreationArgs { bool precacheToneMapperShaderOnly; bool supportsBackgroundBlur; RenderEngine::ContextPriority contextPriority; - RenderEngine::RenderEngineType renderEngineType; + RenderEngine::Threaded threaded; + RenderEngine::GraphicsApi graphicsApi; struct Builder; @@ -261,14 +264,16 @@ private: bool _enableProtectedContext, bool _precacheToneMapperShaderOnly, bool _supportsBackgroundBlur, RenderEngine::ContextPriority _contextPriority, - RenderEngine::RenderEngineType _renderEngineType) + RenderEngine::Threaded _threaded, + RenderEngine::GraphicsApi _graphicsApi) : pixelFormat(_pixelFormat), imageCacheSize(_imageCacheSize), enableProtectedContext(_enableProtectedContext), precacheToneMapperShaderOnly(_precacheToneMapperShaderOnly), supportsBackgroundBlur(_supportsBackgroundBlur), contextPriority(_contextPriority), - renderEngineType(_renderEngineType) {} + threaded(_threaded), + graphicsApi(_graphicsApi) {} RenderEngineCreationArgs() = delete; }; @@ -299,14 +304,18 @@ struct RenderEngineCreationArgs::Builder { this->contextPriority = contextPriority; return *this; } - Builder& setRenderEngineType(RenderEngine::RenderEngineType renderEngineType) { - this->renderEngineType = renderEngineType; + Builder& setThreaded(RenderEngine::Threaded threaded) { + this->threaded = threaded; + return *this; + } + Builder& setGraphicsApi(RenderEngine::GraphicsApi graphicsApi) { + this->graphicsApi = graphicsApi; return *this; } RenderEngineCreationArgs build() const { return RenderEngineCreationArgs(pixelFormat, imageCacheSize, enableProtectedContext, precacheToneMapperShaderOnly, supportsBackgroundBlur, - contextPriority, renderEngineType); + contextPriority, threaded, graphicsApi); } private: @@ -317,8 +326,8 @@ private: bool precacheToneMapperShaderOnly = false; bool supportsBackgroundBlur = false; RenderEngine::ContextPriority contextPriority = RenderEngine::ContextPriority::MEDIUM; - RenderEngine::RenderEngineType renderEngineType = - RenderEngine::RenderEngineType::SKIA_GL_THREADED; + RenderEngine::Threaded threaded = RenderEngine::Threaded::YES; + RenderEngine::GraphicsApi graphicsApi = RenderEngine::GraphicsApi::GL; }; } // namespace renderengine diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp index 92fe4c0b47..ee95e59d90 100644 --- a/libs/renderengine/skia/AutoBackendTexture.cpp +++ b/libs/renderengine/skia/AutoBackendTexture.cpp @@ -77,7 +77,7 @@ AutoBackendTexture::AutoBackendTexture(GrDirectContext* context, AHardwareBuffer backendFormat, isOutputBuffer); } else { - LOG_ALWAYS_FATAL("Unexpected backend %d", backend); + LOG_ALWAYS_FATAL("Unexpected backend %u", static_cast<unsigned>(backend)); } mColorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format); @@ -145,8 +145,8 @@ void logFatalTexture(const char* msg, const GrBackendTexture& tex, ui::Dataspace "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i " "texType: %i\n\t\tVkImageInfo: success: %i fFormat: %i " "fSampleCount: %u fLevelCount: %u colorType %i", - msg, tex.isValid(), dataspace, tex.width(), tex.height(), - tex.hasMipmaps(), tex.isProtected(), + msg, tex.isValid(), static_cast<int32_t>(dataspace), tex.width(), + tex.height(), tex.hasMipmaps(), tex.isProtected(), static_cast<int>(tex.textureType()), retrievedImageInfo, imageInfo.fFormat, imageInfo.fSampleCount, imageInfo.fLevelCount, colorType); diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index 2053c6a34f..fea4129ec0 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -236,7 +236,13 @@ EGLConfig SkiaGLRenderEngine::chooseEglConfig(EGLDisplay display, int format, bo err = selectEGLConfig(display, format, 0, &config); if (err != NO_ERROR) { // this EGL is too lame for android - LOG_ALWAYS_FATAL("no suitable EGLConfig found, giving up"); + LOG_ALWAYS_FATAL("no suitable EGLConfig found, giving up" + " (format: %d, vendor: %s, version: %s, extensions: %s, Client" + " API: %s)", + format, eglQueryString(display, EGL_VENDOR), + eglQueryString(display, EGL_VERSION), + eglQueryString(display, EGL_EXTENSIONS), + eglQueryString(display, EGL_CLIENT_APIS) ?: "Not Supported"); } } } @@ -262,7 +268,7 @@ EGLConfig SkiaGLRenderEngine::chooseEglConfig(EGLDisplay display, int format, bo SkiaGLRenderEngine::SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display, EGLContext ctxt, EGLSurface placeholder, EGLContext protectedContext, EGLSurface protectedPlaceholder) - : SkiaRenderEngine(args.renderEngineType, static_cast<PixelFormat>(args.pixelFormat), + : SkiaRenderEngine(args.threaded, static_cast<PixelFormat>(args.pixelFormat), args.supportsBackgroundBlur), mEGLDisplay(display), mEGLContext(ctxt), diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index 88326e77d7..6e393f03fa 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -53,6 +53,7 @@ #include <SkSurface.h> #include <SkTileMode.h> #include <android-base/stringprintf.h> +#include <common/FlagManager.h> #include <gui/FenceMonitor.h> #include <gui/TraceUtils.h> #include <include/gpu/ganesh/SkSurfaceGanesh.h> @@ -269,9 +270,9 @@ void SkiaRenderEngine::setEnableTracing(bool tracingEnabled) { SkAndroidFrameworkTraceUtil::setEnableTracing(tracingEnabled); } -SkiaRenderEngine::SkiaRenderEngine(RenderEngineType type, PixelFormat pixelFormat, +SkiaRenderEngine::SkiaRenderEngine(Threaded threaded, PixelFormat pixelFormat, bool supportsBackgroundBlur) - : RenderEngine(type), mDefaultPixelFormat(pixelFormat) { + : RenderEngine(threaded), mDefaultPixelFormat(pixelFormat) { if (supportsBackgroundBlur) { ALOGD("Background Blurs Enabled"); mBlurFilter = new KawaseBlurFilter(); @@ -389,10 +390,9 @@ void SkiaRenderEngine::ensureGrContextsCreated() { void SkiaRenderEngine::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, bool isRenderable) { // Only run this if RE is running on its own thread. This - // way the access to GL operations is guaranteed to be happening on the + // way the access to GL/VK operations is guaranteed to be happening on the // same thread. - if (mRenderEngineType != RenderEngineType::SKIA_GL_THREADED && - mRenderEngineType != RenderEngineType::SKIA_VK_THREADED) { + if (!isThreaded()) { return; } // We don't attempt to map a buffer if the buffer contains protected content. In GL this is @@ -420,6 +420,9 @@ void SkiaRenderEngine::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, mGraphicBufferExternalRefs[buffer->getId()]++; if (const auto& iter = cache.find(buffer->getId()); iter == cache.end()) { + if (FlagManager::getInstance().renderable_buffer_usage()) { + isRenderable = buffer->getUsage() & GRALLOC_USAGE_HW_RENDER; + } std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef = std::make_shared<AutoBackendTexture::LocalRef>(grContext, buffer->toAHardwareBuffer(), @@ -761,10 +764,11 @@ void SkiaRenderEngine::drawLayersInternal( // save a snapshot of the activeSurface to use as input to the blur shaders blurInput = activeSurface->makeImageSnapshot(); - // blit the offscreen framebuffer into the destination AHB, but only - // if there are blur regions. backgroundBlurRadius blurs the entire - // image below, so it can skip this step. - if (layer.blurRegions.size()) { + // blit the offscreen framebuffer into the destination AHB. This ensures that + // even if the blurred image does not cover the screen (for example, during + // a rotation animation, or if blur regions are used), the entire screen is + // initialized. + if (layer.blurRegions.size() || FlagManager::getInstance().restore_blur_step()) { SkPaint paint; paint.setBlendMode(SkBlendMode::kSrc); if (CC_UNLIKELY(mCapture->isCaptureRunning())) { diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h index ac134afa64..e88d44cca6 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.h +++ b/libs/renderengine/skia/SkiaRenderEngine.h @@ -59,7 +59,7 @@ class BlurFilter; class SkiaRenderEngine : public RenderEngine { public: static std::unique_ptr<SkiaRenderEngine> create(const RenderEngineCreationArgs& args); - SkiaRenderEngine(RenderEngineType type, PixelFormat pixelFormat, bool supportsBackgroundBlur); + SkiaRenderEngine(Threaded, PixelFormat pixelFormat, bool supportsBackgroundBlur); ~SkiaRenderEngine() override; std::future<void> primeCache(bool shouldPrimeUltraHDR) override final; diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp index ba20d1f223..bff12ce7ff 100644 --- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp @@ -34,6 +34,8 @@ #include <cstdint> #include <memory> +#include <sstream> +#include <string> #include <vector> #include <vulkan/vulkan.h> @@ -67,6 +69,13 @@ struct DestroySemaphoreInfo { DestroySemaphoreInfo(VkSemaphore semaphore) : mSemaphore(semaphore) {} }; +namespace { +void onVkDeviceFault(void* callbackContext, const std::string& description, + const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos, + const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos, + const std::vector<std::byte>& vendorBinaryData); +} // anonymous namespace + struct VulkanInterface { bool initialized = false; VkInstance instance; @@ -79,6 +88,7 @@ struct VulkanInterface { VkPhysicalDeviceFeatures2* physicalDeviceFeatures2 = nullptr; VkPhysicalDeviceSamplerYcbcrConversionFeatures* samplerYcbcrConversionFeatures = nullptr; VkPhysicalDeviceProtectedMemoryFeatures* protectedMemoryFeatures = nullptr; + VkPhysicalDeviceFaultFeaturesEXT* deviceFaultFeatures = nullptr; GrVkGetProc grGetProc; bool isProtected; bool isRealtimePriority; @@ -100,6 +110,8 @@ struct VulkanInterface { backendContext.fDeviceFeatures2 = physicalDeviceFeatures2; backendContext.fGetProc = grGetProc; backendContext.fProtectedContext = isProtected ? GrProtected::kYes : GrProtected::kNo; + backendContext.fDeviceLostContext = this; // VulkanInterface is long-lived + backendContext.fDeviceLostProc = onVkDeviceFault; return backendContext; }; @@ -178,6 +190,68 @@ struct VulkanInterface { } }; +namespace { +void onVkDeviceFault(void* callbackContext, const std::string& description, + const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos, + const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos, + const std::vector<std::byte>& vendorBinaryData) { + VulkanInterface* interface = static_cast<VulkanInterface*>(callbackContext); + const std::string protectedStr = interface->isProtected ? "protected" : "non-protected"; + // The final crash string should contain as much differentiating info as possible, up to 1024 + // bytes. As this final message is constructed, the same information is also dumped to the logs + // but in a more verbose format. Building the crash string is unsightly, so the clearer logging + // statement is always placed first to give context. + ALOGE("VK_ERROR_DEVICE_LOST (%s context): %s", protectedStr.c_str(), description.c_str()); + std::stringstream crashMsg; + crashMsg << "VK_ERROR_DEVICE_LOST (" << protectedStr; + + if (!addressInfos.empty()) { + ALOGE("%zu VkDeviceFaultAddressInfoEXT:", addressInfos.size()); + crashMsg << ", " << addressInfos.size() << " address info ("; + for (VkDeviceFaultAddressInfoEXT addressInfo : addressInfos) { + ALOGE(" addressType: %d", (int)addressInfo.addressType); + ALOGE(" reportedAddress: %" PRIu64, addressInfo.reportedAddress); + ALOGE(" addressPrecision: %" PRIu64, addressInfo.addressPrecision); + crashMsg << addressInfo.addressType << ":" + << addressInfo.reportedAddress << ":" + << addressInfo.addressPrecision << ", "; + } + crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", " + crashMsg << ")"; + } + + if (!vendorInfos.empty()) { + ALOGE("%zu VkDeviceFaultVendorInfoEXT:", vendorInfos.size()); + crashMsg << ", " << vendorInfos.size() << " vendor info ("; + for (VkDeviceFaultVendorInfoEXT vendorInfo : vendorInfos) { + ALOGE(" description: %s", vendorInfo.description); + ALOGE(" vendorFaultCode: %" PRIu64, vendorInfo.vendorFaultCode); + ALOGE(" vendorFaultData: %" PRIu64, vendorInfo.vendorFaultData); + // Omit descriptions for individual vendor info structs in the crash string, as the + // fault code and fault data fields should be enough for clustering, and the verbosity + // isn't worth it. Additionally, vendors may just set the general description field of + // the overall fault to the description of the first element in this list, and that + // overall description will be placed at the end of the crash string. + crashMsg << vendorInfo.vendorFaultCode << ":" + << vendorInfo.vendorFaultData << ", "; + } + crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", " + crashMsg << ")"; + } + + if (!vendorBinaryData.empty()) { + // TODO: b/322830575 - Log in base64, or dump directly to a file that gets put in bugreports + ALOGE("%zu bytes of vendor-specific binary data (please notify Android's Core Graphics" + " Stack team if you observe this message).", + vendorBinaryData.size()); + crashMsg << ", " << vendorBinaryData.size() << " bytes binary"; + } + + crashMsg << "): " << description; + LOG_ALWAYS_FATAL("%s", crashMsg.str().c_str()); +}; +} // anonymous namespace + static GrVkGetProc sGetProc = [](const char* proc_name, VkInstance instance, VkDevice device) { if (device != VK_NULL_HANDLE) { return vkGetDeviceProcAddr(device, proc_name); @@ -429,6 +503,14 @@ VulkanInterface initVulkanInterface(bool protectedContent = false) { tailPnext = &interface.protectedMemoryFeatures->pNext; } + if (interface.grExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) { + interface.deviceFaultFeatures = new VkPhysicalDeviceFaultFeaturesEXT; + interface.deviceFaultFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT; + interface.deviceFaultFeatures->pNext = nullptr; + *tailPnext = interface.deviceFaultFeatures; + tailPnext = &interface.deviceFaultFeatures->pNext; + } + vkGetPhysicalDeviceFeatures2(physicalDevice, interface.physicalDeviceFeatures2); // Looks like this would slow things down and we can't depend on it on all platforms interface.physicalDeviceFeatures2->features.robustBufferAccess = VK_FALSE; @@ -545,6 +627,10 @@ void teardownVulkanInterface(VulkanInterface* interface) { delete interface->physicalDeviceFeatures2; } + if (interface->deviceFaultFeatures) { + delete interface->deviceFaultFeatures; + } + interface->samplerYcbcrConversionFeatures = nullptr; interface->physicalDeviceFeatures2 = nullptr; interface->protectedMemoryFeatures = nullptr; @@ -596,7 +682,7 @@ std::unique_ptr<SkiaVkRenderEngine> SkiaVkRenderEngine::create( } SkiaVkRenderEngine::SkiaVkRenderEngine(const RenderEngineCreationArgs& args) - : SkiaRenderEngine(args.renderEngineType, static_cast<PixelFormat>(args.pixelFormat), + : SkiaRenderEngine(args.threaded, static_cast<PixelFormat>(args.pixelFormat), args.supportsBackgroundBlur) {} SkiaVkRenderEngine::~SkiaVkRenderEngine() { diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp index 5c9820cdc5..09f09a697a 100644 --- a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp +++ b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp @@ -111,7 +111,7 @@ sk_sp<SkImage> KawaseBlurFilter::generate(GrRecordingContext* context, constexpr int kSampleCount = 1; constexpr bool kMipmapped = false; constexpr SkSurfaceProps* kProps = nullptr; - sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(context, skgpu::Budgeted::kYes, scaledInfo, + sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(context, skgpu::Budgeted::kNo, scaledInfo, kSampleCount, kTopLeft_GrSurfaceOrigin, kProps, kMipmapped, input->isProtected()); LOG_ALWAYS_FATAL_IF(!surface, "%s: Failed to create surface for blurring!", __func__); diff --git a/libs/renderengine/tests/Android.bp b/libs/renderengine/tests/Android.bp index 50e166d2a7..473e1d46b8 100644 --- a/libs/renderengine/tests/Android.bp +++ b/libs/renderengine/tests/Android.bp @@ -44,6 +44,7 @@ cc_test { "librenderengine_mocks", "libshaders", "libtonemap", + "libsurfaceflinger_common", ], header_libs: [ "libtonemap_headers", @@ -61,5 +62,6 @@ cc_test { "libsync", "libui", "libutils", + "server_configurable_flags", ], } diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index 11d4fdebdc..4c18704f52 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -106,25 +106,9 @@ public: virtual ~RenderEngineFactory() = default; virtual std::string name() = 0; - virtual renderengine::RenderEngine::RenderEngineType type() = 0; - virtual std::unique_ptr<renderengine::RenderEngine> createRenderEngine() = 0; - virtual bool typeSupported() = 0; -}; - -class SkiaVkRenderEngineFactory : public RenderEngineFactory { -public: - std::string name() override { return "SkiaVkRenderEngineFactory"; } - - renderengine::RenderEngine::RenderEngineType type() { - return renderengine::RenderEngine::RenderEngineType::SKIA_VK; - } - - std::unique_ptr<renderengine::RenderEngine> createRenderEngine() override { - std::unique_ptr<renderengine::RenderEngine> re = createSkiaVkRenderEngine(); - return re; - } - - std::unique_ptr<renderengine::skia::SkiaVkRenderEngine> createSkiaVkRenderEngine() { + virtual renderengine::RenderEngine::GraphicsApi graphicsApi() = 0; + virtual bool apiSupported() = 0; + std::unique_ptr<renderengine::RenderEngine> createRenderEngine() { renderengine::RenderEngineCreationArgs reCreationArgs = renderengine::RenderEngineCreationArgs::Builder() .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888)) @@ -133,65 +117,35 @@ public: .setPrecacheToneMapperShaderOnly(false) .setSupportsBackgroundBlur(true) .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM) - .setRenderEngineType(type()) + .setThreaded(renderengine::RenderEngine::Threaded::NO) + .setGraphicsApi(graphicsApi()) .build(); - return renderengine::skia::SkiaVkRenderEngine::create(reCreationArgs); + return renderengine::RenderEngine::create(reCreationArgs); } - - bool typeSupported() override { - return skia::SkiaVkRenderEngine::canSupportSkiaVkRenderEngine(); - } - void skip() { GTEST_SKIP(); } }; -class SkiaGLESRenderEngineFactory : public RenderEngineFactory { +class SkiaVkRenderEngineFactory : public RenderEngineFactory { public: - std::string name() override { return "SkiaGLRenderEngineFactory"; } + std::string name() override { return "SkiaVkRenderEngineFactory"; } - renderengine::RenderEngine::RenderEngineType type() { - return renderengine::RenderEngine::RenderEngineType::SKIA_GL; + renderengine::RenderEngine::GraphicsApi graphicsApi() override { + return renderengine::RenderEngine::GraphicsApi::VK; } - std::unique_ptr<renderengine::RenderEngine> createRenderEngine() override { - renderengine::RenderEngineCreationArgs reCreationArgs = - renderengine::RenderEngineCreationArgs::Builder() - .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888)) - .setImageCacheSize(1) - .setEnableProtectedContext(false) - .setPrecacheToneMapperShaderOnly(false) - .setSupportsBackgroundBlur(true) - .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM) - .setRenderEngineType(type()) - .build(); - return renderengine::skia::SkiaGLRenderEngine::create(reCreationArgs); + bool apiSupported() override { + return skia::SkiaVkRenderEngine::canSupportSkiaVkRenderEngine(); } - - bool typeSupported() override { return true; } }; -class SkiaGLESCMRenderEngineFactory : public RenderEngineFactory { +class SkiaGLESRenderEngineFactory : public RenderEngineFactory { public: - std::string name() override { return "SkiaGLCMRenderEngineFactory"; } - - renderengine::RenderEngine::RenderEngineType type() { - return renderengine::RenderEngine::RenderEngineType::SKIA_GL; - } + std::string name() override { return "SkiaGLRenderEngineFactory"; } - std::unique_ptr<renderengine::RenderEngine> createRenderEngine() override { - renderengine::RenderEngineCreationArgs reCreationArgs = - renderengine::RenderEngineCreationArgs::Builder() - .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888)) - .setImageCacheSize(1) - .setEnableProtectedContext(false) - .setPrecacheToneMapperShaderOnly(false) - .setSupportsBackgroundBlur(true) - .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM) - .setRenderEngineType(type()) - .build(); - return renderengine::skia::SkiaGLRenderEngine::create(reCreationArgs); + renderengine::RenderEngine::GraphicsApi graphicsApi() { + return renderengine::RenderEngine::GraphicsApi::GL; } - bool typeSupported() override { return true; } + bool apiSupported() override { return true; } }; class RenderEngineTest : public ::testing::TestWithParam<std::shared_ptr<RenderEngineFactory>> { @@ -1526,7 +1480,7 @@ INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest, std::make_shared<SkiaVkRenderEngineFactory>())); TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1534,7 +1488,7 @@ TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) { } TEST_P(RenderEngineTest, drawLayers_fillRedBufferAndEmptyBuffer) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1561,7 +1515,7 @@ TEST_P(RenderEngineTest, drawLayers_fillRedBufferAndEmptyBuffer) { } TEST_P(RenderEngineTest, drawLayers_withoutBuffers_withColorTransform) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1595,7 +1549,7 @@ TEST_P(RenderEngineTest, drawLayers_withoutBuffers_withColorTransform) { } TEST_P(RenderEngineTest, drawLayers_nullOutputBuffer) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1616,7 +1570,7 @@ TEST_P(RenderEngineTest, drawLayers_nullOutputBuffer) { } TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_colorSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1624,7 +1578,7 @@ TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_colorSource) { } TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_colorSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1632,7 +1586,7 @@ TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_colorSource) { } TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_colorSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1640,7 +1594,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_colorSource) { } TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_colorSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1648,7 +1602,7 @@ TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_colorSource) { } TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_colorSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1656,7 +1610,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_colorSource) { } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_colorSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1664,7 +1618,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_colorSource) { } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_colorSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1672,7 +1626,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_colorSource) { } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_colorSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1680,7 +1634,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_colorSource) { } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_colorSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1688,7 +1642,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_colorSource) { } TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_colorSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1696,7 +1650,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_colorSource) { } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_colorSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1706,7 +1660,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_colorSource) { TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_sourceDataspace) { const auto& renderEngineFactory = GetParam(); // skip for non color management - if (!renderEngineFactory->typeSupported()) { + if (!renderEngineFactory->apiSupported()) { GTEST_SKIP(); } @@ -1717,7 +1671,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_sourceDataspace) { TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_outputDataspace) { const auto& renderEngineFactory = GetParam(); // skip for non color management - if (!renderEngineFactory->typeSupported()) { + if (!renderEngineFactory->apiSupported()) { GTEST_SKIP(); } @@ -1726,7 +1680,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_outputDataspace) { } TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_colorSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1734,7 +1688,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_colorSource) { } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_colorSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1742,7 +1696,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_color } TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_colorSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1750,7 +1704,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_colorSource) { } TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_colorSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1758,7 +1712,7 @@ TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_colorSource) } TEST_P(RenderEngineTest, drawLayers_overlayCorners_colorSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1766,7 +1720,7 @@ TEST_P(RenderEngineTest, drawLayers_overlayCorners_colorSource) { } TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_opaqueBufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1774,7 +1728,7 @@ TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_opaqueBufferSource) { } TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_opaqueBufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1782,7 +1736,7 @@ TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_opaqueBufferSource) { } TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_opaqueBufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1790,7 +1744,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_opaqueBufferSource) { } TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_opaqueBufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1798,7 +1752,7 @@ TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_opaqueBufferSource) } TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_opaqueBufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1806,7 +1760,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_opaqueBufferSource) } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_opaqueBufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1814,7 +1768,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_opaqueBufferSource } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_opaqueBufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1822,7 +1776,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_opaqueBufferSourc } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_opaqueBufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1830,7 +1784,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_opaqueBufferSour } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_opaqueBufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1838,7 +1792,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_opaqueBufferSour } TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_opaqueBufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1846,7 +1800,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_opaqueBufferSource) } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_opaqueBufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1856,7 +1810,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_opaqueBufferSource) TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_opaqueBufferSource) { const auto& renderEngineFactory = GetParam(); // skip for non color management - if (!renderEngineFactory->typeSupported()) { + if (!renderEngineFactory->apiSupported()) { GTEST_SKIP(); } @@ -1867,7 +1821,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_o TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_opaqueBufferSource) { const auto& renderEngineFactory = GetParam(); // skip for non color management - if (!renderEngineFactory->typeSupported()) { + if (!renderEngineFactory->apiSupported()) { GTEST_SKIP(); } @@ -1876,7 +1830,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_o } TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_opaqueBufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1884,7 +1838,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_opaqueBufferSource) } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_opaqueBufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1892,7 +1846,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_opaqu } TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_opaqueBufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1900,7 +1854,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_opaqueBufferSour } TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_opaqueBufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1908,7 +1862,7 @@ TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_opaqueBuffer } TEST_P(RenderEngineTest, drawLayers_overlayCorners_opaqueBufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1916,7 +1870,7 @@ TEST_P(RenderEngineTest, drawLayers_overlayCorners_opaqueBufferSource) { } TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_bufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1924,7 +1878,7 @@ TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_bufferSource) { } TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_bufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1932,7 +1886,7 @@ TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_bufferSource) { } TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_bufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1940,7 +1894,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_bufferSource) { } TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_bufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1948,7 +1902,7 @@ TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_bufferSource) { } TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_bufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1956,7 +1910,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_bufferSource) { } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_bufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1964,7 +1918,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_bufferSource) { } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_bufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1972,7 +1926,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_bufferSource) { } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_bufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1980,7 +1934,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_bufferSource) { } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_bufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1988,7 +1942,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_bufferSource) { } TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_bufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -1996,7 +1950,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_bufferSource) { } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_bufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2006,7 +1960,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_bufferSource) { TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_bufferSource) { const auto& renderEngineFactory = GetParam(); // skip for non color management - if (!renderEngineFactory->typeSupported()) { + if (!renderEngineFactory->apiSupported()) { GTEST_SKIP(); } @@ -2017,7 +1971,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_b TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_bufferSource) { const auto& renderEngineFactory = GetParam(); // skip for non color management - if (!renderEngineFactory->typeSupported()) { + if (!renderEngineFactory->apiSupported()) { GTEST_SKIP(); } @@ -2026,7 +1980,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_b } TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_bufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2034,7 +1988,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_bufferSource) { } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_bufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2042,7 +1996,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_buffe } TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_bufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2050,7 +2004,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_bufferSource) { } TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_bufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2058,7 +2012,7 @@ TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_bufferSource } TEST_P(RenderEngineTest, drawLayers_overlayCorners_bufferSource) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2066,7 +2020,7 @@ TEST_P(RenderEngineTest, drawLayers_overlayCorners_bufferSource) { } TEST_P(RenderEngineTest, drawLayers_fillBufferTextureTransform) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2074,7 +2028,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferTextureTransform) { } TEST_P(RenderEngineTest, drawLayers_fillBuffer_premultipliesAlpha) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2082,7 +2036,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBuffer_premultipliesAlpha) { } TEST_P(RenderEngineTest, drawLayers_fillBuffer_withoutPremultiplyingAlpha) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2090,7 +2044,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBuffer_withoutPremultiplyingAlpha) { } TEST_P(RenderEngineTest, drawLayers_fillShadow_castsWithoutCasterLayer) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2108,7 +2062,7 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_castsWithoutCasterLayer) { } TEST_P(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2131,7 +2085,7 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) { } TEST_P(RenderEngineTest, drawLayers_fillShadow_casterColorLayer) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2155,7 +2109,7 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterColorLayer) { } TEST_P(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2180,7 +2134,7 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) { } TEST_P(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2206,7 +2160,7 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) { } TEST_P(RenderEngineTest, drawLayers_fillShadow_translucentCasterWithAlpha) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2235,7 +2189,7 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_translucentCasterWithAlpha) { } TEST_P(RenderEngineTest, cleanupPostRender_cleansUpOnce) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2271,7 +2225,7 @@ TEST_P(RenderEngineTest, cleanupPostRender_cleansUpOnce) { if (mRE->canSkipPostRenderCleanup()) { // Skia's Vk backend may keep the texture alive beyond drawLayersInternal, so // it never gets added to the cleanup list. In those cases, we can skip. - EXPECT_TRUE(GetParam()->type() == renderengine::RenderEngine::RenderEngineType::SKIA_VK); + EXPECT_TRUE(GetParam()->graphicsApi() == renderengine::RenderEngine::GraphicsApi::VK); } else { mRE->cleanupPostRender(); EXPECT_TRUE(mRE->canSkipPostRenderCleanup()); @@ -2279,7 +2233,7 @@ TEST_P(RenderEngineTest, cleanupPostRender_cleansUpOnce) { } TEST_P(RenderEngineTest, testRoundedCornersCrop) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2332,7 +2286,7 @@ TEST_P(RenderEngineTest, testRoundedCornersCrop) { } TEST_P(RenderEngineTest, testRoundedCornersParentCrop) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2380,7 +2334,7 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCrop) { } TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2417,7 +2371,7 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) { } TEST_P(RenderEngineTest, testRoundedCornersXY) { - if (GetParam()->type() != renderengine::RenderEngine::RenderEngineType::SKIA_GL) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } @@ -2460,7 +2414,7 @@ TEST_P(RenderEngineTest, testRoundedCornersXY) { } TEST_P(RenderEngineTest, testClear) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2492,7 +2446,7 @@ TEST_P(RenderEngineTest, testClear) { } TEST_P(RenderEngineTest, testDisableBlendingBuffer) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2543,7 +2497,7 @@ TEST_P(RenderEngineTest, testDisableBlendingBuffer) { } TEST_P(RenderEngineTest, testBorder) { - if (GetParam()->type() != renderengine::RenderEngine::RenderEngineType::SKIA_GL) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } @@ -2588,7 +2542,7 @@ TEST_P(RenderEngineTest, testBorder) { } TEST_P(RenderEngineTest, testDimming) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2663,7 +2617,7 @@ TEST_P(RenderEngineTest, testDimming) { } TEST_P(RenderEngineTest, testDimming_inGammaSpace) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2741,7 +2695,7 @@ TEST_P(RenderEngineTest, testDimming_inGammaSpace) { } TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2804,7 +2758,7 @@ TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform) { } TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform_deviceHandles) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2868,7 +2822,7 @@ TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform_devi } TEST_P(RenderEngineTest, testDimming_withoutTargetLuminance) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2922,7 +2876,7 @@ TEST_P(RenderEngineTest, testDimming_withoutTargetLuminance) { } TEST_P(RenderEngineTest, test_isOpaque) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2972,7 +2926,7 @@ TEST_P(RenderEngineTest, test_isOpaque) { } TEST_P(RenderEngineTest, test_tonemapPQMatches) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } @@ -2989,7 +2943,7 @@ TEST_P(RenderEngineTest, test_tonemapPQMatches) { } TEST_P(RenderEngineTest, test_tonemapHLGMatches) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } @@ -3006,7 +2960,7 @@ TEST_P(RenderEngineTest, test_tonemapHLGMatches) { } TEST_P(RenderEngineTest, r8_behaves_as_mask) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -3066,7 +3020,7 @@ TEST_P(RenderEngineTest, r8_behaves_as_mask) { } TEST_P(RenderEngineTest, r8_respects_color_transform) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -3131,7 +3085,7 @@ TEST_P(RenderEngineTest, r8_respects_color_transform) { } TEST_P(RenderEngineTest, r8_respects_color_transform_when_device_handles) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -3199,7 +3153,7 @@ TEST_P(RenderEngineTest, r8_respects_color_transform_when_device_handles) { } TEST_P(RenderEngineTest, primeShaderCache) { - if (!GetParam()->typeSupported()) { + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } initializeRenderEngine(); diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp index 1b9adba063..d56dbb2a7b 100644 --- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp +++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp @@ -35,8 +35,7 @@ struct RenderEngineThreadedTest : public ::testing::Test { void SetUp() override { mThreadedRE = renderengine::threaded::RenderEngineThreaded::create( - [this]() { return std::unique_ptr<renderengine::RenderEngine>(mRenderEngine); }, - renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED); + [this]() { return std::unique_ptr<renderengine::RenderEngine>(mRenderEngine); }); } std::unique_ptr<renderengine::threaded::RenderEngineThreaded> mThreadedRE; diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp index 367bee89f9..f4cebc05ec 100644 --- a/libs/renderengine/threaded/RenderEngineThreaded.cpp +++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp @@ -33,13 +33,12 @@ namespace android { namespace renderengine { namespace threaded { -std::unique_ptr<RenderEngineThreaded> RenderEngineThreaded::create(CreateInstanceFactory factory, - RenderEngineType type) { - return std::make_unique<RenderEngineThreaded>(std::move(factory), type); +std::unique_ptr<RenderEngineThreaded> RenderEngineThreaded::create(CreateInstanceFactory factory) { + return std::make_unique<RenderEngineThreaded>(std::move(factory)); } -RenderEngineThreaded::RenderEngineThreaded(CreateInstanceFactory factory, RenderEngineType type) - : RenderEngine(type) { +RenderEngineThreaded::RenderEngineThreaded(CreateInstanceFactory factory) + : RenderEngine(Threaded::YES) { ATRACE_CALL(); std::lock_guard lockThread(mThreadMutex); @@ -125,8 +124,10 @@ void RenderEngineThreaded::threadMain(CreateInstanceFactory factory) NO_THREAD_S } void RenderEngineThreaded::waitUntilInitialized() const { - std::unique_lock<std::mutex> lock(mInitializedMutex); - mInitializedCondition.wait(lock, [=] { return mIsInitialized; }); + if (!mIsInitialized) { + std::unique_lock<std::mutex> lock(mInitializedMutex); + mInitializedCondition.wait(lock, [this] { return mIsInitialized.load(); }); + } } std::future<void> RenderEngineThreaded::primeCache(bool shouldPrimeUltraHDR) { diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h index 74af2bd776..d440c961e7 100644 --- a/libs/renderengine/threaded/RenderEngineThreaded.h +++ b/libs/renderengine/threaded/RenderEngineThreaded.h @@ -37,10 +37,9 @@ using CreateInstanceFactory = std::function<std::unique_ptr<renderengine::Render */ class RenderEngineThreaded : public RenderEngine { public: - static std::unique_ptr<RenderEngineThreaded> create(CreateInstanceFactory factory, - RenderEngineType type); + static std::unique_ptr<RenderEngineThreaded> create(CreateInstanceFactory factory); - RenderEngineThreaded(CreateInstanceFactory factory, RenderEngineType type); + RenderEngineThreaded(CreateInstanceFactory factory); ~RenderEngineThreaded() override; std::future<void> primeCache(bool shouldPrimeUltraHDR) override; @@ -97,7 +96,7 @@ private: // Used to allow select thread safe methods to be accessed without requiring the // method to be invoked on the RenderEngine thread - bool mIsInitialized = false; + std::atomic_bool mIsInitialized = false; mutable std::mutex mInitializedMutex; mutable std::condition_variable mInitializedCondition; diff --git a/libs/sensor/Android.bp b/libs/sensor/Android.bp index d992aa5105..cc92bc30a0 100644 --- a/libs/sensor/Android.bp +++ b/libs/sensor/Android.bp @@ -21,6 +21,18 @@ package { default_applicable_licenses: ["frameworks_native_license"], } +aconfig_declarations { + name: "libsensor_flags", + package: "com.android.hardware.libsensor.flags", + srcs: ["libsensor_flags.aconfig"], +} + +cc_aconfig_library { + name: "libsensor_flags_c_lib", + host_supported: true, + aconfig_declarations: "libsensor_flags", +} + cc_library { name: "libsensor", @@ -52,6 +64,10 @@ cc_library { "android.companion.virtual.virtualdevice_aidl-cpp", ], + static_libs: [ + "libsensor_flags_c_lib", + ], + export_include_dirs: ["include"], export_shared_lib_headers: [ diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp index d112a1265c..9411e204e9 100644 --- a/libs/sensor/SensorManager.cpp +++ b/libs/sensor/SensorManager.cpp @@ -37,6 +37,8 @@ #include <sensor/Sensor.h> #include <sensor/SensorEventQueue.h> +#include <com_android_hardware_libsensor_flags.h> + // ---------------------------------------------------------------------------- namespace android { // ---------------------------------------------------------------------------- @@ -88,49 +90,51 @@ SensorManager& SensorManager::getInstanceForPackage(const String16& packageName) SensorManager* sensorManager; auto iterator = sPackageInstances.find(packageName); + const uid_t uid = IPCThreadState::self()->getCallingUid(); + const int deviceId = getDeviceIdForUid(uid); + + // Return the cached instance if the device association of the package has not changed. if (iterator != sPackageInstances.end()) { sensorManager = iterator->second; - } else { - String16 opPackageName = packageName; - const uid_t uid = IPCThreadState::self()->getCallingUid(); - - // It is possible that the calling code has no access to the package name. - // In this case we will get the packages for the calling UID and pick the - // first one for attributing the app op. This will work correctly for - // runtime permissions as for legacy apps we will toggle the app op for - // all packages in the UID. The caveat is that the operation may be attributed - // to the wrong package and stats based on app ops may be slightly off. - if (opPackageName.size() <= 0) { - sp<IBinder> binder = defaultServiceManager()->getService(String16("permission")); - if (binder != nullptr) { - Vector<String16> packages; - interface_cast<IPermissionController>(binder)->getPackagesForUid(uid, packages); - if (!packages.isEmpty()) { - opPackageName = packages[0]; - } else { - ALOGE("No packages for calling UID"); - } + if (sensorManager->mDeviceId == deviceId) { + return *sensorManager; + } + } + + // It is possible that the calling code has no access to the package name. + // In this case we will get the packages for the calling UID and pick the + // first one for attributing the app op. This will work correctly for + // runtime permissions as for legacy apps we will toggle the app op for + // all packages in the UID. The caveat is that the operation may be attributed + // to the wrong package and stats based on app ops may be slightly off. + String16 opPackageName = packageName; + if (opPackageName.size() <= 0) { + sp<IBinder> binder = defaultServiceManager()->getService(String16("permission")); + if (binder != nullptr) { + Vector<String16> packages; + interface_cast<IPermissionController>(binder)->getPackagesForUid(uid, packages); + if (!packages.isEmpty()) { + opPackageName = packages[0]; } else { - ALOGE("Cannot get permission service"); + ALOGE("No packages for calling UID"); } + } else { + ALOGE("Cannot get permission service"); } + } - // Check if the calling UID is observed on a virtual device. If so, provide that device's - // sensors by default instead of the default device's sensors. - const int deviceId = getDeviceIdForUid(uid); - sensorManager = new SensorManager(opPackageName, deviceId); - - // If we had no package name, we looked it up from the UID and the sensor - // manager instance we created should also be mapped to the empty package - // name, to avoid looking up the packages for a UID and get the same result. - if (packageName.size() <= 0) { - sPackageInstances.insert(std::make_pair(String16(), sensorManager)); - } + sensorManager = new SensorManager(opPackageName, deviceId); - // Stash the per package sensor manager. - sPackageInstances.insert(std::make_pair(opPackageName, sensorManager)); + // If we had no package name, we looked it up from the UID and the sensor + // manager instance we created should also be mapped to the empty package + // name, to avoid looking up the packages for a UID and get the same result. + if (packageName.size() <= 0) { + sPackageInstances.insert(std::make_pair(String16(), sensorManager)); } + // Stash the per package sensor manager. + sPackageInstances.insert(std::make_pair(opPackageName, sensorManager)); + return *sensorManager; } @@ -190,6 +194,9 @@ void SensorManager::sensorManagerDied() { } status_t SensorManager::assertStateLocked() { +#if COM_ANDROID_HARDWARE_LIBSENSOR_FLAGS(SENSORMANAGER_PING_BINDER) + if (mSensorServer == nullptr) { +#else bool initSensorManager = false; if (mSensorServer == nullptr) { initSensorManager = true; @@ -201,6 +208,7 @@ status_t SensorManager::assertStateLocked() { } } if (initSensorManager) { +#endif waitForSensorService(&mSensorServer); LOG_ALWAYS_FATAL_IF(mSensorServer == nullptr, "getService(SensorService) NULL"); @@ -248,6 +256,22 @@ ssize_t SensorManager::getSensorList(Sensor const* const** list) { return static_cast<ssize_t>(mSensors.size()); } +ssize_t SensorManager::getDefaultDeviceSensorList(Vector<Sensor> & list) { + Mutex::Autolock _l(mLock); + status_t err = assertStateLocked(); + if (err < 0) { + return static_cast<ssize_t>(err); + } + + if (mDeviceId == DEVICE_ID_DEFAULT) { + list = mSensors; + } else { + list = mSensorServer->getSensorList(mOpPackageName); + } + + return static_cast<ssize_t>(list.size()); +} + ssize_t SensorManager::getDynamicSensorList(Vector<Sensor> & dynamicSensors) { Mutex::Autolock _l(mLock); status_t err = assertStateLocked(); diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h index e67fac7617..49f050a0ac 100644 --- a/libs/sensor/include/sensor/SensorManager.h +++ b/libs/sensor/include/sensor/SensorManager.h @@ -58,6 +58,7 @@ public: ~SensorManager(); ssize_t getSensorList(Sensor const* const** list); + ssize_t getDefaultDeviceSensorList(Vector<Sensor> & list); ssize_t getDynamicSensorList(Vector<Sensor>& list); ssize_t getDynamicSensorList(Sensor const* const** list); ssize_t getRuntimeSensorList(int deviceId, Vector<Sensor>& list); diff --git a/libs/sensor/libsensor_flags.aconfig b/libs/sensor/libsensor_flags.aconfig new file mode 100644 index 0000000000..ef4d7370b5 --- /dev/null +++ b/libs/sensor/libsensor_flags.aconfig @@ -0,0 +1,9 @@ +package: "com.android.hardware.libsensor.flags" + +flag { + name: "sensormanager_ping_binder" + namespace: "sensors" + description: "Whether to pingBinder on SensorManager init" + bug: "322228259" + is_fixed_read_only: true +}
\ No newline at end of file diff --git a/libs/sensorprivacy/Android.bp b/libs/sensorprivacy/Android.bp index 00514c4417..1e7e70775a 100644 --- a/libs/sensorprivacy/Android.bp +++ b/libs/sensorprivacy/Android.bp @@ -57,6 +57,7 @@ cc_library_shared { filegroup { name: "libsensorprivacy_aidl", srcs: [ + "aidl/android/hardware/CameraPrivacyAllowlistEntry.aidl", "aidl/android/hardware/ISensorPrivacyListener.aidl", "aidl/android/hardware/ISensorPrivacyManager.aidl", ], diff --git a/libs/sensorprivacy/SensorPrivacyManager.cpp b/libs/sensorprivacy/SensorPrivacyManager.cpp index 57c74ee565..fe9378616d 100644 --- a/libs/sensorprivacy/SensorPrivacyManager.cpp +++ b/libs/sensorprivacy/SensorPrivacyManager.cpp @@ -108,7 +108,7 @@ bool SensorPrivacyManager::isSensorPrivacyEnabled() bool SensorPrivacyManager::isToggleSensorPrivacyEnabled(int sensor) { - sp<hardware::ISensorPrivacyManager> service = getService(); + sp<hardware::ISensorPrivacyManager> service = getService(); if (service != nullptr) { bool result; service->isCombinedToggleSensorPrivacyEnabled(sensor, &result); @@ -143,6 +143,39 @@ status_t SensorPrivacyManager::isToggleSensorPrivacyEnabled(int toggleType, int return UNKNOWN_ERROR; } +int SensorPrivacyManager::getToggleSensorPrivacyState(int toggleType, int sensor) +{ + sp<hardware::ISensorPrivacyManager> service = getService(); + if (service != nullptr) { + int result; + service->getToggleSensorPrivacyState(toggleType, sensor, &result); + return result; + } + // if the SensorPrivacyManager is not available then assume sensor privacy is disabled + return DISABLED; +} + +std::vector<hardware::CameraPrivacyAllowlistEntry> + SensorPrivacyManager::getCameraPrivacyAllowlist(){ + sp<hardware::ISensorPrivacyManager> service = getService(); + std::vector<hardware::CameraPrivacyAllowlistEntry> result; + if (service != nullptr) { + service->getCameraPrivacyAllowlist(&result); + return result; + } + return result; +} + +bool SensorPrivacyManager::isCameraPrivacyEnabled(String16 packageName){ + sp<hardware::ISensorPrivacyManager> service = getService(); + if (service != nullptr) { + bool result; + service->isCameraPrivacyEnabled(packageName, &result); + return result; + } + return false; +} + status_t SensorPrivacyManager::linkToDeath(const sp<IBinder::DeathRecipient>& recipient) { sp<hardware::ISensorPrivacyManager> service = getService(); diff --git a/libs/sensorprivacy/aidl/android/hardware/CameraPrivacyAllowlistEntry.aidl b/libs/sensorprivacy/aidl/android/hardware/CameraPrivacyAllowlistEntry.aidl new file mode 100644 index 0000000000..03e153704b --- /dev/null +++ b/libs/sensorprivacy/aidl/android/hardware/CameraPrivacyAllowlistEntry.aidl @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2024, 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.hardware; + +parcelable CameraPrivacyAllowlistEntry { + String packageName; + boolean isMandatory; +} diff --git a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl index eccd54c3eb..694af00a87 100644 --- a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl +++ b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl @@ -21,4 +21,5 @@ package android.hardware; */ oneway interface ISensorPrivacyListener { void onSensorPrivacyChanged(int toggleType, int sensor, boolean enabled); + void onSensorPrivacyStateChanged(int toggleType, int sensor, int state); } diff --git a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl index 49a1e1ea05..b6bd39e557 100644 --- a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl +++ b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl @@ -16,6 +16,7 @@ package android.hardware; +import android.hardware.CameraPrivacyAllowlistEntry; import android.hardware.ISensorPrivacyListener; /** @hide */ @@ -41,4 +42,15 @@ interface ISensorPrivacyManager { void setToggleSensorPrivacy(int userId, int source, int sensor, boolean enable); void setToggleSensorPrivacyForProfileGroup(int userId, int source, int sensor, boolean enable); + + List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist(); + + int getToggleSensorPrivacyState(int toggleType, int sensor); + + void setToggleSensorPrivacyState(int userId, int source, int sensor, int state); + + void setToggleSensorPrivacyStateForProfileGroup(int userId, int source, int sensor, int state); + + boolean isCameraPrivacyEnabled(String packageName); + } diff --git a/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h b/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h index fc5fdf7900..9e97e166be 100644 --- a/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h +++ b/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h @@ -32,12 +32,22 @@ class SensorPrivacyManager public: enum { TOGGLE_SENSOR_MICROPHONE = 1, - TOGGLE_SENSOR_CAMERA = 2 + TOGGLE_SENSOR_CAMERA = 2, + TOGGLE_SENSOR_UNKNOWN = -1 }; enum { TOGGLE_TYPE_SOFTWARE = 1, - TOGGLE_TYPE_HARDWARE = 2 + TOGGLE_TYPE_HARDWARE = 2, + TOGGLE_TYPE_UNKNOWN = -1 + }; + + enum { + ENABLED = 1, + DISABLED = 2, + AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS = 3, + AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS = 4, + AUTOMOTIVE_DRIVER_ASSISTANCE_APPS = 5 }; SensorPrivacyManager(); @@ -51,6 +61,9 @@ public: bool isToggleSensorPrivacyEnabled(int sensor); bool isToggleSensorPrivacyEnabled(int toggleType, int sensor); status_t isToggleSensorPrivacyEnabled(int toggleType, int sensor, bool &result); + int getToggleSensorPrivacyState(int toggleType, int sensor); + std::vector<hardware::CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist(); + bool isCameraPrivacyEnabled(String16 packageName); status_t linkToDeath(const sp<IBinder::DeathRecipient>& recipient); status_t unlinkToDeath(const sp<IBinder::DeathRecipient>& recipient); diff --git a/libs/ui/Gralloc2.cpp b/libs/ui/Gralloc2.cpp index e9b5decee8..a5aca9912f 100644 --- a/libs/ui/Gralloc2.cpp +++ b/libs/ui/Gralloc2.cpp @@ -384,8 +384,8 @@ std::string Gralloc2Allocator::dumpDebugInfo(bool /*less*/) const { status_t Gralloc2Allocator::allocate(std::string /*requestorName*/, uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage, - uint32_t bufferCount, uint32_t* outStride, - buffer_handle_t* outBufferHandles, bool importBuffers) const { + uint32_t* outStride, buffer_handle_t* outBufferHandles, + bool importBuffers) const { IMapper::BufferDescriptorInfo descriptorInfo = {}; descriptorInfo.width = width; descriptorInfo.height = height; @@ -400,6 +400,8 @@ status_t Gralloc2Allocator::allocate(std::string /*requestorName*/, uint32_t wid return error; } + constexpr auto bufferCount = 1; + auto ret = mAllocator->allocate(descriptor, bufferCount, [&](const auto& tmpError, const auto& tmpStride, const auto& tmpBuffers) { diff --git a/libs/ui/Gralloc3.cpp b/libs/ui/Gralloc3.cpp index 474d381dbb..152b35a505 100644 --- a/libs/ui/Gralloc3.cpp +++ b/libs/ui/Gralloc3.cpp @@ -371,7 +371,7 @@ std::string Gralloc3Allocator::dumpDebugInfo(bool /*less*/) const { status_t Gralloc3Allocator::allocate(std::string /*requestorName*/, uint32_t width, uint32_t height, android::PixelFormat format, uint32_t layerCount, - uint64_t usage, uint32_t bufferCount, uint32_t* outStride, + uint64_t usage, uint32_t* outStride, buffer_handle_t* outBufferHandles, bool importBuffers) const { IMapper::BufferDescriptorInfo descriptorInfo; sBufferDescriptorInfo(width, height, format, layerCount, usage, &descriptorInfo); @@ -383,6 +383,8 @@ status_t Gralloc3Allocator::allocate(std::string /*requestorName*/, uint32_t wid return error; } + constexpr auto bufferCount = 1; + auto ret = mAllocator->allocate(descriptor, bufferCount, [&](const auto& tmpError, const auto& tmpStride, const auto& tmpBuffers) { diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp index 03ff58a76c..d6970e0477 100644 --- a/libs/ui/Gralloc4.cpp +++ b/libs/ui/Gralloc4.cpp @@ -1069,7 +1069,7 @@ std::string Gralloc4Allocator::dumpDebugInfo(bool less) const { status_t Gralloc4Allocator::allocate(std::string requestorName, uint32_t width, uint32_t height, android::PixelFormat format, uint32_t layerCount, - uint64_t usage, uint32_t bufferCount, uint32_t* outStride, + uint64_t usage, uint32_t* outStride, buffer_handle_t* outBufferHandles, bool importBuffers) const { IMapper::BufferDescriptorInfo descriptorInfo; if (auto error = sBufferDescriptorInfo(requestorName, width, height, format, layerCount, usage, @@ -1084,6 +1084,8 @@ status_t Gralloc4Allocator::allocate(std::string requestorName, uint32_t width, return error; } + constexpr auto bufferCount = 1; + if (mAidlAllocator) { AllocationResult result; #pragma clang diagnostic push diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp index 25850f71cb..f217810b6a 100644 --- a/libs/ui/Gralloc5.cpp +++ b/libs/ui/Gralloc5.cpp @@ -19,6 +19,7 @@ #include <ui/Gralloc5.h> +#include <aidl/android/hardware/graphics/allocator/AllocationError.h> #include <aidlcommonsupport/NativeHandle.h> #include <android/binder_manager.h> #include <android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h> @@ -223,55 +224,75 @@ std::string Gralloc5Allocator::dumpDebugInfo(bool less) const { status_t Gralloc5Allocator::allocate(std::string requestorName, uint32_t width, uint32_t height, android::PixelFormat format, uint32_t layerCount, - uint64_t usage, uint32_t bufferCount, uint32_t *outStride, - buffer_handle_t *outBufferHandles, bool importBuffers) const { - auto descriptorInfo = makeDescriptor(requestorName, width, height, format, layerCount, usage); + uint64_t usage, uint32_t* outStride, + buffer_handle_t* outBufferHandles, bool importBuffers) const { + auto result = allocate(GraphicBufferAllocator::AllocationRequest{ + .importBuffer = importBuffers, + .width = width, + .height = height, + .format = format, + .layerCount = layerCount, + .usage = usage, + .requestorName = requestorName, + }); + + *outStride = result.stride; + outBufferHandles[0] = result.handle; + return result.status; +} + +GraphicBufferAllocator::AllocationResult Gralloc5Allocator::allocate( + const GraphicBufferAllocator::AllocationRequest& request) const { + auto descriptorInfo = makeDescriptor(request.requestorName, request.width, request.height, + request.format, request.layerCount, request.usage); if (!descriptorInfo) { - return BAD_VALUE; + return GraphicBufferAllocator::AllocationResult{BAD_VALUE}; + } + + descriptorInfo->additionalOptions.reserve(request.extras.size()); + for (const auto& option : request.extras) { + ExtendableType type; + type.name = option.name; + type.value = option.value; + descriptorInfo->additionalOptions.push_back(std::move(type)); } AllocationResult result; - auto status = mAllocator->allocate2(*descriptorInfo, bufferCount, &result); + auto status = mAllocator->allocate2(*descriptorInfo, 1, &result); if (!status.isOk()) { auto error = status.getExceptionCode(); if (error == EX_SERVICE_SPECIFIC) { - error = status.getServiceSpecificError(); - } - if (error == OK) { - error = UNKNOWN_ERROR; + switch (static_cast<AllocationError>(status.getServiceSpecificError())) { + case AllocationError::BAD_DESCRIPTOR: + error = BAD_VALUE; + break; + case AllocationError::NO_RESOURCES: + error = NO_MEMORY; + break; + default: + error = UNKNOWN_ERROR; + break; + } } - return error; + return GraphicBufferAllocator::AllocationResult{error}; } - if (importBuffers) { - for (uint32_t i = 0; i < bufferCount; i++) { - auto handle = makeFromAidl(result.buffers[i]); - auto error = mMapper.importBuffer(handle, &outBufferHandles[i]); - native_handle_delete(handle); - if (error != NO_ERROR) { - for (uint32_t j = 0; j < i; j++) { - mMapper.freeBuffer(outBufferHandles[j]); - outBufferHandles[j] = nullptr; - } - return error; - } + GraphicBufferAllocator::AllocationResult ret{OK}; + if (request.importBuffer) { + auto handle = makeFromAidl(result.buffers[0]); + auto error = mMapper.importBuffer(handle, &ret.handle); + native_handle_delete(handle); + if (error != NO_ERROR) { + return GraphicBufferAllocator::AllocationResult{error}; } } else { - for (uint32_t i = 0; i < bufferCount; i++) { - outBufferHandles[i] = dupFromAidl(result.buffers[i]); - if (!outBufferHandles[i]) { - for (uint32_t j = 0; j < i; j++) { - auto buffer = const_cast<native_handle_t *>(outBufferHandles[j]); - native_handle_close(buffer); - native_handle_delete(buffer); - outBufferHandles[j] = nullptr; - } - return NO_MEMORY; - } + ret.handle = dupFromAidl(result.buffers[0]); + if (!ret.handle) { + return GraphicBufferAllocator::AllocationResult{NO_MEMORY}; } } - *outStride = result.stride; + ret.stride = result.stride; // Release all the resources held by AllocationResult (specifically any remaining FDs) result = {}; @@ -280,7 +301,7 @@ status_t Gralloc5Allocator::allocate(std::string requestorName, uint32_t width, // is marked apex_available (b/214400477) and libbinder isn't (which of course is correct) // IPCThreadState::self()->flushCommands(); - return OK; + return ret; } void Gralloc5Mapper::preload() { diff --git a/libs/ui/GraphicBuffer.cpp b/libs/ui/GraphicBuffer.cpp index 429760ffe0..c007fdb587 100644 --- a/libs/ui/GraphicBuffer.cpp +++ b/libs/ui/GraphicBuffer.cpp @@ -106,6 +106,26 @@ GraphicBuffer::GraphicBuffer(const native_handle_t* inHandle, HandleWrapMethod m inUsage, inStride); } +GraphicBuffer::GraphicBuffer(const GraphicBufferAllocator::AllocationRequest& request) + : GraphicBuffer() { + GraphicBufferAllocator& allocator = GraphicBufferAllocator::get(); + auto result = allocator.allocate(request); + mInitCheck = result.status; + if (result.status == NO_ERROR) { + handle = result.handle; + stride = result.stride; + + mBufferMapper.getTransportSize(handle, &mTransportNumFds, &mTransportNumInts); + + width = static_cast<int>(request.width); + height = static_cast<int>(request.height); + format = request.format; + layerCount = request.layerCount; + usage = request.usage; + usage_deprecated = int(usage); + } +} + GraphicBuffer::~GraphicBuffer() { ATRACE_CALL(); @@ -143,6 +163,10 @@ ANativeWindowBuffer* GraphicBuffer::getNativeBuffer() const const_cast<GraphicBuffer*>(this)); } +status_t GraphicBuffer::getDataspace(ui::Dataspace* outDataspace) const { + return mBufferMapper.getDataspace(handle, outDataspace); +} + status_t GraphicBuffer::reallocate(uint32_t inWidth, uint32_t inHeight, PixelFormat inFormat, uint32_t inLayerCount, uint64_t inUsage) { diff --git a/libs/ui/GraphicBufferAllocator.cpp b/libs/ui/GraphicBufferAllocator.cpp index eb0bd4ed0a..98082fb81e 100644 --- a/libs/ui/GraphicBufferAllocator.cpp +++ b/libs/ui/GraphicBufferAllocator.cpp @@ -113,6 +113,79 @@ void GraphicBufferAllocator::dumpToSystemLog(bool less) { ALOGD("%s", s.c_str()); } +auto GraphicBufferAllocator::allocate(const AllocationRequest& request) -> AllocationResult { + ATRACE_CALL(); + if (!request.width || !request.height) { + return AllocationResult(BAD_VALUE); + } + + const auto width = request.width; + const auto height = request.height; + + const uint32_t bpp = bytesPerPixel(request.format); + if (std::numeric_limits<size_t>::max() / width / height < static_cast<size_t>(bpp)) { + ALOGE("Failed to allocate (%u x %u) layerCount %u format %d " + "usage %" PRIx64 ": Requesting too large a buffer size", + request.width, request.height, request.layerCount, request.format, request.usage); + return AllocationResult(BAD_VALUE); + } + + if (request.layerCount < 1) { + return AllocationResult(BAD_VALUE); + } + + auto result = mAllocator->allocate(request); + if (result.status == UNKNOWN_TRANSACTION) { + if (!request.extras.empty()) { + ALOGE("Failed to allocate with additional options, allocator version mis-match? " + "gralloc version = %d", + (int)mMapper.getMapperVersion()); + return result; + } + // If there's no additional options, fall back to previous allocate version + result.status = mAllocator->allocate(request.requestorName, request.width, request.height, + request.format, request.layerCount, request.usage, + &result.stride, &result.handle, request.importBuffer); + } + + if (result.status != NO_ERROR) { + ALOGE("Failed to allocate (%u x %u) layerCount %u format %d " + "usage %" PRIx64 ": %d", + request.width, request.height, request.layerCount, request.format, request.usage, + result.status); + return result; + } + + if (!request.importBuffer) { + return result; + } + size_t bufSize; + + // if stride has no meaning or is too large, + // approximate size with the input width instead + if ((result.stride) != 0 && + std::numeric_limits<size_t>::max() / height / (result.stride) < static_cast<size_t>(bpp)) { + bufSize = static_cast<size_t>(width) * height * bpp; + } else { + bufSize = static_cast<size_t>((result.stride)) * height * bpp; + } + + Mutex::Autolock _l(sLock); + KeyedVector<buffer_handle_t, alloc_rec_t>& list(sAllocList); + alloc_rec_t rec; + rec.width = width; + rec.height = height; + rec.stride = result.stride; + rec.format = request.format; + rec.layerCount = request.layerCount; + rec.usage = request.usage; + rec.size = bufSize; + rec.requestorName = request.requestorName; + list.add(result.handle, rec); + + return result; +} + status_t GraphicBufferAllocator::allocateHelper(uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage, buffer_handle_t* handle, uint32_t* stride, @@ -141,7 +214,7 @@ status_t GraphicBufferAllocator::allocateHelper(uint32_t width, uint32_t height, usage &= ~static_cast<uint64_t>((1 << 10) | (1 << 13)); status_t error = mAllocator->allocate(requestorName, width, height, format, layerCount, usage, - 1, stride, handle, importBuffer); + stride, handle, importBuffer); if (error != NO_ERROR) { ALOGE("Failed to allocate (%u x %u) layerCount %u format %d " "usage %" PRIx64 ": %d", diff --git a/libs/ui/include/ui/Gralloc.h b/libs/ui/include/ui/Gralloc.h index 496ba57789..e6015e0b5e 100644 --- a/libs/ui/include/ui/Gralloc.h +++ b/libs/ui/include/ui/Gralloc.h @@ -23,6 +23,7 @@ #include <ui/PixelFormat.h> #include <ui/Rect.h> #include <utils/StrongPointer.h> +#include "GraphicBufferAllocator.h" #include <string> @@ -218,9 +219,13 @@ public: */ virtual status_t allocate(std::string requestorName, uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage, - uint32_t bufferCount, uint32_t* outStride, - buffer_handle_t* outBufferHandles, + uint32_t* outStride, buffer_handle_t* outBufferHandles, bool importBuffers = true) const = 0; + + virtual GraphicBufferAllocator::AllocationResult allocate( + const GraphicBufferAllocator::AllocationRequest&) const { + return GraphicBufferAllocator::AllocationResult(UNKNOWN_TRANSACTION); + } }; } // namespace android diff --git a/libs/ui/include/ui/Gralloc2.h b/libs/ui/include/ui/Gralloc2.h index a7b6f49206..e50bb3af3e 100644 --- a/libs/ui/include/ui/Gralloc2.h +++ b/libs/ui/include/ui/Gralloc2.h @@ -81,9 +81,8 @@ public: std::string dumpDebugInfo(bool less = true) const override; status_t allocate(std::string requestorName, uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t bufferCount, - uint32_t* outStride, buffer_handle_t* outBufferHandles, - bool importBuffers = true) const override; + PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t* outStride, + buffer_handle_t* outBufferHandles, bool importBuffers = true) const override; private: const Gralloc2Mapper& mMapper; diff --git a/libs/ui/include/ui/Gralloc3.h b/libs/ui/include/ui/Gralloc3.h index 7367549964..035684abcd 100644 --- a/libs/ui/include/ui/Gralloc3.h +++ b/libs/ui/include/ui/Gralloc3.h @@ -82,9 +82,8 @@ public: std::string dumpDebugInfo(bool less = true) const override; status_t allocate(std::string requestorName, uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t bufferCount, - uint32_t* outStride, buffer_handle_t* outBufferHandles, - bool importBuffers = true) const override; + PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t* outStride, + buffer_handle_t* outBufferHandles, bool importBuffers = true) const override; private: const Gralloc3Mapper& mMapper; diff --git a/libs/ui/include/ui/Gralloc4.h b/libs/ui/include/ui/Gralloc4.h index df43be87cd..0f469c0b7e 100644 --- a/libs/ui/include/ui/Gralloc4.h +++ b/libs/ui/include/ui/Gralloc4.h @@ -174,9 +174,8 @@ public: std::string dumpDebugInfo(bool less = true) const override; status_t allocate(std::string requestorName, uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t bufferCount, - uint32_t* outStride, buffer_handle_t* outBufferHandles, - bool importBuffers = true) const override; + PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t* outStride, + buffer_handle_t* outBufferHandles, bool importBuffers = true) const override; private: const Gralloc4Mapper& mMapper; diff --git a/libs/ui/include/ui/Gralloc5.h b/libs/ui/include/ui/Gralloc5.h index 44b97d1a6f..f9e8f5e9fd 100644 --- a/libs/ui/include/ui/Gralloc5.h +++ b/libs/ui/include/ui/Gralloc5.h @@ -172,10 +172,12 @@ public: [[nodiscard]] status_t allocate(std::string requestorName, uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage, - uint32_t bufferCount, uint32_t *outStride, - buffer_handle_t *outBufferHandles, + uint32_t* outStride, buffer_handle_t* outBufferHandles, bool importBuffers) const override; + [[nodiscard]] GraphicBufferAllocator::AllocationResult allocate( + const GraphicBufferAllocator::AllocationRequest&) const override; + private: const Gralloc5Mapper &mMapper; std::shared_ptr<aidl::android::hardware::graphics::allocator::IAllocator> mAllocator; diff --git a/libs/ui/include/ui/GraphicBuffer.h b/libs/ui/include/ui/GraphicBuffer.h index f859848b0c..652d8ba709 100644 --- a/libs/ui/include/ui/GraphicBuffer.h +++ b/libs/ui/include/ui/GraphicBuffer.h @@ -26,6 +26,7 @@ #include <android/hardware_buffer.h> #include <ui/ANativeObjectBase.h> +#include <ui/GraphicBufferAllocator.h> #include <ui/GraphicBufferMapper.h> #include <ui/PixelFormat.h> #include <ui/Rect.h> @@ -103,6 +104,8 @@ public: uint32_t inLayerCount, uint64_t inUsage, std::string requestorName = "<Unknown>"); + GraphicBuffer(const GraphicBufferAllocator::AllocationRequest&); + // Create a GraphicBuffer from an existing handle. enum HandleWrapMethod : uint8_t { // Wrap and use the handle directly. It assumes the handle has been @@ -169,6 +172,8 @@ public: mGenerationNumber = generation; } + status_t getDataspace(ui::Dataspace* outDataspace) const; + // This function is privileged. It requires access to the allocator // device or service, which usually involves adding suitable selinux // rules. diff --git a/libs/ui/include/ui/GraphicBufferAllocator.h b/libs/ui/include/ui/GraphicBufferAllocator.h index 3ed988c4c2..8f461e193b 100644 --- a/libs/ui/include/ui/GraphicBufferAllocator.h +++ b/libs/ui/include/ui/GraphicBufferAllocator.h @@ -22,6 +22,7 @@ #include <memory> #include <string> +#include <vector> #include <cutils/native_handle.h> @@ -42,6 +43,35 @@ class GraphicBufferAllocator : public Singleton<GraphicBufferAllocator> public: static inline GraphicBufferAllocator& get() { return getInstance(); } + struct AdditionalOptions { + const char* name; + int64_t value; + }; + + struct AllocationRequest { + bool importBuffer; + uint32_t width; + uint32_t height; + PixelFormat format; + uint32_t layerCount; + uint64_t usage; + std::string requestorName; + std::vector<AdditionalOptions> extras; + }; + + struct AllocationResult { + status_t status; + buffer_handle_t handle = nullptr; + uint32_t stride = 0; + + explicit AllocationResult(status_t status) : status(status) {} + + explicit AllocationResult(buffer_handle_t handle, uint32_t stride) + : status(OK), handle(handle), stride(stride) {} + }; + + AllocationResult allocate(const AllocationRequest&); + /** * Allocates and imports a gralloc buffer. * diff --git a/libs/ui/tests/Android.bp b/libs/ui/tests/Android.bp index 8ce017d7a3..9a202150c5 100644 --- a/libs/ui/tests/Android.bp +++ b/libs/ui/tests/Android.bp @@ -54,6 +54,17 @@ cc_test { } cc_test { + name: "DisplayIdentification_test", + shared_libs: ["libui"], + static_libs: ["libgmock"], + srcs: ["DisplayIdentification_test.cpp"], + cflags: [ + "-Wall", + "-Werror", + ], +} + +cc_test { name: "FlattenableHelpers_test", shared_libs: ["libui"], srcs: ["FlattenableHelpers_test.cpp"], diff --git a/libs/ui/tests/GraphicBufferAllocator_test.cpp b/libs/ui/tests/GraphicBufferAllocator_test.cpp index f4c0afa71b..efca083e6e 100644 --- a/libs/ui/tests/GraphicBufferAllocator_test.cpp +++ b/libs/ui/tests/GraphicBufferAllocator_test.cpp @@ -51,7 +51,7 @@ public: std::cout << "Setting expected stride to " << stride << std::endl; EXPECT_CALL(*(reinterpret_cast<const mock::MockGrallocAllocator*>(mAllocator.get())), allocate) - .WillOnce(DoAll(SetArgPointee<7>(stride), Return(err))); + .WillOnce(DoAll(SetArgPointee<6>(stride), Return(err))); } std::unique_ptr<const GrallocAllocator>& getAllocator() { return mAllocator; } }; diff --git a/libs/ui/tests/mock/MockGrallocAllocator.h b/libs/ui/tests/mock/MockGrallocAllocator.h index d62e3e2192..d02b3873e0 100644 --- a/libs/ui/tests/mock/MockGrallocAllocator.h +++ b/libs/ui/tests/mock/MockGrallocAllocator.h @@ -35,7 +35,7 @@ public: MOCK_METHOD(std::string, dumpDebugInfo, (bool less), (const, override)); MOCK_METHOD(status_t, allocate, (std::string requestorName, uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, uint32_t bufferCount, uint32_t* outStride, + uint32_t layerCount, uint64_t usage, uint32_t* outStride, buffer_handle_t* outBufferHandles, bool less), (const, override)); }; diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp index 7f2d03d310..d244b1abc4 100644 --- a/services/inputflinger/Android.bp +++ b/services/inputflinger/Android.bp @@ -14,6 +14,7 @@ // Default flags to be used throughout all libraries in inputflinger. package { + default_team: "trendy_team_input_framework", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_native_license" @@ -77,6 +78,7 @@ filegroup { "InputCommonConverter.cpp", "InputDeviceMetricsCollector.cpp", "InputFilter.cpp", + "InputFilterCallbacks.cpp", "InputProcessor.cpp", "PointerChoreographer.cpp", "PreferStylusOverTouchBlocker.cpp", @@ -109,6 +111,7 @@ cc_defaults { ], static_libs: [ "libattestation", + "libperfetto_client_experimental", "libpalmrejection", "libui-types", ], diff --git a/services/inputflinger/InputFilter.cpp b/services/inputflinger/InputFilter.cpp index 9c4a3eb274..1ada5e5678 100644 --- a/services/inputflinger/InputFilter.cpp +++ b/services/inputflinger/InputFilter.cpp @@ -44,45 +44,25 @@ AidlKeyEvent notifyKeyArgsToKeyEvent(const NotifyKeyArgs& args) { return event; } -NotifyKeyArgs keyEventToNotifyKeyArgs(const AidlKeyEvent& event) { - return NotifyKeyArgs(event.id, event.eventTime, event.readTime, event.deviceId, - static_cast<uint32_t>(event.source), event.displayId, event.policyFlags, - static_cast<int32_t>(event.action), event.flags, event.keyCode, - event.scanCode, event.metaState, event.downTime); -} - -namespace { - -class RustCallbacks : public IInputFilter::BnInputFilterCallbacks { -public: - RustCallbacks(InputListenerInterface& nextListener) : mNextListener(nextListener) {} - ndk::ScopedAStatus sendKeyEvent(const AidlKeyEvent& event) override { - mNextListener.notifyKey(keyEventToNotifyKeyArgs(event)); - return ndk::ScopedAStatus::ok(); - } - -private: - InputListenerInterface& mNextListener; -}; - -} // namespace - -InputFilter::InputFilter(InputListenerInterface& listener, IInputFlingerRust& rust) - : mNextListener(listener), mCallbacks(ndk::SharedRefBase::make<RustCallbacks>(listener)) { +InputFilter::InputFilter(InputListenerInterface& listener, IInputFlingerRust& rust, + InputFilterPolicyInterface& policy) + : mNextListener(listener), + mCallbacks(ndk::SharedRefBase::make<InputFilterCallbacks>(listener, policy)), + mPolicy(policy) { LOG_ALWAYS_FATAL_IF(!rust.createInputFilter(mCallbacks, &mInputFilterRust).isOk()); LOG_ALWAYS_FATAL_IF(!mInputFilterRust); } void InputFilter::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) { + mDeviceInfos.clear(); + mDeviceInfos.reserve(args.inputDeviceInfos.size()); + for (auto info : args.inputDeviceInfos) { + AidlDeviceInfo& aidlInfo = mDeviceInfos.emplace_back(); + aidlInfo.deviceId = info.getId(); + aidlInfo.external = info.isExternal(); + } if (isFilterEnabled()) { - std::vector<AidlDeviceInfo> deviceInfos; - for (auto info : args.inputDeviceInfos) { - AidlDeviceInfo aidlInfo; - aidlInfo.deviceId = info.getId(); - aidlInfo.external = info.isExternal(); - deviceInfos.push_back(aidlInfo); - } - LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyInputDevicesChanged(deviceInfos).isOk()); + LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyInputDevicesChanged(mDeviceInfos).isOk()); } mNextListener.notify(args); } @@ -92,11 +72,11 @@ void InputFilter::notifyConfigurationChanged(const NotifyConfigurationChangedArg } void InputFilter::notifyKey(const NotifyKeyArgs& args) { - if (!isFilterEnabled()) { - mNextListener.notifyKey(args); + if (isFilterEnabled()) { + LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyKey(notifyKeyArgsToKeyEvent(args)).isOk()); return; } - LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyKey(notifyKeyArgsToKeyEvent(args)).isOk()); + mNextListener.notify(args); } void InputFilter::notifyMotion(const NotifyMotionArgs& args) { @@ -134,7 +114,36 @@ void InputFilter::setAccessibilityBounceKeysThreshold(nsecs_t threshold) { if (mConfig.bounceKeysThresholdNs != threshold) { mConfig.bounceKeysThresholdNs = threshold; - LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyConfigurationChanged(mConfig).isOk()); + notifyConfigurationChangedLocked(); + } +} + +void InputFilter::setAccessibilitySlowKeysThreshold(nsecs_t threshold) { + std::scoped_lock _l(mLock); + + if (mConfig.slowKeysThresholdNs != threshold) { + mConfig.slowKeysThresholdNs = threshold; + notifyConfigurationChangedLocked(); + } +} + +void InputFilter::setAccessibilityStickyKeysEnabled(bool enabled) { + std::scoped_lock _l(mLock); + + if (mConfig.stickyKeysEnabled != enabled) { + mConfig.stickyKeysEnabled = enabled; + notifyConfigurationChangedLocked(); + if (!enabled) { + // When Sticky keys is disabled, send callback to clear any saved sticky state. + mPolicy.notifyStickyModifierStateChanged(0, 0); + } + } +} + +void InputFilter::notifyConfigurationChangedLocked() { + LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyConfigurationChanged(mConfig).isOk()); + if (isFilterEnabled()) { + LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyInputDevicesChanged(mDeviceInfos).isOk()); } } diff --git a/services/inputflinger/InputFilter.h b/services/inputflinger/InputFilter.h index 06f7d0e601..4ddc9f4f6b 100644 --- a/services/inputflinger/InputFilter.h +++ b/services/inputflinger/InputFilter.h @@ -18,6 +18,8 @@ #include <aidl/com/android/server/inputflinger/IInputFlingerRust.h> #include <utils/Mutex.h> +#include "InputFilterCallbacks.h" +#include "InputFilterPolicyInterface.h" #include "InputListener.h" #include "NotifyArgs.h" @@ -33,6 +35,8 @@ public: */ virtual void dump(std::string& dump) = 0; virtual void setAccessibilityBounceKeysThreshold(nsecs_t threshold) = 0; + virtual void setAccessibilitySlowKeysThreshold(nsecs_t threshold) = 0; + virtual void setAccessibilityStickyKeysEnabled(bool enabled) = 0; }; class InputFilter : public InputFilterInterface { @@ -43,8 +47,10 @@ public: aidl::com::android::server::inputflinger::IInputFilter::IInputFilterCallbacks; using InputFilterConfiguration = aidl::com::android::server::inputflinger::InputFilterConfiguration; + using AidlDeviceInfo = aidl::com::android::server::inputflinger::DeviceInfo; - explicit InputFilter(InputListenerInterface& listener, IInputFlingerRust&); + explicit InputFilter(InputListenerInterface& listener, IInputFlingerRust& rust, + InputFilterPolicyInterface& policy); ~InputFilter() override = default; void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override; void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override; @@ -56,16 +62,23 @@ public: void notifyDeviceReset(const NotifyDeviceResetArgs& args) override; void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override; void setAccessibilityBounceKeysThreshold(nsecs_t threshold) override; + void setAccessibilitySlowKeysThreshold(nsecs_t threshold) override; + void setAccessibilityStickyKeysEnabled(bool enabled) override; void dump(std::string& dump) override; private: InputListenerInterface& mNextListener; - std::shared_ptr<IInputFilterCallbacks> mCallbacks; + std::shared_ptr<InputFilterCallbacks> mCallbacks; + InputFilterPolicyInterface& mPolicy; std::shared_ptr<IInputFilter> mInputFilterRust; + // Keep track of connected peripherals, so that if filters are enabled later, we can pass that + // info to the filters + std::vector<AidlDeviceInfo> mDeviceInfos; mutable std::mutex mLock; InputFilterConfiguration mConfig GUARDED_BY(mLock); bool isFilterEnabled(); + void notifyConfigurationChangedLocked() REQUIRES(mLock); }; } // namespace android diff --git a/services/inputflinger/InputFilterCallbacks.cpp b/services/inputflinger/InputFilterCallbacks.cpp new file mode 100644 index 0000000000..6c3144230f --- /dev/null +++ b/services/inputflinger/InputFilterCallbacks.cpp @@ -0,0 +1,115 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "InputFilterCallbacks" + +#include "InputFilterCallbacks.h" +#include <aidl/com/android/server/inputflinger/BnInputThread.h> +#include <android/binder_auto_utils.h> +#include <utils/StrongPointer.h> +#include <utils/Thread.h> +#include <functional> + +namespace android { + +using AidlKeyEvent = aidl::com::android::server::inputflinger::KeyEvent; + +NotifyKeyArgs keyEventToNotifyKeyArgs(const AidlKeyEvent& event) { + return NotifyKeyArgs(event.id, event.eventTime, event.readTime, event.deviceId, + static_cast<uint32_t>(event.source), event.displayId, event.policyFlags, + static_cast<int32_t>(event.action), event.flags, event.keyCode, + event.scanCode, event.metaState, event.downTime); +} + +namespace { + +using namespace aidl::com::android::server::inputflinger; + +class InputFilterThreadImpl : public Thread { +public: + explicit InputFilterThreadImpl(std::function<void()> loop) + : Thread(/*canCallJava=*/true), mThreadLoop(loop) {} + + ~InputFilterThreadImpl() {} + +private: + std::function<void()> mThreadLoop; + + bool threadLoop() override { + mThreadLoop(); + return true; + } +}; + +class InputFilterThread : public BnInputThread { +public: + InputFilterThread(std::shared_ptr<IInputThreadCallback> callback) : mCallback(callback) { + mThread = sp<InputFilterThreadImpl>::make([this]() { loopOnce(); }); + mThread->run("InputFilterThread", ANDROID_PRIORITY_URGENT_DISPLAY); + } + + ndk::ScopedAStatus finish() override { + mThread->requestExit(); + return ndk::ScopedAStatus::ok(); + } + +private: + sp<Thread> mThread; + std::shared_ptr<IInputThreadCallback> mCallback; + + void loopOnce() { LOG_ALWAYS_FATAL_IF(!mCallback->loopOnce().isOk()); } +}; + +} // namespace + +InputFilterCallbacks::InputFilterCallbacks(InputListenerInterface& listener, + InputFilterPolicyInterface& policy) + : mNextListener(listener), mPolicy(policy) {} + +ndk::ScopedAStatus InputFilterCallbacks::sendKeyEvent(const AidlKeyEvent& event) { + mNextListener.notifyKey(keyEventToNotifyKeyArgs(event)); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus InputFilterCallbacks::onModifierStateChanged(int32_t modifierState, + int32_t lockedModifierState) { + std::scoped_lock _l(mLock); + mStickyModifierState.modifierState = modifierState; + mStickyModifierState.lockedModifierState = lockedModifierState; + mPolicy.notifyStickyModifierStateChanged(modifierState, lockedModifierState); + ALOGI("Sticky keys modifier state changed: modifierState=%d, lockedModifierState=%d", + modifierState, lockedModifierState); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus InputFilterCallbacks::createInputFilterThread( + const std::shared_ptr<IInputThreadCallback>& callback, + std::shared_ptr<IInputThread>* aidl_return) { + *aidl_return = ndk::SharedRefBase::make<InputFilterThread>(callback); + return ndk::ScopedAStatus::ok(); +} + +uint32_t InputFilterCallbacks::getModifierState() { + std::scoped_lock _l(mLock); + return mStickyModifierState.modifierState; +} + +uint32_t InputFilterCallbacks::getLockedModifierState() { + std::scoped_lock _l(mLock); + return mStickyModifierState.lockedModifierState; +} + +} // namespace android diff --git a/services/inputflinger/InputFilterCallbacks.h b/services/inputflinger/InputFilterCallbacks.h new file mode 100644 index 0000000000..a74955b5c6 --- /dev/null +++ b/services/inputflinger/InputFilterCallbacks.h @@ -0,0 +1,65 @@ +/* + * 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 <aidl/com/android/server/inputflinger/IInputFlingerRust.h> +#include <android/binder_auto_utils.h> +#include <utils/Mutex.h> +#include <memory> +#include <mutex> +#include "InputFilterPolicyInterface.h" +#include "InputListener.h" +#include "NotifyArgs.h" + +/** + * The C++ component of InputFilter designed as a wrapper around the rust callback implementation. + */ +namespace android { + +using IInputFilter = aidl::com::android::server::inputflinger::IInputFilter; +using AidlKeyEvent = aidl::com::android::server::inputflinger::KeyEvent; +using aidl::com::android::server::inputflinger::IInputThread; +using IInputThreadCallback = + aidl::com::android::server::inputflinger::IInputThread::IInputThreadCallback; + +class InputFilterCallbacks : public IInputFilter::BnInputFilterCallbacks { +public: + explicit InputFilterCallbacks(InputListenerInterface& listener, + InputFilterPolicyInterface& policy); + ~InputFilterCallbacks() override = default; + + uint32_t getModifierState(); + uint32_t getLockedModifierState(); + +private: + InputListenerInterface& mNextListener; + InputFilterPolicyInterface& mPolicy; + mutable std::mutex mLock; + struct StickyModifierState { + uint32_t modifierState; + uint32_t lockedModifierState; + } mStickyModifierState GUARDED_BY(mLock); + + ndk::ScopedAStatus sendKeyEvent(const AidlKeyEvent& event) override; + ndk::ScopedAStatus onModifierStateChanged(int32_t modifierState, + int32_t lockedModifierState) override; + ndk::ScopedAStatus createInputFilterThread( + const std::shared_ptr<IInputThreadCallback>& callback, + std::shared_ptr<IInputThread>* aidl_return) override; +}; + +} // namespace android
\ No newline at end of file diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp index 296f2449ea..ae066c0f4a 100644 --- a/services/inputflinger/InputManager.cpp +++ b/services/inputflinger/InputManager.cpp @@ -127,7 +127,8 @@ std::shared_ptr<IInputFlingerRust> createInputFlingerRust() { */ InputManager::InputManager(const sp<InputReaderPolicyInterface>& readerPolicy, InputDispatcherPolicyInterface& dispatcherPolicy, - PointerChoreographerPolicyInterface& choreographerPolicy) { + PointerChoreographerPolicyInterface& choreographerPolicy, + InputFilterPolicyInterface& inputFilterPolicy) { mInputFlingerRust = createInputFlingerRust(); mDispatcher = createInputDispatcher(dispatcherPolicy); @@ -135,7 +136,8 @@ InputManager::InputManager(const sp<InputReaderPolicyInterface>& readerPolicy, std::make_unique<TracedInputListener>("InputDispatcher", *mDispatcher)); if (ENABLE_INPUT_FILTER_RUST) { - mInputFilter = std::make_unique<InputFilter>(*mTracingStages.back(), *mInputFlingerRust); + mInputFilter = std::make_unique<InputFilter>(*mTracingStages.back(), *mInputFlingerRust, + inputFilterPolicy); mTracingStages.emplace_back( std::make_unique<TracedInputListener>("InputFilter", *mInputFilter)); } @@ -258,13 +260,16 @@ void InputManager::dump(std::string& dump) { } // Used by tests only. -binder::Status InputManager::createInputChannel(const std::string& name, InputChannel* outChannel) { +binder::Status InputManager::createInputChannel(const std::string& name, + android::os::InputChannelCore* outChannel) { IPCThreadState* ipc = IPCThreadState::self(); - const int uid = ipc->getCallingUid(); + const uid_t uid = ipc->getCallingUid(); if (uid != AID_SHELL && uid != AID_ROOT) { - ALOGE("Invalid attempt to register input channel over IPC" - "from non shell/root entity (PID: %d)", ipc->getCallingPid()); - return binder::Status::ok(); + LOG(ERROR) << __func__ << " can only be called by SHELL or ROOT users, " + << "but was called from UID " << uid; + return binder::Status:: + fromExceptionCode(EX_SECURITY, + "This uid is not allowed to call createInputChannel"); } base::Result<std::unique_ptr<InputChannel>> channel = mDispatcher->createInputChannel(name); @@ -272,7 +277,7 @@ binder::Status InputManager::createInputChannel(const std::string& name, InputCh return binder::Status::fromExceptionCode(exceptionCodeFromStatusT(channel.error().code()), channel.error().message().c_str()); } - (*channel)->copyTo(*outChannel); + InputChannel::moveChannel(std::move(*channel), *outChannel); return binder::Status::ok(); } diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h index fa7db379e0..c479aaf9b6 100644 --- a/services/inputflinger/InputManager.h +++ b/services/inputflinger/InputManager.h @@ -29,6 +29,7 @@ #include <InputDispatcherInterface.h> #include <InputDispatcherPolicyInterface.h> +#include <InputFilterPolicyInterface.h> #include <PointerChoreographerPolicyInterface.h> #include <input/Input.h> #include <input/InputTransport.h> @@ -119,7 +120,8 @@ protected: public: InputManager(const sp<InputReaderPolicyInterface>& readerPolicy, InputDispatcherPolicyInterface& dispatcherPolicy, - PointerChoreographerPolicyInterface& choreographerPolicy); + PointerChoreographerPolicyInterface& choreographerPolicy, + InputFilterPolicyInterface& inputFilterPolicy); status_t start() override; status_t stop() override; @@ -134,7 +136,8 @@ public: void dump(std::string& dump) override; status_t dump(int fd, const Vector<String16>& args) override; - binder::Status createInputChannel(const std::string& name, InputChannel* outChannel) override; + binder::Status createInputChannel(const std::string& name, + android::os::InputChannelCore* outChannel) override; binder::Status removeInputChannel(const sp<IBinder>& connectionToken) override; binder::Status setFocusedWindow(const gui::FocusRequest&) override; diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp index 0be4c327b7..3ac4285304 100644 --- a/services/inputflinger/PointerChoreographer.cpp +++ b/services/inputflinger/PointerChoreographer.cpp @@ -109,7 +109,9 @@ NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotio const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); pc.move(deltaX, deltaY); - pc.unfade(PointerControllerInterface::Transition::IMMEDIATE); + if (canUnfadeOnDisplay(displayId)) { + pc.unfade(PointerControllerInterface::Transition::IMMEDIATE); + } const auto [x, y] = pc.getPosition(); NotifyMotionArgs newArgs(args); @@ -131,7 +133,9 @@ NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMo const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); pc.move(deltaX, deltaY); - pc.unfade(PointerControllerInterface::Transition::IMMEDIATE); + if (canUnfadeOnDisplay(displayId)) { + pc.unfade(PointerControllerInterface::Transition::IMMEDIATE); + } const auto [x, y] = pc.getPosition(); newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x); @@ -140,7 +144,9 @@ NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMo newArgs.yCursorPosition = y; } else { // This is a trackpad gesture with fake finger(s) that should not move the mouse pointer. - pc.unfade(PointerControllerInterface::Transition::IMMEDIATE); + if (canUnfadeOnDisplay(displayId)) { + pc.unfade(PointerControllerInterface::Transition::IMMEDIATE); + } const auto [x, y] = pc.getPosition(); for (uint32_t i = 0; i < newArgs.getPointerCount(); i++) { @@ -222,7 +228,8 @@ void PointerChoreographer::processStylusHoverEventLocked(const NotifyMotionArgs& pc.setPosition(x, y); if (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT) { pc.fade(PointerControllerInterface::Transition::IMMEDIATE); - } else { + pc.updatePointerIcon(PointerIconStyle::TYPE_NOT_SPECIFIED); + } else if (canUnfadeOnDisplay(args.displayId)) { pc.unfade(PointerControllerInterface::Transition::IMMEDIATE); } } @@ -322,6 +329,10 @@ InputDeviceInfo* PointerChoreographer::findInputDeviceLocked(DeviceId deviceId) return it != mInputDeviceInfos.end() ? &(*it) : nullptr; } +bool PointerChoreographer::canUnfadeOnDisplay(int32_t displayId) { + return mDisplaysWithPointersHidden.find(displayId) == mDisplaysWithPointersHidden.end(); +} + void PointerChoreographer::updatePointerControllersLocked() { std::set<int32_t /*displayId*/> mouseDisplaysToKeep; std::set<DeviceId> touchDevicesToKeep; @@ -341,7 +352,7 @@ void PointerChoreographer::updatePointerControllersLocked() { mMousePointersByDisplay.try_emplace(displayId, getMouseControllerConstructor(displayId)); auto [_, isNewMouseDevice] = mMouseDevices.emplace(info.getId()); - if (isNewMouseDevice || isNewMousePointer) { + if ((isNewMouseDevice || isNewMousePointer) && canUnfadeOnDisplay(displayId)) { mousePointerIt->second->unfade(PointerControllerInterface::Transition::IMMEDIATE); } } @@ -512,6 +523,28 @@ bool PointerChoreographer::setPointerIcon( return true; } +void PointerChoreographer::setPointerIconVisibility(int32_t displayId, bool visible) { + std::scoped_lock lock(mLock); + if (visible) { + mDisplaysWithPointersHidden.erase(displayId); + // We do not unfade the icons here, because we don't know when the last event happened. + return; + } + + mDisplaysWithPointersHidden.emplace(displayId); + + // Hide any icons that are currently visible on the display. + if (auto it = mMousePointersByDisplay.find(displayId); it != mMousePointersByDisplay.end()) { + const auto& [_, controller] = *it; + controller->fade(PointerControllerInterface::Transition::IMMEDIATE); + } + for (const auto& [_, controller] : mStylusPointersByDevice) { + if (controller->getDisplayId() == displayId) { + controller->fade(PointerControllerInterface::Transition::IMMEDIATE); + } + } +} + PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor( int32_t displayId) { std::function<std::shared_ptr<PointerControllerInterface>()> ctor = diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h index f46419ec2e..6aab3aade0 100644 --- a/services/inputflinger/PointerChoreographer.h +++ b/services/inputflinger/PointerChoreographer.h @@ -67,6 +67,11 @@ public: */ virtual bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, int32_t displayId, DeviceId deviceId) = 0; + /** + * Set whether pointer icons for mice, touchpads, and styluses should be visible on the + * given display. + */ + virtual void setPointerIconVisibility(int32_t displayId, bool visible) = 0; /** * This method may be called on any thread (usually by the input manager on a binder thread). @@ -89,6 +94,7 @@ public: void setStylusPointerIconEnabled(bool enabled) override; bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, int32_t displayId, DeviceId deviceId) override; + void setPointerIconVisibility(int32_t displayId, bool visible) override; void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override; void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override; @@ -110,6 +116,7 @@ private: std::pair<int32_t, PointerControllerInterface&> getDisplayIdAndMouseControllerLocked( int32_t associatedDisplayId) REQUIRES(mLock); InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(mLock); + bool canUnfadeOnDisplay(int32_t displayId) REQUIRES(mLock); NotifyMotionArgs processMotion(const NotifyMotionArgs& args); NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); @@ -143,6 +150,7 @@ private: std::vector<DisplayViewport> mViewports GUARDED_BY(mLock); bool mShowTouchesEnabled GUARDED_BY(mLock); bool mStylusPointerIconEnabled GUARDED_BY(mLock); + std::set<int32_t /*displayId*/> mDisplaysWithPointersHidden; }; } // namespace android diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl index 14b41cd00c..994d1c4b1a 100644 --- a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl +++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl @@ -17,6 +17,8 @@ package com.android.server.inputflinger; import com.android.server.inputflinger.DeviceInfo; +import com.android.server.inputflinger.IInputThread; +import com.android.server.inputflinger.IInputThread.IInputThreadCallback; import com.android.server.inputflinger.InputFilterConfiguration; import com.android.server.inputflinger.KeyEvent; @@ -33,6 +35,12 @@ interface IInputFilter { interface IInputFilterCallbacks { /** Sends back a filtered key event */ void sendKeyEvent(in KeyEvent event); + + /** Sends back modifier state */ + void onModifierStateChanged(int modifierState, int lockedModifierState); + + /** Creates an Input filter thread */ + IInputThread createInputFilterThread(in IInputThreadCallback callback); } /** Returns if InputFilter is enabled */ diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl new file mode 100644 index 0000000000..2f6b8fc6ff --- /dev/null +++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl @@ -0,0 +1,45 @@ +/* + * Copyright 2024 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 com.android.server.inputflinger; + +/** Interface to handle and run things on an InputThread + * Exposes main functionality of InputThread.h to rust which internally used system/core/libutils + * infrastructure. + * + * <p> + * NOTE: Tried using rust provided threading infrastructure but that uses std::thread which doesn't + * have JNI support and can't call into Java policy that we use currently. libutils provided + * Thread.h also recommends against using std::thread and using the provided infrastructure that + * already provides way of attaching JniEnv to the created thread. So, we are using this interface + * to expose the InputThread infrastructure to rust. + * </p> + * TODO(b/321769871): Implement the threading infrastructure with JniEnv support in rust + */ +interface IInputThread { + /** Finish input thread (if not running, this call does nothing) */ + void finish(); + + /** Callbacks from C++ to call into inputflinger rust components */ + interface IInputThreadCallback { + /** + * The created thread will keep looping and calling this function. + * It's the responsibility of RUST component to appropriately put the thread to sleep and + * wake according to the use case. + */ + void loopOnce(); + } +}
\ No newline at end of file diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl index 3b2e88ba24..9984a6a9ea 100644 --- a/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl +++ b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl @@ -22,4 +22,8 @@ package com.android.server.inputflinger; parcelable InputFilterConfiguration { // Threshold value for Bounce keys filter (check bounce_keys_filter.rs) long bounceKeysThresholdNs; + // If sticky keys filter is enabled (check sticky_keys_filter.rs) + boolean stickyKeysEnabled; + // Threshold value for Slow keys filter (check slow_keys_filter.rs) + long slowKeysThresholdNs; }
\ No newline at end of file diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp index c7baceea70..6d71acc775 100644 --- a/services/inputflinger/dispatcher/Android.bp +++ b/services/inputflinger/dispatcher/Android.bp @@ -13,6 +13,7 @@ // limitations under the License. package { + default_team: "trendy_team_input_framework", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_native_license" @@ -49,6 +50,7 @@ filegroup { "Monitor.cpp", "TouchedWindow.cpp", "TouchState.cpp", + "trace/*.cpp", ], } @@ -72,6 +74,7 @@ cc_defaults { static_libs: [ "libattestation", "libgui_window_info_static", + "libperfetto_client_experimental", ], target: { android: { diff --git a/services/inputflinger/dispatcher/Connection.cpp b/services/inputflinger/dispatcher/Connection.cpp index f304712e8c..9dee66f0f8 100644 --- a/services/inputflinger/dispatcher/Connection.cpp +++ b/services/inputflinger/dispatcher/Connection.cpp @@ -20,22 +20,15 @@ namespace android::inputdispatcher { -Connection::Connection(const std::shared_ptr<InputChannel>& inputChannel, bool monitor, +Connection::Connection(std::unique_ptr<InputChannel> inputChannel, bool monitor, const IdGenerator& idGenerator) : status(Status::NORMAL), - inputChannel(inputChannel), monitor(monitor), - inputPublisher(inputChannel), + inputPublisher(std::move(inputChannel)), inputState(idGenerator) {} -const std::string Connection::getWindowName() const { - if (inputChannel != nullptr) { - return inputChannel->getName(); - } - if (monitor) { - return "monitor"; - } - return "?"; -} +sp<IBinder> Connection::getToken() const { + return inputPublisher.getChannel().getConnectionToken(); +}; } // namespace android::inputdispatcher diff --git a/services/inputflinger/dispatcher/Connection.h b/services/inputflinger/dispatcher/Connection.h index c17baea0e5..a834a8cf86 100644 --- a/services/inputflinger/dispatcher/Connection.h +++ b/services/inputflinger/dispatcher/Connection.h @@ -42,7 +42,6 @@ public: }; Status status; - std::shared_ptr<InputChannel> inputChannel; // never null bool monitor; InputPublisher inputPublisher; InputState inputState; @@ -59,12 +58,14 @@ public: // yet received a "finished" response from the application. std::deque<std::unique_ptr<DispatchEntry>> waitQueue; - Connection(const std::shared_ptr<InputChannel>& inputChannel, bool monitor, + Connection(std::unique_ptr<InputChannel> inputChannel, bool monitor, const IdGenerator& idGenerator); - inline const std::string getInputChannelName() const { return inputChannel->getName(); } + inline const std::string getInputChannelName() const { + return inputPublisher.getChannel().getName(); + } - const std::string getWindowName() const; + sp<IBinder> getToken() const; }; } // namespace android::inputdispatcher diff --git a/services/inputflinger/dispatcher/DebugConfig.h b/services/inputflinger/dispatcher/DebugConfig.h index c889b9b136..fe33d94504 100644 --- a/services/inputflinger/dispatcher/DebugConfig.h +++ b/services/inputflinger/dispatcher/DebugConfig.h @@ -98,13 +98,6 @@ const bool DEBUG_TOUCH_MODE = constexpr bool DEBUG_TOUCH_OCCLUSION = true; /** - * Log debug messages about the app switch latency optimization. - * Enable this via "adb shell setprop log.tag.InputDispatcherAppSwitch DEBUG" (requires restart) - */ -const bool DEBUG_APP_SWITCH = - android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "AppSwitch"); - -/** * Log debug messages about hover events. * Enable this via "adb shell setprop log.tag.InputDispatcherHover DEBUG" (requires restart) */ diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp index cc0d49c15e..264dc03e70 100644 --- a/services/inputflinger/dispatcher/Entry.cpp +++ b/services/inputflinger/dispatcher/Entry.cpp @@ -164,6 +164,11 @@ std::string KeyEntry::getDescription() const { keyCode, scanCode, metaState, repeatCount, policyFlags); } +std::ostream& operator<<(std::ostream& out, const KeyEntry& keyEntry) { + out << keyEntry.getDescription(); + return out; +} + // --- TouchModeEntry --- TouchModeEntry::TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode, int displayId) @@ -277,9 +282,10 @@ std::string SensorEntry::getDescription() const { volatile int32_t DispatchEntry::sNextSeqAtomic; DispatchEntry::DispatchEntry(std::shared_ptr<const EventEntry> eventEntry, - ftl::Flags<InputTarget::Flags> targetFlags, + ftl::Flags<InputTargetFlags> targetFlags, const ui::Transform& transform, const ui::Transform& rawTransform, - float globalScaleFactor) + float globalScaleFactor, gui::Uid targetUid, int64_t vsyncId, + std::optional<int32_t> windowId) : seq(nextSeq()), eventEntry(std::move(eventEntry)), targetFlags(targetFlags), @@ -287,7 +293,10 @@ DispatchEntry::DispatchEntry(std::shared_ptr<const EventEntry> eventEntry, rawTransform(rawTransform), globalScaleFactor(globalScaleFactor), deliveryTime(0), - resolvedFlags(0) { + resolvedFlags(0), + targetUid(targetUid), + vsyncId(vsyncId), + windowId(windowId) { switch (this->eventEntry->type) { case EventEntry::Type::KEY: { const KeyEntry& keyEntry = static_cast<const KeyEntry&>(*this->eventEntry); diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h index e2e13c3fde..1298b5d511 100644 --- a/services/inputflinger/dispatcher/Entry.h +++ b/services/inputflinger/dispatcher/Entry.h @@ -17,7 +17,8 @@ #pragma once #include "InjectionState.h" -#include "InputTarget.h" +#include "InputTargetFlags.h" +#include "trace/EventTrackerInterface.h" #include <gui/InputApplication.h> #include <input/Input.h> @@ -125,6 +126,7 @@ struct KeyEntry : EventEntry { int32_t scanCode; int32_t metaState; nsecs_t downTime; + std::unique_ptr<trace::EventTrackerInterface> traceTracker; bool syntheticRepeat; // set to true for synthetic key repeats @@ -147,6 +149,8 @@ struct KeyEntry : EventEntry { std::string getDescription() const override; }; +std::ostream& operator<<(std::ostream& out, const KeyEntry& motionEntry); + struct MotionEntry : EventEntry { int32_t deviceId; uint32_t source; @@ -165,6 +169,7 @@ struct MotionEntry : EventEntry { nsecs_t downTime; std::vector<PointerProperties> pointerProperties; std::vector<PointerCoords> pointerCoords; + std::unique_ptr<trace::EventTrackerInterface> traceTracker; size_t getPointerCount() const { return pointerProperties.size(); } @@ -210,7 +215,7 @@ struct DispatchEntry { const uint32_t seq; // unique sequence number, never 0 std::shared_ptr<const EventEntry> eventEntry; // the event to dispatch - const ftl::Flags<InputTarget::Flags> targetFlags; + const ftl::Flags<InputTargetFlags> targetFlags; ui::Transform transform; ui::Transform rawTransform; float globalScaleFactor; @@ -222,17 +227,27 @@ struct DispatchEntry { int32_t resolvedFlags; + // Information about the dispatch window used for tracing. We avoid holding a window handle + // here because information in a window handle may be dynamically updated within the lifespan + // of this dispatch entry. + gui::Uid targetUid; + int64_t vsyncId; + // The window that this event is targeting. The only case when this windowId is not populated + // is when dispatching an event to a global monitor. + std::optional<int32_t> windowId; + DispatchEntry(std::shared_ptr<const EventEntry> eventEntry, - ftl::Flags<InputTarget::Flags> targetFlags, const ui::Transform& transform, - const ui::Transform& rawTransform, float globalScaleFactor); + ftl::Flags<InputTargetFlags> targetFlags, const ui::Transform& transform, + const ui::Transform& rawTransform, float globalScaleFactor, gui::Uid targetUid, + int64_t vsyncId, std::optional<int32_t> windowId); DispatchEntry(const DispatchEntry&) = delete; DispatchEntry& operator=(const DispatchEntry&) = delete; inline bool hasForegroundTarget() const { - return targetFlags.test(InputTarget::Flags::FOREGROUND); + return targetFlags.test(InputTargetFlags::FOREGROUND); } - inline bool isSplit() const { return targetFlags.test(InputTarget::Flags::SPLIT); } + inline bool isSplit() const { return targetFlags.test(InputTargetFlags::SPLIT); } private: static volatile int32_t sNextSeqAtomic; diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 9a3fe436b9..3a18f256e2 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -35,7 +35,7 @@ #include <input/PrintTools.h> #include <input/TraceTools.h> #include <openssl/mem.h> -#include <powermanager/PowerManager.h> +#include <private/android_filesystem_config.h> #include <unistd.h> #include <utils/Trace.h> @@ -52,6 +52,8 @@ #include "Connection.h" #include "DebugConfig.h" #include "InputDispatcher.h" +#include "trace/InputTracer.h" +#include "trace/InputTracingPerfettoBackend.h" #define INDENT " " #define INDENT2 " " @@ -72,11 +74,25 @@ using android::os::InputEventInjectionResult; using android::os::InputEventInjectionSync; namespace input_flags = com::android::input::flags; -static const bool REMOVE_APP_SWITCH_DROPS = input_flags::remove_app_switch_drops(); - namespace android::inputdispatcher { namespace { + +// Input tracing is only available on debuggable builds (userdebug and eng) when the feature +// flag is enabled. When the flag is changed, tracing will only be available after reboot. +bool isInputTracingEnabled() { + static const std::string buildType = base::GetProperty("ro.build.type", "user"); + static const bool isUserdebugOrEng = buildType == "userdebug" || buildType == "eng"; + return input_flags::enable_input_event_tracing() && isUserdebugOrEng; +} + +template <class Entry> +void ensureEventTraced(const Entry& entry) { + if (!entry.traceTracker) { + LOG(FATAL) << "Expected event entry to be traced, but it wasn't: " << entry; + } +} + // Temporarily releases a held mutex for the lifetime of the instance. // Named to match std::scoped_lock class scoped_unlock { @@ -94,10 +110,8 @@ const std::chrono::duration DEFAULT_INPUT_DISPATCHING_TIMEOUT = std::chrono::mil android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS * HwTimeoutMultiplier()); -// Amount of time to allow for all pending events to be processed when an app switch -// key is on the way. This is used to preempt input dispatch and drop input events -// when an application takes too long to respond and the user has pressed an app switch key. -constexpr nsecs_t APP_SWITCH_TIMEOUT = 500 * 1000000LL; // 0.5sec +// The default minimum time gap between two user activity poke events. +const std::chrono::milliseconds DEFAULT_USER_ACTIVITY_POKE_INTERVAL = 100ms; const std::chrono::duration STALE_EVENT_TIMEOUT = std::chrono::seconds(10) * HwTimeoutMultiplier(); @@ -107,11 +121,6 @@ constexpr nsecs_t SLOW_EVENT_PROCESSING_WARNING_TIMEOUT = 2000 * 1000000LL; // 2 // Log a warning when an interception call takes longer than this to process. constexpr std::chrono::milliseconds SLOW_INTERCEPTION_THRESHOLD = 50ms; -// Additional key latency in case a connection is still processing some motion events. -// This will help with the case when a user touched a button that opens a new window, -// and gives us the chance to dispatch the key to this new window. -constexpr std::chrono::nanoseconds KEY_WAITING_FOR_EVENTS_TIMEOUT = 500ms; - // Number of recent events to keep for debugging purposes. constexpr size_t RECENT_QUEUE_MAX_SIZE = 10; @@ -252,6 +261,14 @@ Result<void> validateInputEvent(const InputEvent& event) { } } +std::bitset<MAX_POINTER_ID + 1> getPointerIds(const std::vector<PointerProperties>& pointers) { + std::bitset<MAX_POINTER_ID + 1> pointerIds; + for (const PointerProperties& pointer : pointers) { + pointerIds.set(pointer.id); + } + return pointerIds; +} + std::string dumpRegion(const Region& region) { if (region.isEmpty()) { return "<empty>"; @@ -348,14 +365,22 @@ size_t firstMarkedBit(T set) { return i; } -std::unique_ptr<DispatchEntry> createDispatchEntry( - const InputTarget& inputTarget, std::shared_ptr<const EventEntry> eventEntry, - ftl::Flags<InputTarget::Flags> inputTargetFlags) { +std::unique_ptr<DispatchEntry> createDispatchEntry(const InputTarget& inputTarget, + std::shared_ptr<const EventEntry> eventEntry, + ftl::Flags<InputTarget::Flags> inputTargetFlags, + int64_t vsyncId) { + const sp<WindowInfoHandle> win = inputTarget.windowHandle; + const std::optional<int32_t> windowId = + win ? std::make_optional(win->getInfo()->id) : std::nullopt; + // Assume the only targets that are not associated with a window are global monitors, and use + // the system UID for global monitors for tracing purposes. + const gui::Uid uid = win ? win->getInfo()->ownerUid : gui::Uid(AID_SYSTEM); if (inputTarget.useDefaultPointerTransform()) { const ui::Transform& transform = inputTarget.getDefaultPointerTransform(); return std::make_unique<DispatchEntry>(eventEntry, inputTargetFlags, transform, inputTarget.displayTransform, - inputTarget.globalScaleFactor); + inputTarget.globalScaleFactor, uid, vsyncId, + windowId); } ALOG_ASSERT(eventEntry->type == EventEntry::Type::MOTION); @@ -402,19 +427,10 @@ std::unique_ptr<DispatchEntry> createDispatchEntry( std::unique_ptr<DispatchEntry> dispatchEntry = std::make_unique<DispatchEntry>(std::move(combinedMotionEntry), inputTargetFlags, firstPointerTransform, inputTarget.displayTransform, - inputTarget.globalScaleFactor); + inputTarget.globalScaleFactor, uid, vsyncId, windowId); return dispatchEntry; } -status_t openInputChannelPair(const std::string& name, std::shared_ptr<InputChannel>& serverChannel, - std::unique_ptr<InputChannel>& clientChannel) { - std::unique_ptr<InputChannel> uniqueServerChannel; - status_t result = InputChannel::openInputChannelPair(name, uniqueServerChannel, clientChannel); - - serverChannel = std::move(uniqueServerChannel); - return result; -} - template <typename T> bool sharedPointersEqual(const std::shared_ptr<T>& lhs, const std::shared_ptr<T>& rhs) { if (lhs == nullptr && rhs == nullptr) { @@ -630,15 +646,15 @@ std::vector<TouchedWindow> getHoveringWindowsLocked(const TouchState* oldState, } // We should consider all hovering pointers here. But for now, just use the first one - const int32_t pointerId = entry.pointerProperties[0].id; + const PointerProperties& pointer = entry.pointerProperties[0]; std::set<sp<WindowInfoHandle>> oldWindows; if (oldState != nullptr) { - oldWindows = oldState->getWindowsWithHoveringPointer(entry.deviceId, pointerId); + oldWindows = oldState->getWindowsWithHoveringPointer(entry.deviceId, pointer.id); } std::set<sp<WindowInfoHandle>> newWindows = - newTouchState.getWindowsWithHoveringPointer(entry.deviceId, pointerId); + newTouchState.getWindowsWithHoveringPointer(entry.deviceId, pointer.id); // If the pointer is no longer in the new window set, send HOVER_EXIT. for (const sp<WindowInfoHandle>& oldWindow : oldWindows) { @@ -672,7 +688,7 @@ std::vector<TouchedWindow> getHoveringWindowsLocked(const TouchState* oldState, } touchedWindow.dispatchMode = InputTarget::DispatchMode::AS_IS; } - touchedWindow.addHoveringPointer(entry.deviceId, pointerId); + touchedWindow.addHoveringPointer(entry.deviceId, pointer); if (canReceiveForegroundTouches(*newWindow->getInfo())) { touchedWindow.targetFlags |= InputTarget::Flags::FOREGROUND; } @@ -698,7 +714,7 @@ void filterUntrustedTargets(TouchState& touchState, std::vector<InputTarget>& ta // TODO(b/282025641): simplify this code once InputTargets are being identified // separately from TouchedWindows. std::erase_if(targets, [&](const InputTarget& target) { - return target.inputChannel->getConnectionToken() == window.windowHandle->getToken(); + return target.connection->getToken() == window.windowHandle->getToken(); }); return true; } @@ -740,17 +756,84 @@ bool shouldSplitTouch(const TouchState& touchState, const MotionEntry& entry) { return true; } +/** + * Return true if stylus is currently down anywhere on the specified display, and false otherwise. + */ +bool isStylusActiveInDisplay( + int32_t displayId, + const std::unordered_map<int32_t /*displayId*/, TouchState>& touchStatesByDisplay) { + const auto it = touchStatesByDisplay.find(displayId); + if (it == touchStatesByDisplay.end()) { + return false; + } + const TouchState& state = it->second; + return state.hasActiveStylus(); +} + +Result<void> validateWindowInfosUpdate(const gui::WindowInfosUpdate& update) { + struct HashFunction { + size_t operator()(const WindowInfo& info) const { return info.id; } + }; + + std::unordered_set<WindowInfo, HashFunction> windowSet; + for (const WindowInfo& info : update.windowInfos) { + const auto [_, inserted] = windowSet.insert(info); + if (!inserted) { + return Error() << "Duplicate entry for " << info; + } + } + return {}; +} + +int32_t getUserActivityEventType(const EventEntry& eventEntry) { + switch (eventEntry.type) { + case EventEntry::Type::KEY: { + return USER_ACTIVITY_EVENT_BUTTON; + } + case EventEntry::Type::MOTION: { + const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry); + if (MotionEvent::isTouchEvent(motionEntry.source, motionEntry.action)) { + return USER_ACTIVITY_EVENT_TOUCH; + } + return USER_ACTIVITY_EVENT_OTHER; + } + default: { + LOG_ALWAYS_FATAL("%s events are not user activity", + ftl::enum_string(eventEntry.type).c_str()); + } + } +} + +std::pair<bool /*cancelPointers*/, bool /*cancelNonPointers*/> expandCancellationMode( + CancelationOptions::Mode mode) { + switch (mode) { + case CancelationOptions::Mode::CANCEL_ALL_EVENTS: + return {true, true}; + case CancelationOptions::Mode::CANCEL_POINTER_EVENTS: + return {true, false}; + case CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS: + return {false, true}; + case CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS: + return {false, true}; + } +} + } // namespace // --- InputDispatcher --- InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy) + : InputDispatcher(policy, + isInputTracingEnabled() ? std::make_unique<trace::impl::PerfettoBackend>() + : nullptr) {} + +InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy, + std::unique_ptr<trace::InputTracingBackendInterface> traceBackend) : mPolicy(policy), mPendingEvent(nullptr), mLastDropReason(DropReason::NOT_DROPPED), mIdGenerator(IdGenerator::Source::INPUT_DISPATCHER), - mAppSwitchSawKeyDown(false), - mAppSwitchDueTime(LLONG_MAX), + mMinTimeBetweenUserActivityPokes(DEFAULT_USER_ACTIVITY_POKE_INTERVAL), mNextUnblockedEvent(nullptr), mMonitorDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT), mDispatchEnabled(false), @@ -769,6 +852,12 @@ InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy) SurfaceComposerClient::getDefault()->addWindowInfosListener(mWindowInfoListener); #endif mKeyRepeatState.lastKeyEntry = nullptr; + + if (traceBackend) { + mTracer = std::make_unique<trace::impl::InputTracer>(std::move(traceBackend)); + } + + mLastUserActivityTimes.fill(0); } InputDispatcher::~InputDispatcher() { @@ -781,7 +870,7 @@ InputDispatcher::~InputDispatcher() { while (!mConnectionsByToken.empty()) { std::shared_ptr<Connection> connection = mConnectionsByToken.begin()->second; - removeInputChannelLocked(connection->inputChannel->getConnectionToken(), /*notify=*/false); + removeInputChannelLocked(connection->getToken(), /*notify=*/false); } } @@ -812,7 +901,7 @@ void InputDispatcher::dispatchOnce() { // Run a dispatch loop if there are no pending commands. // The dispatch loop might enqueue commands to run afterwards. if (!haveCommandsLocked()) { - dispatchOnceInnerLocked(&nextWakeupTime); + dispatchOnceInnerLocked(/*byref*/ nextWakeupTime); } // Run all pending commands if there are any. @@ -901,7 +990,7 @@ nsecs_t InputDispatcher::processAnrsLocked() { } connection->responsive = false; // Stop waking up for this unresponsive connection - mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken()); + mAnrTracker.eraseToken(connection->getToken()); onAnrLocked(connection); return LLONG_MIN; } @@ -911,15 +1000,14 @@ std::chrono::nanoseconds InputDispatcher::getDispatchingTimeoutLocked( if (connection->monitor) { return mMonitorDispatchingTimeout; } - const sp<WindowInfoHandle> window = - getWindowHandleLocked(connection->inputChannel->getConnectionToken()); + const sp<WindowInfoHandle> window = getWindowHandleLocked(connection->getToken()); if (window != nullptr) { return window->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT); } return DEFAULT_INPUT_DISPATCHING_TIMEOUT; } -void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { +void InputDispatcher::dispatchOnceInnerLocked(nsecs_t& nextWakeupTime) { nsecs_t currentTime = now(); // Reset the key repeat timer whenever normal dispatch is suspended while the @@ -937,38 +1025,16 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { return; } - // Optimize latency of app switches. - // Essentially we start a short timeout when an app switch key (HOME / ENDCALL) has - // been pressed. When it expires, we preempt dispatch and drop all other pending events. - bool isAppSwitchDue; - if (!REMOVE_APP_SWITCH_DROPS) { - isAppSwitchDue = mAppSwitchDueTime <= currentTime; - if (mAppSwitchDueTime < *nextWakeupTime) { - *nextWakeupTime = mAppSwitchDueTime; - } - } - // Ready to start a new event. // If we don't already have a pending event, go grab one. if (!mPendingEvent) { if (mInboundQueue.empty()) { - if (!REMOVE_APP_SWITCH_DROPS) { - if (isAppSwitchDue) { - // The inbound queue is empty so the app switch key we were waiting - // for will never arrive. Stop waiting for it. - resetPendingAppSwitchLocked(false); - isAppSwitchDue = false; - } - } - // Synthesize a key repeat if appropriate. if (mKeyRepeatState.lastKeyEntry) { if (currentTime >= mKeyRepeatState.nextRepeatTime) { mPendingEvent = synthesizeKeyRepeatLocked(currentTime); } else { - if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) { - *nextWakeupTime = mKeyRepeatState.nextRepeatTime; - } + nextWakeupTime = std::min(nextWakeupTime, mKeyRepeatState.nextRepeatTime); } } @@ -1057,16 +1123,6 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { case EventEntry::Type::KEY: { std::shared_ptr<const KeyEntry> keyEntry = std::static_pointer_cast<const KeyEntry>(mPendingEvent); - if (!REMOVE_APP_SWITCH_DROPS) { - if (isAppSwitchDue) { - if (isAppSwitchKeyEvent(*keyEntry)) { - resetPendingAppSwitchLocked(true); - isAppSwitchDue = false; - } else if (dropReason == DropReason::NOT_DROPPED) { - dropReason = DropReason::APP_SWITCH; - } - } - } if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *keyEntry)) { dropReason = DropReason::STALE; } @@ -1074,17 +1130,16 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { dropReason = DropReason::BLOCKED; } done = dispatchKeyLocked(currentTime, keyEntry, &dropReason, nextWakeupTime); + if (done && mTracer) { + ensureEventTraced(*keyEntry); + mTracer->eventProcessingComplete(*keyEntry->traceTracker); + } break; } case EventEntry::Type::MOTION: { std::shared_ptr<const MotionEntry> motionEntry = std::static_pointer_cast<const MotionEntry>(mPendingEvent); - if (!REMOVE_APP_SWITCH_DROPS) { - if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) { - dropReason = DropReason::APP_SWITCH; - } - } if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *motionEntry)) { // The event is stale. However, only drop stale events if there isn't an ongoing // gesture. That would allow us to complete the processing of the current stroke. @@ -1104,17 +1159,17 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { } } done = dispatchMotionLocked(currentTime, motionEntry, &dropReason, nextWakeupTime); + if (done && mTracer) { + ensureEventTraced(*motionEntry); + mTracer->eventProcessingComplete(*motionEntry->traceTracker); + } break; } case EventEntry::Type::SENSOR: { std::shared_ptr<const SensorEntry> sensorEntry = std::static_pointer_cast<const SensorEntry>(mPendingEvent); - if (!REMOVE_APP_SWITCH_DROPS) { - if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) { - dropReason = DropReason::APP_SWITCH; - } - } + // Sensor timestamps use SYSTEM_TIME_BOOTTIME time base, so we can't use // 'currentTime' here, get SYSTEM_TIME_BOOTTIME instead. nsecs_t bootTime = systemTime(SYSTEM_TIME_BOOTTIME); @@ -1134,7 +1189,7 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { mLastDropReason = dropReason; releasePendingEventLocked(); - *nextWakeupTime = LLONG_MIN; // force next poll to wake up immediately + nextWakeupTime = LLONG_MIN; // force next poll to wake up immediately } } @@ -1146,7 +1201,7 @@ bool InputDispatcher::isStaleEvent(nsecs_t currentTime, const EventEntry& entry) * Return true if the events preceding this incoming motion event should be dropped * Return false otherwise (the default behaviour) */ -bool InputDispatcher::shouldPruneInboundQueueLocked(const MotionEntry& motionEntry) { +bool InputDispatcher::shouldPruneInboundQueueLocked(const MotionEntry& motionEntry) const { const bool isPointerDownEvent = motionEntry.action == AMOTION_EVENT_ACTION_DOWN && isFromSource(motionEntry.source, AINPUT_SOURCE_CLASS_POINTER); @@ -1188,16 +1243,6 @@ bool InputDispatcher::shouldPruneInboundQueueLocked(const MotionEntry& motionEnt } } - // Prevent getting stuck: if we have a pending key event, and some motion events that have not - // yet been processed by some connections, the dispatcher will wait for these motion - // events to be processed before dispatching the key event. This is because these motion events - // may cause a new window to be launched, which the user might expect to receive focus. - // To prevent waiting forever for such events, just send the key to the currently focused window - if (isPointerDownEvent && mKeyIsWaitingForEventsTimeout) { - ALOGD("Received a new pointer down event, stop waiting for events to process and " - "just send the pending key event to the focused window."); - mKeyIsWaitingForEventsTimeout = now(); - } return false; } @@ -1211,27 +1256,12 @@ bool InputDispatcher::enqueueInboundEventLocked(std::unique_ptr<EventEntry> newE case EventEntry::Type::KEY: { LOG_ALWAYS_FATAL_IF((entry.policyFlags & POLICY_FLAG_TRUSTED) == 0, "Unexpected untrusted event."); - // Optimize app switch latency. - // If the application takes too long to catch up then we drop all events preceding - // the app switch key. - const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry); - if (!REMOVE_APP_SWITCH_DROPS) { - if (isAppSwitchKeyEvent(keyEntry)) { - if (keyEntry.action == AKEY_EVENT_ACTION_DOWN) { - mAppSwitchSawKeyDown = true; - } else if (keyEntry.action == AKEY_EVENT_ACTION_UP) { - if (mAppSwitchSawKeyDown) { - if (DEBUG_APP_SWITCH) { - ALOGD("App switch is pending!"); - } - mAppSwitchDueTime = keyEntry.eventTime + APP_SWITCH_TIMEOUT; - mAppSwitchSawKeyDown = false; - needWake = true; - } - } - } + const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry); + if (mTracer) { + ensureEventTraced(keyEntry); } + // If a new up event comes in, and the pending event with same key code has been asked // to try again later because of the policy. We have to reset the intercept key wake up // time for it may have been handled in the policy and could be dropped. @@ -1252,10 +1282,28 @@ bool InputDispatcher::enqueueInboundEventLocked(std::unique_ptr<EventEntry> newE case EventEntry::Type::MOTION: { LOG_ALWAYS_FATAL_IF((entry.policyFlags & POLICY_FLAG_TRUSTED) == 0, "Unexpected untrusted event."); - if (shouldPruneInboundQueueLocked(static_cast<const MotionEntry&>(entry))) { + const auto& motionEntry = static_cast<const MotionEntry&>(entry); + if (mTracer) { + ensureEventTraced(motionEntry); + } + if (shouldPruneInboundQueueLocked(motionEntry)) { mNextUnblockedEvent = mInboundQueue.back(); needWake = true; } + + const bool isPointerDownEvent = motionEntry.action == AMOTION_EVENT_ACTION_DOWN && + isFromSource(motionEntry.source, AINPUT_SOURCE_CLASS_POINTER); + if (isPointerDownEvent && mKeyIsWaitingForEventsTimeout) { + // Prevent waiting too long for unprocessed events: if we have a pending key event, + // and some other events have not yet been processed, the dispatcher will wait for + // these events to be processed before dispatching the key event. This is because + // the unprocessed events may cause the focus to change (for example, by launching a + // new window or tapping a different window). To prevent waiting too long, we force + // the key to be sent to the currently focused window when a new tap comes in. + ALOGD("Received a new pointer down event, stop waiting for events to process and " + "just send the pending key event to the currently focused window."); + mKeyIsWaitingForEventsTimeout = now(); + } break; } case EventEntry::Type::FOCUS: { @@ -1367,13 +1415,10 @@ void InputDispatcher::dropInboundEventLocked(const EventEntry& entry, DropReason } reason = "inbound event was dropped because input dispatch is disabled"; break; - case DropReason::APP_SWITCH: - ALOGI("Dropped event because of pending overdue app switch."); - reason = "inbound event was dropped because of pending overdue app switch"; - break; case DropReason::BLOCKED: - ALOGI("Dropped event because the current application is not responding and the user " - "has started interacting with a different application."); + LOG(INFO) << "Dropping because the current application is not responding and the user " + "has started interacting with a different application: " + << entry.getDescription(); reason = "inbound event was dropped because the current application is not responding " "and the user has started interacting with a different application"; break; @@ -1433,33 +1478,6 @@ void InputDispatcher::dropInboundEventLocked(const EventEntry& entry, DropReason } } -static bool isAppSwitchKeyCode(int32_t keyCode) { - return keyCode == AKEYCODE_HOME || keyCode == AKEYCODE_ENDCALL || - keyCode == AKEYCODE_APP_SWITCH; -} - -bool InputDispatcher::isAppSwitchKeyEvent(const KeyEntry& keyEntry) { - return !(keyEntry.flags & AKEY_EVENT_FLAG_CANCELED) && isAppSwitchKeyCode(keyEntry.keyCode) && - (keyEntry.policyFlags & POLICY_FLAG_TRUSTED) && - (keyEntry.policyFlags & POLICY_FLAG_PASS_TO_USER); -} - -bool InputDispatcher::isAppSwitchPendingLocked() const { - return mAppSwitchDueTime != LLONG_MAX; -} - -void InputDispatcher::resetPendingAppSwitchLocked(bool handled) { - mAppSwitchDueTime = LLONG_MAX; - - if (DEBUG_APP_SWITCH) { - if (handled) { - ALOGD("App switch has arrived."); - } else { - ALOGD("App switch was abandoned."); - } - } -} - bool InputDispatcher::haveCommandsLocked() const { return !mCommandQueue.empty(); } @@ -1532,6 +1550,10 @@ std::shared_ptr<KeyEntry> InputDispatcher::synthesizeKeyRepeatLocked(nsecs_t cur entry->repeatCount + 1, entry->downTime); newEntry->syntheticRepeat = true; + if (mTracer) { + newEntry->traceTracker = mTracer->traceInboundEvent(*newEntry); + } + mKeyRepeatState.lastKeyEntry = newEntry; mKeyRepeatState.nextRepeatTime = currentTime + mConfig.keyRepeatDelay; return newEntry; @@ -1604,15 +1626,15 @@ void InputDispatcher::enqueueFocusEventLocked(const sp<IBinder>& windowToken, bo void InputDispatcher::dispatchFocusLocked(nsecs_t currentTime, std::shared_ptr<const FocusEntry> entry) { - std::shared_ptr<InputChannel> channel = getInputChannelLocked(entry->connectionToken); - if (channel == nullptr) { - return; // Window has gone away + std::shared_ptr<Connection> connection = getConnectionLocked(entry->connectionToken); + if (connection == nullptr) { + return; // Connection has gone away } InputTarget target; - target.inputChannel = channel; + target.connection = connection; entry->dispatchInProgress = true; std::string message = std::string("Focus ") + (entry->hasFocus ? "entering " : "leaving ") + - channel->getName(); + connection->getInputChannelName(); std::string reason = std::string("reason=").append(entry->reason); android_log_event_list(LOGTAG_INPUT_FOCUS) << message << reason << LOG_ID_EVENTS; dispatchEventLocked(currentTime, entry, {target}); @@ -1672,8 +1694,8 @@ void InputDispatcher::dispatchPointerCaptureChangedLocked( } } - auto channel = getInputChannelLocked(token); - if (channel == nullptr) { + auto connection = getConnectionLocked(token); + if (connection == nullptr) { // Window has gone away, clean up Pointer Capture state. mWindowTokenWithPointerCapture = nullptr; if (mCurrentPointerCaptureRequest.enable) { @@ -1682,7 +1704,7 @@ void InputDispatcher::dispatchPointerCaptureChangedLocked( return; } InputTarget target; - target.inputChannel = channel; + target.connection = connection; entry->dispatchInProgress = true; dispatchEventLocked(currentTime, entry, {target}); @@ -1713,19 +1735,19 @@ std::vector<InputTarget> InputDispatcher::getInputTargetsFromWindowHandlesLocked if (token == nullptr) { continue; } - std::shared_ptr<InputChannel> channel = getInputChannelLocked(token); - if (channel == nullptr) { - continue; // Window has gone away + std::shared_ptr<Connection> connection = getConnectionLocked(token); + if (connection == nullptr) { + continue; // Connection has gone away } InputTarget target; - target.inputChannel = channel; + target.connection = connection; inputTargets.push_back(target); } return inputTargets; } bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<const KeyEntry> entry, - DropReason* dropReason, nsecs_t* nextWakeupTime) { + DropReason* dropReason, nsecs_t& nextWakeupTime) { // Preprocessing. if (!entry->dispatchInProgress) { if (entry->repeatCount == 0 && entry->action == AKEY_EVENT_ACTION_DOWN && @@ -1776,9 +1798,7 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<con // Handle case where the policy asked us to try again later last time. if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) { if (currentTime < entry->interceptKeyWakeupTime) { - if (entry->interceptKeyWakeupTime < *nextWakeupTime) { - *nextWakeupTime = entry->interceptKeyWakeupTime; - } + nextWakeupTime = std::min(nextWakeupTime, entry->interceptKeyWakeupTime); return false; // wait until next wakeup } entry->interceptKeyResult = KeyEntry::InterceptKeyResult::UNKNOWN; @@ -1840,6 +1860,13 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<con // Add monitor channels from event's or focused display. addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry)); + if (mTracer) { + ensureEventTraced(*entry); + for (const auto& target : inputTargets) { + mTracer->dispatchToTargetHint(*entry->traceTracker, target); + } + } + // Dispatch the key. dispatchEventLocked(currentTime, entry, inputTargets); return true; @@ -1858,7 +1885,7 @@ void InputDispatcher::logOutboundKeyDetails(const char* prefix, const KeyEntry& void InputDispatcher::dispatchSensorLocked(nsecs_t currentTime, const std::shared_ptr<const SensorEntry>& entry, - DropReason* dropReason, nsecs_t* nextWakeupTime) { + DropReason* dropReason, nsecs_t& nextWakeupTime) { if (DEBUG_OUTBOUND_EVENT_DETAILS) { ALOGD("notifySensorEvent eventTime=%" PRId64 ", hwTimestamp=%" PRId64 ", deviceId=%d, " "source=0x%x, sensorType=%s", @@ -1898,7 +1925,7 @@ bool InputDispatcher::flushSensor(int deviceId, InputDeviceSensorType sensorType bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<const MotionEntry> entry, - DropReason* dropReason, nsecs_t* nextWakeupTime) { + DropReason* dropReason, nsecs_t& nextWakeupTime) { ATRACE_CALL(); // Preprocessing. if (!entry->dispatchInProgress) { @@ -1958,6 +1985,7 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, isPointerEvent ? CancelationOptions::Mode::CANCEL_POINTER_EVENTS : CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS); CancelationOptions options(mode, "input event injection failed"); + options.displayId = entry->displayId; synthesizeCancelationEventsForMonitorsLocked(options); return true; } @@ -1965,6 +1993,13 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, // Add monitor channels from event's or focused display. addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry)); + if (mTracer) { + ensureEventTraced(*entry); + for (const auto& target : inputTargets) { + mTracer->dispatchToTargetHint(*entry->traceTracker, target); + } + } + // Dispatch the motion. dispatchEventLocked(currentTime, entry, inputTargets); return true; @@ -1983,12 +2018,12 @@ void InputDispatcher::enqueueDragEventLocked(const sp<WindowInfoHandle>& windowH void InputDispatcher::dispatchDragLocked(nsecs_t currentTime, std::shared_ptr<const DragEntry> entry) { - std::shared_ptr<InputChannel> channel = getInputChannelLocked(entry->connectionToken); - if (channel == nullptr) { - return; // Window has gone away + std::shared_ptr<Connection> connection = getConnectionLocked(entry->connectionToken); + if (connection == nullptr) { + return; // Connection has gone away } InputTarget target; - target.inputChannel = channel; + target.connection = connection; entry->dispatchInProgress = true; dispatchEventLocked(currentTime, entry, {target}); } @@ -2041,17 +2076,8 @@ void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, pokeUserActivityLocked(*eventEntry); for (const InputTarget& inputTarget : inputTargets) { - std::shared_ptr<Connection> connection = - getConnectionLocked(inputTarget.inputChannel->getConnectionToken()); - if (connection != nullptr) { - prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget); - } else { - if (DEBUG_DROPPED_EVENTS_VERBOSE) { - LOG(INFO) << "Dropping event delivery to target with channel " - << inputTarget.inputChannel->getName() - << " because it is no longer registered with the input dispatcher."; - } - } + std::shared_ptr<Connection> connection = inputTarget.connection; + prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget); } } @@ -2062,12 +2088,24 @@ void InputDispatcher::cancelEventsForAnrLocked(const std::shared_ptr<Connection> // sending new pointers to the connection when it blocked, but focused events will continue to // pile up. ALOGW("Canceling events for %s because it is unresponsive", - connection->inputChannel->getName().c_str()); - if (connection->status == Connection::Status::NORMAL) { - CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, - "application not responding"); - synthesizeCancelationEventsForConnectionLocked(connection, options); + connection->getInputChannelName().c_str()); + if (connection->status != Connection::Status::NORMAL) { + return; + } + CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, + "application not responding"); + + sp<WindowInfoHandle> windowHandle; + if (!connection->monitor) { + windowHandle = getWindowHandleLocked(connection->getToken()); + if (windowHandle == nullptr) { + // The window that is receiving this ANR was removed, so there is no need to generate + // cancellations, because the cancellations would have already been generated when + // the window was removed. + return; + } } + synthesizeCancelationEventsForConnectionLocked(connection, options, windowHandle); } void InputDispatcher::resetNoFocusedWindowTimeoutLocked() { @@ -2124,7 +2162,8 @@ bool InputDispatcher::shouldWaitToSendKeyLocked(nsecs_t currentTime, // Start the timer // Wait to send key because there are unprocessed events that may cause focus to change mKeyIsWaitingForEventsTimeout = currentTime + - std::chrono::duration_cast<std::chrono::nanoseconds>(KEY_WAITING_FOR_EVENTS_TIMEOUT) + std::chrono::duration_cast<std::chrono::nanoseconds>( + mPolicy.getKeyWaitingForEventsTimeout()) .count(); return true; } @@ -2143,7 +2182,7 @@ bool InputDispatcher::shouldWaitToSendKeyLocked(nsecs_t currentTime, } sp<WindowInfoHandle> InputDispatcher::findFocusedWindowTargetLocked( - nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime, + nsecs_t currentTime, const EventEntry& entry, nsecs_t& nextWakeupTime, InputEventInjectionResult& outInjectionResult) { outInjectionResult = InputEventInjectionResult::FAILED; // Default result @@ -2182,7 +2221,7 @@ sp<WindowInfoHandle> InputDispatcher::findFocusedWindowTargetLocked( ALOGW("Waiting because no window has focus but %s may eventually add a " "window when it finishes starting up. Will wait for %" PRId64 "ms", mAwaitedFocusedApplication->getName().c_str(), millis(timeout)); - *nextWakeupTime = *mNoFocusedWindowTimeoutTime; + nextWakeupTime = std::min(nextWakeupTime, *mNoFocusedWindowTimeoutTime); outInjectionResult = InputEventInjectionResult::PENDING; return nullptr; } else if (currentTime > *mNoFocusedWindowTimeoutTime) { @@ -2227,7 +2266,7 @@ sp<WindowInfoHandle> InputDispatcher::findFocusedWindowTargetLocked( // prior input events. if (entry.type == EventEntry::Type::KEY) { if (shouldWaitToSendKeyLocked(currentTime, focusedWindowHandle->getName().c_str())) { - *nextWakeupTime = *mKeyIsWaitingForEventsTimeout; + nextWakeupTime = std::min(nextWakeupTime, *mKeyIsWaitingForEventsTimeout); outInjectionResult = InputEventInjectionResult::PENDING; return nullptr; } @@ -2245,17 +2284,11 @@ std::vector<Monitor> InputDispatcher::selectResponsiveMonitorsLocked( const std::vector<Monitor>& monitors) const { std::vector<Monitor> responsiveMonitors; std::copy_if(monitors.begin(), monitors.end(), std::back_inserter(responsiveMonitors), - [this](const Monitor& monitor) REQUIRES(mLock) { - std::shared_ptr<Connection> connection = - getConnectionLocked(monitor.inputChannel->getConnectionToken()); - if (connection == nullptr) { - ALOGE("Could not find connection for monitor %s", - monitor.inputChannel->getName().c_str()); - return false; - } + [](const Monitor& monitor) REQUIRES(mLock) { + std::shared_ptr<Connection> connection = monitor.connection; if (!connection->responsive) { ALOGW("Unresponsive monitor %s will not get the new gesture", - connection->inputChannel->getName().c_str()); + connection->getInputChannelName().c_str()); return false; } return true; @@ -2329,7 +2362,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( /* Case 1: New splittable pointer going down, or need target for hover or scroll. */ const auto [x, y] = resolveTouchedPosition(entry); const int32_t pointerIndex = MotionEvent::getActionIndex(action); - const int32_t pointerId = entry.pointerProperties[pointerIndex].id; + const PointerProperties& pointer = entry.pointerProperties[pointerIndex]; // Outside targets should be added upon first dispatched DOWN event. That means, this should // be a pointer that would generate ACTION_DOWN, *and* touch should not already be down. const bool isStylus = isPointerFromStylus(entry, pointerIndex); @@ -2337,7 +2370,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( findTouchedWindowAtLocked(displayId, x, y, isStylus); if (isDown) { - targets += findOutsideTargetsLocked(displayId, newTouchedWindowHandle, pointerId); + targets += findOutsideTargetsLocked(displayId, newTouchedWindowHandle, pointer.id); } // Handle the case where we did not find a window. if (newTouchedWindowHandle == nullptr) { @@ -2395,7 +2428,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( if (isHoverAction) { // The "windowHandle" is the target of this hovering pointer. - tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointerId); + tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointer); } // Set target flags. @@ -2418,12 +2451,10 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( // Update the temporary touch state. if (!isHoverAction) { - std::bitset<MAX_POINTER_ID + 1> pointerIds; - pointerIds.set(pointerId); const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN || maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN; tempTouchState.addOrUpdateWindow(windowHandle, InputTarget::DispatchMode::AS_IS, - targetFlags, entry.deviceId, pointerIds, + targetFlags, entry.deviceId, {pointer}, isDownOrPointerDown ? std::make_optional(entry.eventTime) : std::nullopt); @@ -2446,7 +2477,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( } tempTouchState.addOrUpdateWindow(wallpaper, InputTarget::DispatchMode::AS_IS, - wallpaperFlags, entry.deviceId, pointerIds, + wallpaperFlags, entry.deviceId, {pointer}, entry.eventTime); } } @@ -2457,12 +2488,12 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( // make it pilfering. This will prevent other non-spy windows from getting this pointer, // which is a specific behaviour that we want. for (TouchedWindow& touchedWindow : tempTouchState.windows) { - if (touchedWindow.hasTouchingPointer(entry.deviceId, pointerId) && + if (touchedWindow.hasTouchingPointer(entry.deviceId, pointer.id) && touchedWindow.hasPilferingPointers(entry.deviceId)) { // This window is already pilfering some pointers, and this new pointer is also // going to it. Therefore, take over this pointer and don't give it to anyone // else. - touchedWindow.addPilferingPointer(entry.deviceId, pointerId); + touchedWindow.addPilferingPointer(entry.deviceId, pointer.id); } } @@ -2531,8 +2562,8 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( // Make a slippery exit from the old window. std::bitset<MAX_POINTER_ID + 1> pointerIds; - const int32_t pointerId = entry.pointerProperties[0].id; - pointerIds.set(pointerId); + const PointerProperties& pointer = entry.pointerProperties[0]; + pointerIds.set(pointer.id); const TouchedWindow& touchedWindow = tempTouchState.getTouchedWindow(oldTouchedWindowHandle); @@ -2562,13 +2593,13 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, InputTarget::DispatchMode::SLIPPERY_ENTER, - targetFlags, entry.deviceId, pointerIds, + targetFlags, entry.deviceId, {pointer}, entry.eventTime); // Check if the wallpaper window should deliver the corresponding event. slipWallpaperTouch(targetFlags, oldTouchedWindowHandle, newTouchedWindowHandle, - tempTouchState, entry.deviceId, pointerId, targets); - tempTouchState.removeTouchingPointerFromWindow(entry.deviceId, pointerId, + tempTouchState, entry.deviceId, pointer, targets); + tempTouchState.removeTouchingPointerFromWindow(entry.deviceId, pointer.id, oldTouchedWindowHandle); } } @@ -2577,14 +2608,12 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( if (!isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) { // If no split, we suppose all touched windows should receive pointer down. const int32_t pointerIndex = MotionEvent::getActionIndex(action); - for (size_t i = 0; i < tempTouchState.windows.size(); i++) { - TouchedWindow& touchedWindow = tempTouchState.windows[i]; + std::vector<PointerProperties> touchingPointers{entry.pointerProperties[pointerIndex]}; + for (TouchedWindow& touchedWindow : tempTouchState.windows) { // Ignore drag window for it should just track one pointer. if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) { continue; } - std::bitset<MAX_POINTER_ID + 1> touchingPointers; - touchingPointers.set(entry.pointerProperties[pointerIndex].id); touchedWindow.addTouchingPointers(entry.deviceId, touchingPointers); } } @@ -2636,7 +2665,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( for (InputTarget& target : targets) { if (target.dispatchMode == InputTarget::DispatchMode::OUTSIDE) { sp<WindowInfoHandle> targetWindow = - getWindowHandleLocked(target.inputChannel->getConnectionToken()); + getWindowHandleLocked(target.connection->getToken()); if (targetWindow->getInfo()->ownerUid != foregroundWindowUid) { target.flags |= InputTarget::Flags::ZERO_COORDS; } @@ -2655,13 +2684,13 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( // Output targets from the touch state. for (const TouchedWindow& touchedWindow : tempTouchState.windows) { - std::bitset<MAX_POINTER_ID + 1> touchingPointers = + std::vector<PointerProperties> touchingPointers = touchedWindow.getTouchingPointers(entry.deviceId); - if (touchingPointers.none()) { + if (touchingPointers.empty()) { continue; } addPointerWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.dispatchMode, - touchedWindow.targetFlags, touchingPointers, + touchedWindow.targetFlags, getPointerIds(touchingPointers), touchedWindow.getDownTimeInTarget(entry.deviceId), targets); } @@ -2834,13 +2863,13 @@ std::optional<InputTarget> InputDispatcher::createInputTargetLocked( const sp<android::gui::WindowInfoHandle>& windowHandle, InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags, std::optional<nsecs_t> firstDownTimeInTarget) const { - std::shared_ptr<InputChannel> inputChannel = getInputChannelLocked(windowHandle->getToken()); - if (inputChannel == nullptr) { + std::shared_ptr<Connection> connection = getConnectionLocked(windowHandle->getToken()); + if (connection == nullptr) { ALOGW("Not creating InputTarget for %s, no input channel", windowHandle->getName().c_str()); return {}; } InputTarget inputTarget; - inputTarget.inputChannel = inputChannel; + inputTarget.connection = connection; inputTarget.windowHandle = windowHandle; inputTarget.dispatchMode = dispatchMode; inputTarget.flags = targetFlags; @@ -2864,8 +2893,7 @@ void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHa std::vector<InputTarget>::iterator it = std::find_if(inputTargets.begin(), inputTargets.end(), [&windowHandle](const InputTarget& inputTarget) { - return inputTarget.inputChannel->getConnectionToken() == - windowHandle->getToken(); + return inputTarget.connection->getToken() == windowHandle->getToken(); }); const WindowInfo* windowInfo = windowHandle->getInfo(); @@ -2905,8 +2933,7 @@ void InputDispatcher::addPointerWindowTargetLocked( std::vector<InputTarget>::iterator it = std::find_if(inputTargets.begin(), inputTargets.end(), [&windowHandle](const InputTarget& inputTarget) { - return inputTarget.inputChannel->getConnectionToken() == - windowHandle->getToken(); + return inputTarget.connection->getToken() == windowHandle->getToken(); }); // This is a hack, because the actual entry could potentially be an ACTION_DOWN event that @@ -2956,7 +2983,7 @@ void InputDispatcher::addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& for (const Monitor& monitor : selectResponsiveMonitorsLocked(monitorsIt->second)) { InputTarget target; - target.inputChannel = monitor.inputChannel; + target.connection = monitor.connection; // target.firstDownTimeInTarget is not set for global monitors. It is only required in split // touch and global monitoring works as intended even without setting firstDownTimeInTarget if (const auto& it = mDisplayInfos.find(displayId); it != mDisplayInfos.end()) { @@ -3158,6 +3185,21 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) { // Not poking user activity if the event type does not represent a user activity return; } + + const int32_t eventType = getUserActivityEventType(eventEntry); + if (input_flags::rate_limit_user_activity_poke_in_dispatcher()) { + // Note that we're directly getting the time diff between the current event and the previous + // event. This is assuming that the first user event always happens at a timestamp that is + // greater than `mMinTimeBetweenUserActivityPokes` (otherwise, the first user event will + // wrongly be dropped). In real life, `mMinTimeBetweenUserActivityPokes` is a much smaller + // value than the potential first user activity event time, so this is ok. + std::chrono::nanoseconds timeSinceLastEvent = + std::chrono::nanoseconds(eventEntry.eventTime - mLastUserActivityTimes[eventType]); + if (timeSinceLastEvent < mMinTimeBetweenUserActivityPokes) { + return; + } + } + int32_t displayId = getTargetDisplayId(eventEntry); sp<WindowInfoHandle> focusedWindowHandle = getFocusedWindowHandleLocked(displayId); const WindowInfo* windowDisablingUserActivityInfo = nullptr; @@ -3168,7 +3210,6 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) { } } - int32_t eventType = USER_ACTIVITY_EVENT_OTHER; switch (eventEntry.type) { case EventEntry::Type::MOTION: { const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry); @@ -3182,9 +3223,6 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) { } return; } - if (MotionEvent::isTouchEvent(motionEntry.source, motionEntry.action)) { - eventType = USER_ACTIVITY_EVENT_TOUCH; - } break; } case EventEntry::Type::KEY: { @@ -3208,7 +3246,6 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) { return; } - eventType = USER_ACTIVITY_EVENT_BUTTON; break; } default: { @@ -3218,6 +3255,7 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) { } } + mLastUserActivityTimes[eventType] = eventEntry.eventTime; auto command = [this, eventTime = eventEntry.eventTime, eventType, displayId]() REQUIRES(mLock) { scoped_unlock unlock(mLock); @@ -3316,10 +3354,17 @@ void InputDispatcher::enqueueDispatchEntryAndStartDispatchCycleLocked( void InputDispatcher::enqueueDispatchEntryLocked(const std::shared_ptr<Connection>& connection, std::shared_ptr<const EventEntry> eventEntry, const InputTarget& inputTarget) { + const bool isKeyOrMotion = eventEntry->type == EventEntry::Type::KEY || + eventEntry->type == EventEntry::Type::MOTION; + if (isKeyOrMotion && !inputTarget.windowHandle && !connection->monitor) { + LOG(FATAL) << "All InputTargets for non-monitors must be associated with a window; target: " + << inputTarget << " connection: " << connection->getInputChannelName() + << " entry: " << eventEntry->getDescription(); + } // This is a new event. // Enqueue a new dispatch entry onto the outbound queue for this connection. std::unique_ptr<DispatchEntry> dispatchEntry = - createDispatchEntry(inputTarget, eventEntry, inputTarget.flags); + createDispatchEntry(inputTarget, eventEntry, inputTarget.flags, mWindowInfosVsyncId); // Use the eventEntry from dispatchEntry since the entry may have changed and can now be a // different EventEntry than what was passed in. @@ -3377,30 +3422,46 @@ void InputDispatcher::enqueueDispatchEntryLocked(const std::shared_ptr<Connectio InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED)) { resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; } + if (dispatchEntry->targetFlags.test(InputTarget::Flags::NO_FOCUS_CHANGE)) { + resolvedFlags |= AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE; + } dispatchEntry->resolvedFlags = resolvedFlags; if (resolvedAction != motionEntry.action) { + std::optional<std::vector<PointerProperties>> usingProperties; + std::optional<std::vector<PointerCoords>> usingCoords; + if (resolvedAction == AMOTION_EVENT_ACTION_HOVER_EXIT || + resolvedAction == AMOTION_EVENT_ACTION_CANCEL) { + // This is a HOVER_EXIT or an ACTION_CANCEL event that was synthesized by + // the dispatcher, and therefore the coordinates of this event are currently + // incorrect. These events should use the coordinates of the last dispatched + // ACTION_MOVE or HOVER_MOVE. We need to query InputState to get this data. + const bool hovering = resolvedAction == AMOTION_EVENT_ACTION_HOVER_EXIT; + std::optional<std::pair<std::vector<PointerProperties>, + std::vector<PointerCoords>>> + pointerInfo = + connection->inputState.getPointersOfLastEvent(motionEntry, + hovering); + if (pointerInfo) { + usingProperties = pointerInfo->first; + usingCoords = pointerInfo->second; + } + } // Generate a new MotionEntry with a new eventId using the resolved action and // flags. - resolvedMotion = - std::make_shared<MotionEntry>(mIdGenerator.nextId(), - motionEntry.injectionState, - motionEntry.eventTime, - motionEntry.deviceId, motionEntry.source, - motionEntry.displayId, - motionEntry.policyFlags, resolvedAction, - motionEntry.actionButton, resolvedFlags, - motionEntry.metaState, - motionEntry.buttonState, - motionEntry.classification, - motionEntry.edgeFlags, - motionEntry.xPrecision, - motionEntry.yPrecision, - motionEntry.xCursorPosition, - motionEntry.yCursorPosition, - motionEntry.downTime, - motionEntry.pointerProperties, - motionEntry.pointerCoords); + resolvedMotion = std::make_shared< + MotionEntry>(mIdGenerator.nextId(), motionEntry.injectionState, + motionEntry.eventTime, motionEntry.deviceId, + motionEntry.source, motionEntry.displayId, + motionEntry.policyFlags, resolvedAction, + motionEntry.actionButton, resolvedFlags, + motionEntry.metaState, motionEntry.buttonState, + motionEntry.classification, motionEntry.edgeFlags, + motionEntry.xPrecision, motionEntry.yPrecision, + motionEntry.xCursorPosition, motionEntry.yCursorPosition, + motionEntry.downTime, + usingProperties.value_or(motionEntry.pointerProperties), + usingCoords.value_or(motionEntry.pointerCoords)); if (ATRACE_ENABLED()) { std::string message = StringPrintf("Transmute MotionEvent(id=0x%" PRIx32 ") to MotionEvent(id=0x%" PRIx32 ").", @@ -3425,7 +3486,7 @@ void InputDispatcher::enqueueDispatchEntryLocked(const std::shared_ptr<Connectio << cancelEvent->getDescription(); std::unique_ptr<DispatchEntry> cancelDispatchEntry = createDispatchEntry(inputTarget, std::move(cancelEvent), - ftl::Flags<InputTarget::Flags>()); + ftl::Flags<InputTarget::Flags>(), mWindowInfosVsyncId); // Send these cancel events to the queue before sending the event from the new // device. @@ -3438,15 +3499,14 @@ void InputDispatcher::enqueueDispatchEntryLocked(const std::shared_ptr<Connectio << "~ dropping inconsistent event: " << *dispatchEntry; return; // skip the inconsistent event } - - if ((resolvedMotion->flags & AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE) && + if ((dispatchEntry->resolvedFlags & AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE) && (resolvedMotion->policyFlags & POLICY_FLAG_TRUSTED)) { // Skip reporting pointer down outside focus to the policy. break; } dispatchPointerDownOutsideFocus(resolvedMotion->source, resolvedMotion->action, - inputTarget.inputChannel->getConnectionToken()); + inputTarget.connection->getToken()); break; } @@ -3529,13 +3589,9 @@ void InputDispatcher::processInteractionsLocked(const EventEntry& entry, continue; // Skip windows that receive ACTION_OUTSIDE } - sp<IBinder> token = target.inputChannel->getConnectionToken(); - std::shared_ptr<Connection> connection = getConnectionLocked(token); - if (connection == nullptr) { - continue; - } + sp<IBinder> token = target.connection->getToken(); newConnectionTokens.insert(std::move(token)); - newConnections.emplace_back(connection); + newConnections.emplace_back(target.connection); if (target.windowHandle) { interactionUids.emplace(target.windowHandle->getInfo()->ownerUid); } @@ -3555,7 +3611,7 @@ void InputDispatcher::processInteractionsLocked(const EventEntry& entry, std::string targetList; for (const std::shared_ptr<Connection>& connection : newConnections) { - targetList += connection->getWindowName() + ", "; + targetList += connection->getInputChannelName() + ", "; } std::string message = "Interaction with: " + targetList; if (targetList.empty()) { @@ -3593,6 +3649,7 @@ status_t InputDispatcher::publishMotionEvent(Connection& connection, PointerCoords scaledCoords[MAX_POINTERS]; const PointerCoords* usingCoords = motionEntry.pointerCoords.data(); + // TODO(b/316355518): Do not modify coords before dispatch. // Set the X and Y offset and X and Y scale depending on the input source. if ((motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) && !(dispatchEntry.targetFlags.test(InputTarget::Flags::ZERO_COORDS))) { @@ -3668,6 +3725,9 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, keyEntry.keyCode, keyEntry.scanCode, keyEntry.metaState, keyEntry.repeatCount, keyEntry.downTime, keyEntry.eventTime); + if (mTracer) { + mTracer->traceEventDispatch(*dispatchEntry, keyEntry.traceTracker.get()); + } break; } @@ -3676,7 +3736,11 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, LOG(INFO) << "Publishing " << *dispatchEntry << " to " << connection->getInputChannelName(); } + const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry); status = publishMotionEvent(*connection, *dispatchEntry); + if (mTracer) { + mTracer->traceEventDispatch(*dispatchEntry, motionEntry.traceTracker.get()); + } break; } @@ -3761,7 +3825,7 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, connection->outboundQueue.erase(connection->outboundQueue.begin()); traceOutboundQueueLength(*connection); if (connection->responsive) { - mAnrTracker.insert(timeoutTime, connection->inputChannel->getConnectionToken()); + mAnrTracker.insert(timeoutTime, connection->getToken()); } traceWaitQueueLength(*connection); } @@ -3814,8 +3878,7 @@ void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, connection->getInputChannelName().c_str(), seq, toString(handled)); } - if (connection->status == Connection::Status::BROKEN || - connection->status == Connection::Status::ZOMBIE) { + if (connection->status != Connection::Status::NORMAL) { return; } @@ -3852,7 +3915,7 @@ void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime, auto command = [this, connection]() REQUIRES(mLock) { scoped_unlock unlock(mLock); - mPolicy.notifyInputChannelBroken(connection->inputChannel->getConnectionToken()); + mPolicy.notifyInputChannelBroken(connection->getToken()); }; postCommandLocked(std::move(command)); } @@ -3910,10 +3973,9 @@ int InputDispatcher::handleReceiveCallback(int events, sp<IBinder> connectionTok if (shouldReportMetricsForConnection(*connection)) { const InputPublisher::Timeline& timeline = std::get<InputPublisher::Timeline>(*result); - mLatencyTracker - .trackGraphicsLatency(timeline.inputEventId, - connection->inputChannel->getConnectionToken(), - std::move(timeline.graphicsTimeline)); + mLatencyTracker.trackGraphicsLatency(timeline.inputEventId, + connection->getToken(), + std::move(timeline.graphicsTimeline)); } } gotOne = true; @@ -3934,8 +3996,7 @@ int InputDispatcher::handleReceiveCallback(int events, sp<IBinder> connectionTok } else { // Monitor channels are never explicitly unregistered. // We do it automatically when the remote endpoint is closed so don't warn about them. - const bool stillHaveWindowHandle = - getWindowHandleLocked(connection->inputChannel->getConnectionToken()) != nullptr; + const bool stillHaveWindowHandle = getWindowHandleLocked(connection->getToken()) != nullptr; notify = !connection->monitor && stillHaveWindowHandle; if (notify) { ALOGW("channel '%s' ~ Consumer closed input channel or an error occurred. events=0x%x", @@ -3944,39 +4005,85 @@ int InputDispatcher::handleReceiveCallback(int events, sp<IBinder> connectionTok } // Remove the channel. - removeInputChannelLocked(connection->inputChannel->getConnectionToken(), notify); + removeInputChannelLocked(connection->getToken(), notify); return 0; // remove the callback } void InputDispatcher::synthesizeCancelationEventsForAllConnectionsLocked( const CancelationOptions& options) { - for (const auto& [token, connection] : mConnectionsByToken) { - synthesizeCancelationEventsForConnectionLocked(connection, options); + // Cancel windows (i.e. non-monitors). + // A channel must have at least one window to receive any input. If a window was removed, the + // event streams directed to the window will already have been canceled during window removal. + // So there is no need to generate cancellations for connections without any windows. + const auto [cancelPointers, cancelNonPointers] = expandCancellationMode(options.mode); + // Generate cancellations for touched windows first. This is to avoid generating cancellations + // through a non-touched window if there are more than one window for an input channel. + if (cancelPointers) { + for (const auto& [displayId, touchState] : mTouchStatesByDisplay) { + if (options.displayId.has_value() && options.displayId != displayId) { + continue; + } + for (const auto& touchedWindow : touchState.windows) { + synthesizeCancelationEventsForWindowLocked(touchedWindow.windowHandle, options); + } + } } + // Follow up by generating cancellations for all windows, because we don't explicitly track + // the windows that have an ongoing focus event stream. + if (cancelNonPointers) { + for (const auto& [_, handles] : mWindowHandlesByDisplay) { + for (const auto& windowHandle : handles) { + synthesizeCancelationEventsForWindowLocked(windowHandle, options); + } + } + } + + // Cancel monitors. + synthesizeCancelationEventsForMonitorsLocked(options); } void InputDispatcher::synthesizeCancelationEventsForMonitorsLocked( const CancelationOptions& options) { for (const auto& [_, monitors] : mGlobalMonitorsByDisplay) { for (const Monitor& monitor : monitors) { - synthesizeCancelationEventsForInputChannelLocked(monitor.inputChannel, options); + synthesizeCancelationEventsForConnectionLocked(monitor.connection, options, + /*window=*/nullptr); } } } -void InputDispatcher::synthesizeCancelationEventsForInputChannelLocked( - const std::shared_ptr<InputChannel>& channel, const CancelationOptions& options) { - std::shared_ptr<Connection> connection = getConnectionLocked(channel->getConnectionToken()); - if (connection == nullptr) { - return; +void InputDispatcher::synthesizeCancelationEventsForWindowLocked( + const sp<WindowInfoHandle>& windowHandle, const CancelationOptions& options, + const std::shared_ptr<Connection>& connection) { + if (windowHandle == nullptr) { + LOG(FATAL) << __func__ << ": Window handle must not be null"; + } + if (connection) { + // The connection can be optionally provided to avoid multiple lookups. + if (windowHandle->getToken() != connection->getToken()) { + LOG(FATAL) << __func__ + << ": Wrong connection provided for window: " << windowHandle->getName(); + } } - synthesizeCancelationEventsForConnectionLocked(connection, options); + std::shared_ptr<Connection> resolvedConnection = + connection ? connection : getConnectionLocked(windowHandle->getToken()); + if (!resolvedConnection) { + LOG(DEBUG) << __func__ << "No connection found for window: " << windowHandle->getName(); + return; + } + synthesizeCancelationEventsForConnectionLocked(resolvedConnection, options, windowHandle); } void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( - const std::shared_ptr<Connection>& connection, const CancelationOptions& options) { - if (connection->status == Connection::Status::BROKEN) { + const std::shared_ptr<Connection>& connection, const CancelationOptions& options, + const sp<WindowInfoHandle>& window) { + if (!connection->monitor && window == nullptr) { + LOG(FATAL) << __func__ + << ": Cannot send event to non-monitor channel without a window - channel: " + << connection->getInputChannelName(); + } + if (connection->status != Connection::Status::NORMAL) { return; } @@ -4002,8 +4109,8 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( const bool wasEmpty = connection->outboundQueue.empty(); // The target to use if we don't find a window associated with the channel. - const InputTarget fallbackTarget{.inputChannel = connection->inputChannel}; - const auto& token = connection->inputChannel->getConnectionToken(); + const InputTarget fallbackTarget{.connection = connection}; + const auto& token = connection->getToken(); for (size_t i = 0; i < cancelationEvents.size(); i++) { std::unique_ptr<EventEntry> cancelationEventEntry = std::move(cancelationEvents[i]); @@ -4012,10 +4119,7 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( switch (cancelationEventEntry->type) { case EventEntry::Type::KEY: { const auto& keyEntry = static_cast<const KeyEntry&>(*cancelationEventEntry); - const std::optional<int32_t> targetDisplay = keyEntry.displayId != ADISPLAY_ID_NONE - ? std::make_optional(keyEntry.displayId) - : std::nullopt; - if (const auto& window = getWindowHandleLocked(token, targetDisplay); window) { + if (window) { addWindowTargetLocked(window, InputTarget::DispatchMode::AS_IS, /*targetFlags=*/{}, keyEntry.downTime, targets); } else { @@ -4026,11 +4130,7 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( } case EventEntry::Type::MOTION: { const auto& motionEntry = static_cast<const MotionEntry&>(*cancelationEventEntry); - const std::optional<int32_t> targetDisplay = - motionEntry.displayId != ADISPLAY_ID_NONE - ? std::make_optional(motionEntry.displayId) - : std::nullopt; - if (const auto& window = getWindowHandleLocked(token, targetDisplay); window) { + if (window) { std::bitset<MAX_POINTER_ID + 1> pointerIds; for (uint32_t pointerIndex = 0; pointerIndex < motionEntry.getPointerCount(); pointerIndex++) { @@ -4088,7 +4188,7 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( const nsecs_t downTime, const std::shared_ptr<Connection>& connection, ftl::Flags<InputTarget::Flags> targetFlags) { - if (connection->status == Connection::Status::BROKEN) { + if (connection->status != Connection::Status::NORMAL) { return; } @@ -4104,8 +4204,12 @@ void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( connection->getInputChannelName().c_str(), downEvents.size()); } - sp<WindowInfoHandle> windowHandle = - getWindowHandleLocked(connection->inputChannel->getConnectionToken()); + const auto [_, touchedWindowState, displayId] = + findTouchStateWindowAndDisplayLocked(connection->getToken()); + if (touchedWindowState == nullptr) { + LOG(FATAL) << __func__ << ": Touch state is out of sync: No touched window for token"; + } + const auto& windowHandle = touchedWindowState->windowHandle; const bool wasEmpty = connection->outboundQueue.empty(); for (std::unique_ptr<EventEntry>& downEventEntry : downEvents) { @@ -4123,8 +4227,8 @@ void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( targetFlags, pointerIds, motionEntry.downTime, targets); } else { - targets.emplace_back(InputTarget{.inputChannel = connection->inputChannel, - .flags = targetFlags}); + targets.emplace_back( + InputTarget{.connection = connection, .flags = targetFlags}); const auto it = mDisplayInfos.find(motionEntry.displayId); if (it != mDisplayInfos.end()) { targets.back().displayTransform = it->second.transform; @@ -4159,17 +4263,6 @@ void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( } } -void InputDispatcher::synthesizeCancelationEventsForWindowLocked( - const sp<WindowInfoHandle>& windowHandle, const CancelationOptions& options) { - if (windowHandle != nullptr) { - std::shared_ptr<Connection> wallpaperConnection = - getConnectionLocked(windowHandle->getToken()); - if (wallpaperConnection != nullptr) { - synthesizeCancelationEventsForConnectionLocked(wallpaperConnection, options); - } - } -} - std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent( const MotionEntry& originalMotionEntry, std::bitset<MAX_POINTER_ID + 1> pointerIds, nsecs_t splitDownTime) { @@ -4359,6 +4452,9 @@ void InputDispatcher::notifyKey(const NotifyKeyArgs& args) { args.deviceId, args.source, args.displayId, policyFlags, args.action, flags, keyCode, args.scanCode, metaState, repeatCount, args.downTime); + if (mTracer) { + newEntry->traceTracker = mTracer->traceInboundEvent(*newEntry); + } needWake = enqueueInboundEventLocked(std::move(newEntry)); mLock.unlock(); @@ -4427,7 +4523,8 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs& args) { policyFlags |= POLICY_FLAG_TRUSTED; android::base::Timer t; - mPolicy.interceptMotionBeforeQueueing(args.displayId, args.eventTime, policyFlags); + mPolicy.interceptMotionBeforeQueueing(args.displayId, args.source, args.action, args.eventTime, + policyFlags); if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) { ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms", std::to_string(t.duration().count()).c_str()); @@ -4484,6 +4581,9 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs& args) { args.yPrecision, args.xCursorPosition, args.yCursorPosition, args.downTime, args.pointerProperties, args.pointerCoords); + if (mTracer) { + newEntry->traceTracker = mTracer->traceInboundEvent(*newEntry); + } if (args.id != android::os::IInputConstants::INVALID_INPUT_EVENT_ID && IdGenerator::getSource(args.id) == IdGenerator::Source::INPUT_READER && @@ -4671,6 +4771,9 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev incomingKey.getScanCode(), metaState, incomingKey.getRepeatCount(), incomingKey.getDownTime()); + if (mTracer) { + injectedEntry->traceTracker = mTracer->traceInboundEvent(*injectedEntry); + } injectedEntries.push(std::move(injectedEntry)); break; } @@ -4688,7 +4791,9 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev if (!(policyFlags & POLICY_FLAG_FILTERED)) { nsecs_t eventTime = motionEvent.getEventTime(); android::base::Timer t; - mPolicy.interceptMotionBeforeQueueing(displayId, eventTime, /*byref*/ policyFlags); + mPolicy.interceptMotionBeforeQueueing(displayId, motionEvent.getSource(), + motionEvent.getAction(), eventTime, + /*byref*/ policyFlags); if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) { ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms", std::to_string(t.duration().count()).c_str()); @@ -4726,6 +4831,9 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev samplePointerCoords + pointerCount)); transformMotionEntryForInjectionLocked(*injectedEntry, motionEvent.getTransform()); + if (mTracer) { + injectedEntry->traceTracker = mTracer->traceInboundEvent(*injectedEntry); + } injectedEntries.push(std::move(injectedEntry)); for (size_t i = motionEvent.getHistorySize(); i > 0; i--) { sampleEventTimes += 1; @@ -4848,7 +4956,7 @@ std::unique_ptr<VerifiedInputEvent> InputDispatcher::verifyInputEvent(const Inpu break; } default: { - ALOGE("Cannot verify events of type %" PRId32, event.getType()); + LOG(ERROR) << "Cannot verify events of type " << ftl::enum_string(event.getType()); return nullptr; } } @@ -5056,16 +5164,14 @@ bool InputDispatcher::canWindowReceiveMotionLocked(const sp<WindowInfoHandle>& w return false; } - return true; -} - -std::shared_ptr<InputChannel> InputDispatcher::getInputChannelLocked( - const sp<IBinder>& token) const { - auto connectionIt = mConnectionsByToken.find(token); - if (connectionIt == mConnectionsByToken.end()) { - return nullptr; + // Ignore touches if stylus is down anywhere on screen + if (info.inputConfig.test(WindowInfo::InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH) && + isStylusActiveInDisplay(info.displayId, mTouchStatesByDisplay)) { + LOG(INFO) << "Dropping touch from " << window->getName() << " because stylus is active"; + return false; } - return connectionIt->second->inputChannel; + + return true; } void InputDispatcher::updateWindowHandlesForDisplayLocked( @@ -5087,7 +5193,7 @@ void InputDispatcher::updateWindowHandlesForDisplayLocked( std::vector<sp<WindowInfoHandle>> newHandles; for (const sp<WindowInfoHandle>& handle : windowInfoHandles) { const WindowInfo* info = handle->getInfo(); - if (getInputChannelLocked(handle->getToken()) == nullptr) { + if (getConnectionLocked(handle->getToken()) == nullptr) { const bool noInputChannel = info->inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL); const bool canReceiveInput = @@ -5166,6 +5272,7 @@ void InputDispatcher::setInputWindowsLocked( // Copy old handles for release if they are no longer present. const std::vector<sp<WindowInfoHandle>> oldWindowHandles = getWindowHandlesLocked(displayId); + const sp<WindowInfoHandle> removedFocusedWindowHandle = getFocusedWindowHandleLocked(displayId); updateWindowHandlesForDisplayLocked(windowInfoHandles, displayId); @@ -5174,7 +5281,7 @@ void InputDispatcher::setInputWindowsLocked( std::optional<FocusResolver::FocusChanges> changes = mFocusResolver.setInputWindows(displayId, windowHandles); if (changes) { - onFocusChangedLocked(*changes); + onFocusChangedLocked(*changes, removedFocusedWindowHandle); } std::unordered_map<int32_t, TouchState>::iterator stateIt = @@ -5186,19 +5293,16 @@ void InputDispatcher::setInputWindowsLocked( if (getWindowHandleLocked(touchedWindow.windowHandle) == nullptr) { LOG(INFO) << "Touched window was removed: " << touchedWindow.windowHandle->getName() << " in display %" << displayId; - std::shared_ptr<InputChannel> touchedInputChannel = - getInputChannelLocked(touchedWindow.windowHandle->getToken()); - if (touchedInputChannel != nullptr) { - CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, - "touched window was removed"); - synthesizeCancelationEventsForInputChannelLocked(touchedInputChannel, options); - // Since we are about to drop the touch, cancel the events for the wallpaper as - // well. - if (touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND) && - touchedWindow.windowHandle->getInfo()->inputConfig.test( - gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) { - sp<WindowInfoHandle> wallpaper = state.getWallpaperWindow(); - synthesizeCancelationEventsForWindowLocked(wallpaper, options); + CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, + "touched window was removed"); + synthesizeCancelationEventsForWindowLocked(touchedWindow.windowHandle, options); + // Since we are about to drop the touch, cancel the events for the wallpaper as + // well. + if (touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND) && + touchedWindow.windowHandle->getInfo()->inputConfig.test( + gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) { + if (const auto& ww = state.getWallpaperWindow(); ww) { + synthesizeCancelationEventsForWindowLocked(ww, options); } } state.windows.erase(state.windows.begin() + i); @@ -5268,6 +5372,14 @@ void InputDispatcher::setFocusedApplicationLocked( resetNoFocusedWindowTimeoutLocked(); } +void InputDispatcher::setMinTimeBetweenUserActivityPokes(std::chrono::milliseconds interval) { + if (interval.count() < 0) { + LOG_ALWAYS_FATAL("Minimum time between user activity pokes should be >= 0"); + } + std::scoped_lock _l(mLock); + mMinTimeBetweenUserActivityPokes = interval; +} + /** * Sets the focused display, which is responsible for receiving focus-dispatched input events where * the display not specified. @@ -5288,15 +5400,16 @@ void InputDispatcher::setFocusedDisplay(int32_t displayId) { sp<IBinder> oldFocusedWindowToken = mFocusResolver.getFocusedWindowToken(mFocusedDisplayId); if (oldFocusedWindowToken != nullptr) { - std::shared_ptr<InputChannel> inputChannel = - getInputChannelLocked(oldFocusedWindowToken); - if (inputChannel != nullptr) { - CancelationOptions - options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, - "The display which contains this window no longer has focus."); - options.displayId = ADISPLAY_ID_NONE; - synthesizeCancelationEventsForInputChannelLocked(inputChannel, options); + const auto windowHandle = + getWindowHandleLocked(oldFocusedWindowToken, mFocusedDisplayId); + if (windowHandle == nullptr) { + LOG(FATAL) << __func__ << ": Previously focused token did not have a window"; } + CancelationOptions + options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, + "The display which contains this window no longer has focus."); + options.displayId = ADISPLAY_ID_NONE; + synthesizeCancelationEventsForWindowLocked(windowHandle, options); } mFocusedDisplayId = displayId; @@ -5476,9 +5589,10 @@ bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp< } const int32_t deviceId = *deviceIds.begin(); - sp<WindowInfoHandle> toWindowHandle = getWindowHandleLocked(toToken, displayId); - if (toWindowHandle == nullptr) { - ALOGW("Cannot transfer touch because to window not found."); + const sp<WindowInfoHandle> fromWindowHandle = touchedWindow->windowHandle; + const sp<WindowInfoHandle> toWindowHandle = getWindowHandleLocked(toToken, displayId); + if (!toWindowHandle) { + ALOGW("Cannot transfer touch because the transfer target window was not found."); return false; } @@ -5490,8 +5604,7 @@ bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp< // Erase old window. ftl::Flags<InputTarget::Flags> oldTargetFlags = touchedWindow->targetFlags; - std::bitset<MAX_POINTER_ID + 1> pointerIds = touchedWindow->getTouchingPointers(deviceId); - sp<WindowInfoHandle> fromWindowHandle = touchedWindow->windowHandle; + std::vector<PointerProperties> pointers = touchedWindow->getTouchingPointers(deviceId); state->removeWindowByToken(fromToken); // Add new window. @@ -5501,18 +5614,20 @@ bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp< if (canReceiveForegroundTouches(*toWindowHandle->getInfo())) { newTargetFlags |= InputTarget::Flags::FOREGROUND; } + // Transferring touch focus using this API should not effect the focused window. + newTargetFlags |= InputTarget::Flags::NO_FOCUS_CHANGE; state->addOrUpdateWindow(toWindowHandle, InputTarget::DispatchMode::AS_IS, newTargetFlags, - deviceId, pointerIds, downTimeInTarget); + deviceId, pointers, downTimeInTarget); // Store the dragging window. if (isDragDrop) { - if (pointerIds.count() != 1) { + if (pointers.size() != 1) { ALOGW("The drag and drop cannot be started when there is no pointer or more than 1" " pointer on the window."); return false; } // Track the pointer id for drag window and generate the drag state. - const size_t id = firstMarkedBit(pointerIds); + const size_t id = pointers.begin()->id; mDragState = std::make_unique<DragState>(toWindowHandle, id); } @@ -5523,13 +5638,13 @@ bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp< fromConnection->inputState.mergePointerStateTo(toConnection->inputState); CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, "transferring touch from this window to another window"); - synthesizeCancelationEventsForConnectionLocked(fromConnection, options); + synthesizeCancelationEventsForWindowLocked(fromWindowHandle, options, fromConnection); synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection, newTargetFlags); // Check if the wallpaper window should deliver the corresponding event. transferWallpaperTouch(oldTargetFlags, newTargetFlags, fromWindowHandle, toWindowHandle, - *state, deviceId, pointerIds); + *state, deviceId, pointers); } } // release lock @@ -5758,11 +5873,10 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) const { if (!mConnectionsByToken.empty()) { dump += INDENT "Connections:\n"; for (const auto& [token, connection] : mConnectionsByToken) { - dump += StringPrintf(INDENT2 "%i: channelName='%s', windowName='%s', " + dump += StringPrintf(INDENT2 "%i: channelName='%s', " "status=%s, monitor=%s, responsive=%s\n", - connection->inputChannel->getFd().get(), + connection->inputPublisher.getChannel().getFd(), connection->getInputChannelName().c_str(), - connection->getWindowName().c_str(), ftl::enum_string(connection->status).c_str(), toString(connection->monitor), toString(connection->responsive)); @@ -5792,16 +5906,6 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) const { dump += INDENT "Connections: <none>\n"; } - dump += "input_flags::remove_app_switch_drops() = "; - dump += toString(REMOVE_APP_SWITCH_DROPS); - dump += "\n"; - if (isAppSwitchPendingLocked()) { - dump += StringPrintf(INDENT "AppSwitch: pending, due in %" PRId64 "ms\n", - ns2ms(mAppSwitchDueTime - now())); - } else { - dump += INDENT "AppSwitch: not pending\n"; - } - if (!mTouchModePerDisplay.empty()) { dump += INDENT "TouchModePerDisplay:\n"; for (const auto& [displayId, touchMode] : mTouchModePerDisplay) { @@ -5818,14 +5922,16 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) const { ns2ms(mConfig.keyRepeatTimeout)); dump += mLatencyTracker.dump(INDENT2); dump += mLatencyAggregator.dump(INDENT2); + dump += INDENT "InputTracer: "; + dump += mTracer == nullptr ? "Disabled" : "Enabled"; } void InputDispatcher::dumpMonitors(std::string& dump, const std::vector<Monitor>& monitors) const { const size_t numMonitors = monitors.size(); for (size_t i = 0; i < numMonitors; i++) { const Monitor& monitor = monitors[i]; - const std::shared_ptr<InputChannel>& channel = monitor.inputChannel; - dump += StringPrintf(INDENT2 "%zu: '%s', ", i, channel->getName().c_str()); + const std::shared_ptr<Connection>& connection = monitor.connection; + dump += StringPrintf(INDENT2 "%zu: '%s', ", i, connection->getInputChannelName().c_str()); dump += "\n"; } } @@ -5855,20 +5961,20 @@ Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputChannel(const { // acquire lock std::scoped_lock _l(mLock); const sp<IBinder>& token = serverChannel->getConnectionToken(); - auto&& fd = serverChannel->getFd(); + const int fd = serverChannel->getFd(); std::shared_ptr<Connection> connection = std::make_shared<Connection>(std::move(serverChannel), /*monitor=*/false, mIdGenerator); - if (mConnectionsByToken.find(token) != mConnectionsByToken.end()) { + auto [_, inserted] = mConnectionsByToken.try_emplace(token, connection); + if (!inserted) { ALOGE("Created a new connection, but the token %p is already known", token.get()); } - mConnectionsByToken.emplace(token, connection); std::function<int(int events)> callback = std::bind(&InputDispatcher::handleReceiveCallback, this, std::placeholders::_1, token); - mLooper->addFd(fd.get(), 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback), + mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback), nullptr); } // release lock @@ -5880,9 +5986,9 @@ Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputChannel(const Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputMonitor(int32_t displayId, const std::string& name, gui::Pid pid) { - std::shared_ptr<InputChannel> serverChannel; + std::unique_ptr<InputChannel> serverChannel; std::unique_ptr<InputChannel> clientChannel; - status_t result = openInputChannelPair(name, serverChannel, clientChannel); + status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel); if (result) { return base::Error(result) << "Failed to open input channel pair with name " << name; } @@ -5895,21 +6001,23 @@ Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputMonitor(int32_ << " without a specified display."; } - std::shared_ptr<Connection> connection = - std::make_shared<Connection>(serverChannel, /*monitor=*/true, mIdGenerator); const sp<IBinder>& token = serverChannel->getConnectionToken(); - auto&& fd = serverChannel->getFd(); + const int fd = serverChannel->getFd(); + std::shared_ptr<Connection> connection = + std::make_shared<Connection>(std::move(serverChannel), /*monitor=*/true, + mIdGenerator); - if (mConnectionsByToken.find(token) != mConnectionsByToken.end()) { + auto [_, inserted] = mConnectionsByToken.emplace(token, connection); + if (!inserted) { ALOGE("Created a new connection, but the token %p is already known", token.get()); } - mConnectionsByToken.emplace(token, connection); + std::function<int(int events)> callback = std::bind(&InputDispatcher::handleReceiveCallback, this, std::placeholders::_1, token); - mGlobalMonitorsByDisplay[displayId].emplace_back(serverChannel, pid); + mGlobalMonitorsByDisplay[displayId].emplace_back(connection, pid); - mLooper->addFd(fd.get(), 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback), + mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback), nullptr); } @@ -5948,7 +6056,7 @@ status_t InputDispatcher::removeInputChannelLocked(const sp<IBinder>& connection removeMonitorChannelLocked(connectionToken); } - mLooper->removeFd(connection->inputChannel->getFd().get()); + mLooper->removeFd(connection->inputPublisher.getChannel().getFd()); nsecs_t currentTime = now(); abortBrokenDispatchCycleLocked(currentTime, connection, notify); @@ -5961,7 +6069,7 @@ void InputDispatcher::removeMonitorChannelLocked(const sp<IBinder>& connectionTo for (auto it = mGlobalMonitorsByDisplay.begin(); it != mGlobalMonitorsByDisplay.end();) { auto& [displayId, monitors] = *it; std::erase_if(monitors, [connectionToken](const Monitor& monitor) { - return monitor.inputChannel->getConnectionToken() == connectionToken; + return monitor.connection->getToken() == connectionToken; }); if (monitors.empty()) { @@ -5978,8 +6086,8 @@ status_t InputDispatcher::pilferPointers(const sp<IBinder>& token) { } status_t InputDispatcher::pilferPointersLocked(const sp<IBinder>& token) { - const std::shared_ptr<InputChannel> requestingChannel = getInputChannelLocked(token); - if (!requestingChannel) { + const std::shared_ptr<Connection> requestingConnection = getConnectionLocked(token); + if (!requestingConnection) { LOG(WARNING) << "Attempted to pilfer pointers from an un-registered channel or invalid token"; return BAD_VALUE; @@ -6006,20 +6114,20 @@ status_t InputDispatcher::pilferPointersLocked(const sp<IBinder>& token) { "input channel stole pointer stream"); options.deviceId = deviceId; options.displayId = displayId; - std::bitset<MAX_POINTER_ID + 1> pointerIds = window.getTouchingPointers(deviceId); + std::vector<PointerProperties> pointers = window.getTouchingPointers(deviceId); + std::bitset<MAX_POINTER_ID + 1> pointerIds = getPointerIds(pointers); options.pointerIds = pointerIds; + std::string canceledWindows; for (const TouchedWindow& w : state.windows) { - const std::shared_ptr<InputChannel> channel = - getInputChannelLocked(w.windowHandle->getToken()); - if (channel != nullptr && channel->getConnectionToken() != token) { - synthesizeCancelationEventsForInputChannelLocked(channel, options); + if (w.windowHandle->getToken() != token) { + synthesizeCancelationEventsForWindowLocked(w.windowHandle, options); canceledWindows += canceledWindows.empty() ? "[" : ", "; - canceledWindows += channel->getName(); + canceledWindows += w.windowHandle->getName(); } } canceledWindows += canceledWindows.empty() ? "[]" : "]"; - LOG(INFO) << "Channel " << requestingChannel->getName() + LOG(INFO) << "Channel " << requestingConnection->getInputChannelName() << " is stealing input gesture for device " << deviceId << " from " << canceledWindows; @@ -6085,7 +6193,7 @@ void InputDispatcher::setDisplayEligibilityForPointerCapture(int32_t displayId, std::optional<gui::Pid> InputDispatcher::findMonitorPidByTokenLocked(const sp<IBinder>& token) { for (const auto& [_, monitors] : mGlobalMonitorsByDisplay) { for (const Monitor& monitor : monitors) { - if (monitor.inputChannel->getConnectionToken() == token) { + if (monitor.connection->getToken() == token) { return monitor.pid; } } @@ -6117,8 +6225,8 @@ std::string InputDispatcher::getConnectionNameLocked(const sp<IBinder>& connecti } void InputDispatcher::removeConnectionLocked(const std::shared_ptr<Connection>& connection) { - mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken()); - mConnectionsByToken.erase(connection->inputChannel->getConnectionToken()); + mAnrTracker.eraseToken(connection->getToken()); + mConnectionsByToken.erase(connection->getToken()); } void InputDispatcher::doDispatchCycleFinishedCommand(nsecs_t finishTime, @@ -6140,12 +6248,11 @@ void InputDispatcher::doDispatchCycleFinishedCommand(nsecs_t finishTime, const nsecs_t eventDuration = finishTime - dispatchEntry.deliveryTime; if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) { - ALOGI("%s spent %" PRId64 "ms processing %s", connection->getWindowName().c_str(), + ALOGI("%s spent %" PRId64 "ms processing %s", connection->getInputChannelName().c_str(), ns2ms(eventDuration), dispatchEntry.eventEntry->getDescription().c_str()); } if (shouldReportFinishedEvent(dispatchEntry, *connection)) { - mLatencyTracker.trackFinishedEvent(dispatchEntry.eventEntry->id, - connection->inputChannel->getConnectionToken(), + mLatencyTracker.trackFinishedEvent(dispatchEntry.eventEntry->id, connection->getToken(), dispatchEntry.deliveryTime, consumeTime, finishTime); } @@ -6166,7 +6273,7 @@ void InputDispatcher::doDispatchCycleFinishedCommand(nsecs_t finishTime, std::unique_ptr<DispatchEntry> dispatchEntry = std::move(*entryIt); connection->waitQueue.erase(entryIt); - const sp<IBinder>& connectionToken = connection->inputChannel->getConnectionToken(); + const sp<IBinder>& connectionToken = connection->getToken(); mAnrTracker.erase(dispatchEntry->timeoutTime, connectionToken); if (!connection->responsive) { connection->responsive = isConnectionResponsive(*connection); @@ -6177,9 +6284,18 @@ void InputDispatcher::doDispatchCycleFinishedCommand(nsecs_t finishTime, } traceWaitQueueLength(*connection); if (fallbackKeyEntry && connection->status == Connection::Status::NORMAL) { - const InputTarget target{.inputChannel = connection->inputChannel, - .flags = dispatchEntry->targetFlags}; - enqueueDispatchEntryLocked(connection, std::move(fallbackKeyEntry), target); + const auto windowHandle = getWindowHandleLocked(connection->getToken()); + // Only dispatch fallbacks if there is a window for the connection. + if (windowHandle != nullptr) { + const auto inputTarget = + createInputTargetLocked(windowHandle, InputTarget::DispatchMode::AS_IS, + dispatchEntry->targetFlags, + fallbackKeyEntry->downTime); + if (inputTarget.has_value()) { + enqueueDispatchEntryLocked(connection, std::move(fallbackKeyEntry), + *inputTarget); + } + } } releaseDispatchEntry(std::move(dispatchEntry)); } @@ -6213,7 +6329,7 @@ void InputDispatcher::onAnrLocked(const std::shared_ptr<Connection>& connection) // is already healthy again. Don't raise ANR in this situation if (connection->waitQueue.empty()) { ALOGI("Not raising ANR because the connection %s has recovered", - connection->inputChannel->getName().c_str()); + connection->getInputChannelName().c_str()); return; } /** @@ -6228,10 +6344,10 @@ void InputDispatcher::onAnrLocked(const std::shared_ptr<Connection>& connection) const nsecs_t currentWait = now() - oldestEntry.deliveryTime; std::string reason = android::base::StringPrintf("%s is not responding. Waited %" PRId64 "ms for %s", - connection->inputChannel->getName().c_str(), + connection->getInputChannelName().c_str(), ns2ms(currentWait), oldestEntry.eventEntry->getDescription().c_str()); - sp<IBinder> connectionToken = connection->inputChannel->getConnectionToken(); + sp<IBinder> connectionToken = connection->getToken(); updateLastAnrStateLocked(getWindowHandleLocked(connectionToken), reason); processConnectionUnresponsiveLocked(*connection, std::move(reason)); @@ -6330,15 +6446,15 @@ void InputDispatcher::sendWindowResponsiveCommandLocked(const sp<IBinder>& token */ void InputDispatcher::processConnectionUnresponsiveLocked(const Connection& connection, std::string reason) { - const sp<IBinder>& connectionToken = connection.inputChannel->getConnectionToken(); + const sp<IBinder>& connectionToken = connection.getToken(); std::optional<gui::Pid> pid; if (connection.monitor) { - ALOGW("Monitor %s is unresponsive: %s", connection.inputChannel->getName().c_str(), + ALOGW("Monitor %s is unresponsive: %s", connection.getInputChannelName().c_str(), reason.c_str()); pid = findMonitorPidByTokenLocked(connectionToken); } else { // The connection is a window - ALOGW("Window %s is unresponsive: %s", connection.inputChannel->getName().c_str(), + ALOGW("Window %s is unresponsive: %s", connection.getInputChannelName().c_str(), reason.c_str()); const sp<WindowInfoHandle> handle = getWindowHandleLocked(connectionToken); if (handle != nullptr) { @@ -6352,7 +6468,7 @@ void InputDispatcher::processConnectionUnresponsiveLocked(const Connection& conn * Tell the policy that a connection has become responsive so that it can stop ANR. */ void InputDispatcher::processConnectionResponsiveLocked(const Connection& connection) { - const sp<IBinder>& connectionToken = connection.inputChannel->getConnectionToken(); + const sp<IBinder>& connectionToken = connection.getToken(); std::optional<gui::Pid> pid; if (connection.monitor) { pid = findMonitorPidByTokenLocked(connectionToken); @@ -6403,22 +6519,26 @@ std::unique_ptr<const KeyEntry> InputDispatcher::afterKeyEventLockedInterruptabl mLock.unlock(); if (const auto unhandledKeyFallback = - mPolicy.dispatchUnhandledKey(connection->inputChannel->getConnectionToken(), - event, keyEntry.policyFlags); + mPolicy.dispatchUnhandledKey(connection->getToken(), event, + keyEntry.policyFlags); unhandledKeyFallback) { event = *unhandledKeyFallback; } mLock.lock(); - // Cancel the fallback key. + // Cancel the fallback key, but only if we still have a window for the channel. + // It could have been removed during the policy call. if (*fallbackKeyCode != AKEYCODE_UNKNOWN) { - CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS, - "application handled the original non-fallback key " - "or is no longer a foreground target, " - "canceling previously dispatched fallback key"); - options.keyCode = *fallbackKeyCode; - synthesizeCancelationEventsForConnectionLocked(connection, options); + const auto windowHandle = getWindowHandleLocked(connection->getToken()); + if (windowHandle != nullptr) { + CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS, + "application handled the original non-fallback key " + "or is no longer a foreground target, " + "canceling previously dispatched fallback key"); + options.keyCode = *fallbackKeyCode; + synthesizeCancelationEventsForWindowLocked(windowHandle, options, connection); + } } connection->inputState.removeFallbackKey(originalKeyCode); } @@ -6448,8 +6568,8 @@ std::unique_ptr<const KeyEntry> InputDispatcher::afterKeyEventLockedInterruptabl mLock.unlock(); bool fallback = false; - if (auto fb = mPolicy.dispatchUnhandledKey(connection->inputChannel->getConnectionToken(), - event, keyEntry.policyFlags); + if (auto fb = mPolicy.dispatchUnhandledKey(connection->getToken(), event, + keyEntry.policyFlags); fb) { fallback = true; event = *fb; @@ -6494,10 +6614,13 @@ std::unique_ptr<const KeyEntry> InputDispatcher::afterKeyEventLockedInterruptabl } } - CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS, - "canceling fallback, policy no longer desires it"); - options.keyCode = *fallbackKeyCode; - synthesizeCancelationEventsForConnectionLocked(connection, options); + const auto windowHandle = getWindowHandleLocked(connection->getToken()); + if (windowHandle != nullptr) { + CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS, + "canceling fallback, policy no longer desires it"); + options.keyCode = *fallbackKeyCode; + synthesizeCancelationEventsForWindowLocked(windowHandle, options, connection); + } fallback = false; *fallbackKeyCode = AKEYCODE_UNKNOWN; @@ -6557,7 +6680,8 @@ void InputDispatcher::traceInboundQueueLengthLocked() { void InputDispatcher::traceOutboundQueueLength(const Connection& connection) { if (ATRACE_ENABLED()) { char counterName[40]; - snprintf(counterName, sizeof(counterName), "oq:%s", connection.getWindowName().c_str()); + snprintf(counterName, sizeof(counterName), "oq:%s", + connection.getInputChannelName().c_str()); ATRACE_INT(counterName, connection.outboundQueue.size()); } } @@ -6565,7 +6689,8 @@ void InputDispatcher::traceOutboundQueueLength(const Connection& connection) { void InputDispatcher::traceWaitQueueLength(const Connection& connection) { if (ATRACE_ENABLED()) { char counterName[40]; - snprintf(counterName, sizeof(counterName), "wq:%s", connection.getWindowName().c_str()); + snprintf(counterName, sizeof(counterName), "wq:%s", + connection.getInputChannelName().c_str()); ATRACE_INT(counterName, connection.waitQueue.size()); } } @@ -6634,15 +6759,19 @@ void InputDispatcher::setFocusedWindow(const FocusRequest& request) { mLooper->wake(); } -void InputDispatcher::onFocusChangedLocked(const FocusResolver::FocusChanges& changes) { +void InputDispatcher::onFocusChangedLocked(const FocusResolver::FocusChanges& changes, + const sp<WindowInfoHandle> removedFocusedWindowHandle) { if (changes.oldFocus) { - std::shared_ptr<InputChannel> focusedInputChannel = getInputChannelLocked(changes.oldFocus); - if (focusedInputChannel) { - CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, - "focus left window"); - synthesizeCancelationEventsForInputChannelLocked(focusedInputChannel, options); - enqueueFocusEventLocked(changes.oldFocus, /*hasFocus=*/false, changes.reason); + const auto resolvedWindow = removedFocusedWindowHandle != nullptr + ? removedFocusedWindowHandle + : getWindowHandleLocked(changes.oldFocus, changes.displayId); + if (resolvedWindow == nullptr) { + LOG(FATAL) << __func__ << ": Previously focused token did not have a window"; } + CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, + "focus left window"); + synthesizeCancelationEventsForWindowLocked(resolvedWindow, options); + enqueueFocusEventLocked(changes.oldFocus, /*hasFocus=*/false, changes.reason); } if (changes.newFocus) { resetNoFocusedWindowTimeoutLocked(); @@ -6723,6 +6852,15 @@ void InputDispatcher::displayRemoved(int32_t displayId) { } void InputDispatcher::onWindowInfosChanged(const gui::WindowInfosUpdate& update) { + if (auto result = validateWindowInfosUpdate(update); !result.ok()) { + { + // acquire lock + std::scoped_lock _l(mLock); + logDispatchStateLocked(); + } + LOG_ALWAYS_FATAL("Incorrect WindowInfosUpdate provided: %s", + result.error().message().c_str()); + }; // The listener sends the windows as a flattened array. Separate the windows by display for // more convenient parsing. std::unordered_map<int32_t, std::vector<sp<WindowInfoHandle>>> handlesPerDisplay; @@ -6803,10 +6941,10 @@ void InputDispatcher::setMonitorDispatchingTimeoutForTest(std::chrono::nanosecon void InputDispatcher::slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags, const sp<WindowInfoHandle>& oldWindowHandle, const sp<WindowInfoHandle>& newWindowHandle, - TouchState& state, int32_t deviceId, int32_t pointerId, + TouchState& state, int32_t deviceId, + const PointerProperties& pointerProperties, std::vector<InputTarget>& targets) const { - std::bitset<MAX_POINTER_ID + 1> pointerIds; - pointerIds.set(pointerId); + std::vector<PointerProperties> pointers{pointerProperties}; const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test( gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER); const bool newHasWallpaper = targetFlags.test(InputTarget::Flags::FOREGROUND) && @@ -6823,16 +6961,16 @@ void InputDispatcher::slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFl if (oldWallpaper != nullptr) { const TouchedWindow& oldTouchedWindow = state.getTouchedWindow(oldWallpaper); addPointerWindowTargetLocked(oldWallpaper, InputTarget::DispatchMode::SLIPPERY_EXIT, - oldTouchedWindow.targetFlags, pointerIds, + oldTouchedWindow.targetFlags, getPointerIds(pointers), oldTouchedWindow.getDownTimeInTarget(deviceId), targets); - state.removeTouchingPointerFromWindow(deviceId, pointerId, oldWallpaper); + state.removeTouchingPointerFromWindow(deviceId, pointerProperties.id, oldWallpaper); } if (newWallpaper != nullptr) { state.addOrUpdateWindow(newWallpaper, InputTarget::DispatchMode::SLIPPERY_ENTER, InputTarget::Flags::WINDOW_IS_OBSCURED | InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED, - deviceId, pointerIds); + deviceId, pointers); } } @@ -6841,7 +6979,7 @@ void InputDispatcher::transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldT const sp<WindowInfoHandle> fromWindowHandle, const sp<WindowInfoHandle> toWindowHandle, TouchState& state, int32_t deviceId, - std::bitset<MAX_POINTER_ID + 1> pointerIds) { + const std::vector<PointerProperties>& pointers) { const bool oldHasWallpaper = oldTargetFlags.test(InputTarget::Flags::FOREGROUND) && fromWindowHandle->getInfo()->inputConfig.test( gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER); @@ -6866,11 +7004,12 @@ void InputDispatcher::transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldT if (newWallpaper != nullptr) { nsecs_t downTimeInTarget = now(); - ftl::Flags<InputTarget::Flags> wallpaperFlags = oldTargetFlags & InputTarget::Flags::SPLIT; + ftl::Flags<InputTarget::Flags> wallpaperFlags = newTargetFlags; + wallpaperFlags |= oldTargetFlags & InputTarget::Flags::SPLIT; wallpaperFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED | InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED; state.addOrUpdateWindow(newWallpaper, InputTarget::DispatchMode::AS_IS, wallpaperFlags, - deviceId, pointerIds, downTimeInTarget); + deviceId, pointers, downTimeInTarget); std::shared_ptr<Connection> wallpaperConnection = getConnectionLocked(newWallpaper->getToken()); if (wallpaperConnection != nullptr) { diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 3f99b2d40e..f2fd0cab71 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -32,6 +32,8 @@ #include "Monitor.h" #include "TouchState.h" #include "TouchedWindow.h" +#include "trace/InputTracerInterface.h" +#include "trace/InputTracingBackendInterface.h" #include <attestation/HmacKeyManager.h> #include <gui/InputApplication.h> @@ -39,6 +41,7 @@ #include <input/Input.h> #include <input/InputTransport.h> #include <limits.h> +#include <powermanager/PowerManager.h> #include <stddef.h> #include <unistd.h> #include <utils/BitSet.h> @@ -82,6 +85,9 @@ public: static constexpr bool kDefaultInTouchMode = true; explicit InputDispatcher(InputDispatcherPolicyInterface& policy); + // Constructor used for testing. + explicit InputDispatcher(InputDispatcherPolicyInterface&, + std::unique_ptr<trace::InputTracingBackendInterface>); ~InputDispatcher() override; void dump(std::string& dump) const override; @@ -111,6 +117,7 @@ public: int32_t displayId, const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) override; void setFocusedDisplay(int32_t displayId) override; + void setMinTimeBetweenUserActivityPokes(std::chrono::milliseconds interval) override; void setInputDispatchMode(bool enabled, bool frozen) override; void setInputFilterEnabled(bool enabled) override; bool setInTouchMode(bool inTouchMode, gui::Pid pid, gui::Uid uid, bool hasPermission, @@ -155,7 +162,6 @@ private: enum class DropReason { NOT_DROPPED, POLICY, - APP_SWITCH, DISABLED, BLOCKED, STALE, @@ -172,6 +178,9 @@ private: std::condition_variable mDispatcherIsAlive; mutable std::condition_variable mDispatcherEnteredIdle; + // Input event tracer. The tracer will only exist on builds where input tracing is allowed. + std::unique_ptr<trace::InputTracerInterface> mTracer GUARDED_BY(mLock); + sp<Looper> mLooper; std::shared_ptr<const EventEntry> mPendingEvent GUARDED_BY(mLock); @@ -204,12 +213,17 @@ private: int64_t mWindowInfosVsyncId GUARDED_BY(mLock); + std::chrono::milliseconds mMinTimeBetweenUserActivityPokes GUARDED_BY(mLock); + + /** Stores the latest user-activity poke event times per user activity types. */ + std::array<nsecs_t, USER_ACTIVITY_EVENT_LAST + 1> mLastUserActivityTimes GUARDED_BY(mLock); + // With each iteration, InputDispatcher nominally processes one queued event, // a timeout, or a response from an input consumer. // This method should only be called on the input dispatcher's own thread. void dispatchOnce(); - void dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) REQUIRES(mLock); + void dispatchOnceInnerLocked(nsecs_t& nextWakeupTime) REQUIRES(mLock); // Enqueues an inbound event. Returns true if mLooper->wake() should be called. bool enqueueInboundEventLocked(std::unique_ptr<EventEntry> entry) REQUIRES(mLock); @@ -228,14 +242,6 @@ private: // Adds an event to a queue of recent events for debugging purposes. void addRecentEventLocked(std::shared_ptr<const EventEntry> entry) REQUIRES(mLock); - // App switch latency optimization. - bool mAppSwitchSawKeyDown GUARDED_BY(mLock); - nsecs_t mAppSwitchDueTime GUARDED_BY(mLock); - - bool isAppSwitchKeyEvent(const KeyEntry& keyEntry); - bool isAppSwitchPendingLocked() const REQUIRES(mLock); - void resetPendingAppSwitchLocked(bool handled) REQUIRES(mLock); - // Blocked event latency optimization. Drops old events when the user intends // to transfer focus to a new application. std::shared_ptr<const EventEntry> mNextUnblockedEvent GUARDED_BY(mLock); @@ -363,8 +369,6 @@ private: REQUIRES(mLock); sp<android::gui::WindowInfoHandle> getWindowHandleLocked( const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock); - std::shared_ptr<InputChannel> getInputChannelLocked(const sp<IBinder>& windowToken) const - REQUIRES(mLock); sp<android::gui::WindowInfoHandle> getFocusedWindowHandleLocked(int displayId) const REQUIRES(mLock); bool canWindowReceiveMotionLocked(const sp<android::gui::WindowInfoHandle>& window, @@ -435,9 +439,9 @@ private: bool dispatchDeviceResetLocked(nsecs_t currentTime, const DeviceResetEntry& entry) REQUIRES(mLock); bool dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<const KeyEntry> entry, - DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock); + DropReason* dropReason, nsecs_t& nextWakeupTime) REQUIRES(mLock); bool dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<const MotionEntry> entry, - DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock); + DropReason* dropReason, nsecs_t& nextWakeupTime) REQUIRES(mLock); void dispatchFocusLocked(nsecs_t currentTime, std::shared_ptr<const FocusEntry> entry) REQUIRES(mLock); void dispatchPointerCaptureChangedLocked( @@ -449,7 +453,7 @@ private: void dispatchEventLocked(nsecs_t currentTime, std::shared_ptr<const EventEntry> entry, const std::vector<InputTarget>& inputTargets) REQUIRES(mLock); void dispatchSensorLocked(nsecs_t currentTime, const std::shared_ptr<const SensorEntry>& entry, - DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock); + DropReason* dropReason, nsecs_t& nextWakeupTime) REQUIRES(mLock); void dispatchDragLocked(nsecs_t currentTime, std::shared_ptr<const DragEntry> entry) REQUIRES(mLock); void logOutboundKeyDetails(const char* prefix, const KeyEntry& entry); @@ -466,7 +470,7 @@ private: bool isStaleEvent(nsecs_t currentTime, const EventEntry& entry); - bool shouldPruneInboundQueueLocked(const MotionEntry& motionEntry) REQUIRES(mLock); + bool shouldPruneInboundQueueLocked(const MotionEntry& motionEntry) const REQUIRES(mLock); /** * Time to stop waiting for the events to be processed while trying to dispatch a key. @@ -521,7 +525,7 @@ private: int32_t getTargetDisplayId(const EventEntry& entry); sp<android::gui::WindowInfoHandle> findFocusedWindowTargetLocked( - nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime, + nsecs_t currentTime, const EventEntry& entry, nsecs_t& nextWakeupTime, android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock); std::vector<InputTarget> findTouchedWindowTargetsLocked( nsecs_t currentTime, const MotionEntry& entry, @@ -611,21 +615,21 @@ private: REQUIRES(mLock); void synthesizeCancelationEventsForMonitorsLocked(const CancelationOptions& options) REQUIRES(mLock); - void synthesizeCancelationEventsForInputChannelLocked( - const std::shared_ptr<InputChannel>& channel, const CancelationOptions& options) + void synthesizeCancelationEventsForWindowLocked(const sp<gui::WindowInfoHandle>&, + const CancelationOptions&, + const std::shared_ptr<Connection>& = nullptr) REQUIRES(mLock); + // This is a convenience function used to generate cancellation for a connection without having + // to check whether it's a monitor or a window. For non-monitors, the window handle must not be + // null. Always prefer the "-ForWindow" method above when explicitly dealing with windows. void synthesizeCancelationEventsForConnectionLocked( - const std::shared_ptr<Connection>& connection, const CancelationOptions& options) - REQUIRES(mLock); + const std::shared_ptr<Connection>& connection, const CancelationOptions& options, + const sp<gui::WindowInfoHandle>& window) REQUIRES(mLock); void synthesizePointerDownEventsForConnectionLocked( const nsecs_t downTime, const std::shared_ptr<Connection>& connection, ftl::Flags<InputTarget::Flags> targetFlags) REQUIRES(mLock); - void synthesizeCancelationEventsForWindowLocked( - const sp<android::gui::WindowInfoHandle>& windowHandle, - const CancelationOptions& options) REQUIRES(mLock); - // 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, @@ -652,7 +656,9 @@ private: bool handled, nsecs_t consumeTime) REQUIRES(mLock); void doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken, const KeyEntry& entry) REQUIRES(mLock); - void onFocusChangedLocked(const FocusResolver::FocusChanges& changes) REQUIRES(mLock); + void onFocusChangedLocked(const FocusResolver::FocusChanges& changes, + const sp<gui::WindowInfoHandle> removedFocusedWindowHandle = nullptr) + REQUIRES(mLock); void sendFocusChangedCommandLocked(const sp<IBinder>& oldToken, const sp<IBinder>& newToken) REQUIRES(mLock); void sendDropWindowCommandLocked(const sp<IBinder>& token, float x, float y) REQUIRES(mLock); @@ -690,14 +696,15 @@ private: void slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags, const sp<android::gui::WindowInfoHandle>& oldWindowHandle, const sp<android::gui::WindowInfoHandle>& newWindowHandle, - TouchState& state, int32_t deviceId, int32_t pointerId, + TouchState& state, int32_t deviceId, + const PointerProperties& pointerProperties, std::vector<InputTarget>& targets) const REQUIRES(mLock); void transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldTargetFlags, ftl::Flags<InputTarget::Flags> newTargetFlags, const sp<android::gui::WindowInfoHandle> fromWindowHandle, const sp<android::gui::WindowInfoHandle> toWindowHandle, TouchState& state, int32_t deviceId, - std::bitset<MAX_POINTER_ID + 1> pointerIds) REQUIRES(mLock); + const std::vector<PointerProperties>& pointers) 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 a4ac4fbe6c..1fec9b7599 100644 --- a/services/inputflinger/dispatcher/InputState.cpp +++ b/services/inputflinger/dispatcher/InputState.cpp @@ -195,6 +195,16 @@ bool InputState::trackMotion(const MotionEntry& entry, int32_t flags) { } } +std::optional<std::pair<std::vector<PointerProperties>, std::vector<PointerCoords>>> +InputState::getPointersOfLastEvent(const MotionEntry& entry, bool hovering) const { + ssize_t index = findMotionMemento(entry, hovering); + if (index == -1) { + return std::nullopt; + } + return std::make_pair(mMotionMementos[index].pointerProperties, + mMotionMementos[index].pointerCoords); +} + ssize_t InputState::findKeyMemento(const KeyEntry& entry) const { for (size_t i = 0; i < mKeyMementos.size(); i++) { const KeyMemento& memento = mKeyMementos[i]; @@ -311,16 +321,6 @@ bool InputState::shouldCancelPreviousStream(const MotionEntry& motionEntry) cons return true; } - // Use the previous stream cancellation logic to generate all HOVER_EXIT events. - // If this hover event was generated as a result of the pointer leaving the window, - // the HOVER_EXIT event should have the same coordinates as the previous - // HOVER_MOVE event in this stream. Ensure that all HOVER_EXITs have the same - // coordinates as the previous event by cancelling the stream here. With this approach, the - // HOVER_EXIT event is generated from the previous event. - if (actionMasked == AMOTION_EVENT_ACTION_HOVER_EXIT && lastMemento.hovering) { - return true; - } - // If the stream changes its source, just cancel the current gesture to be safe. It's // possible that the app isn't handling source changes properly if (motionEntry.source != lastMemento.source) { diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h index b0e4209882..d49469ddc1 100644 --- a/services/inputflinger/dispatcher/InputState.h +++ b/services/inputflinger/dispatcher/InputState.h @@ -48,6 +48,15 @@ public: // and should be skipped. bool trackMotion(const MotionEntry& entry, int32_t flags); + /** + * Return the PointerProperties and the PointerCoords for the last event, if found. Return + * std::nullopt if not found. We should not return std::vector<PointerCoords> in isolation, + * because the pointers can technically be stored in the vector in any order, so the + * PointerProperties are needed to specify the order in which the pointer coords are stored. + */ + std::optional<std::pair<std::vector<PointerProperties>, std::vector<PointerCoords>>> + getPointersOfLastEvent(const MotionEntry& entry, bool hovering) const; + // Create cancel events for the previous stream if the current motionEntry requires it. std::unique_ptr<EventEntry> cancelConflictingInputStream(const MotionEntry& motionEntry); diff --git a/services/inputflinger/dispatcher/InputTarget.cpp b/services/inputflinger/dispatcher/InputTarget.cpp index c02c5d67a2..28e3c6d69a 100644 --- a/services/inputflinger/dispatcher/InputTarget.cpp +++ b/services/inputflinger/dispatcher/InputTarget.cpp @@ -83,9 +83,9 @@ std::string InputTarget::getPointerInfoString() const { } std::ostream& operator<<(std::ostream& out, const InputTarget& target) { - out << "{inputChannel="; - if (target.inputChannel != nullptr) { - out << target.inputChannel->getName(); + out << "{connection="; + if (target.connection != nullptr) { + out << target.connection->getInputChannelName(); } else { out << "<null>"; } diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h index aef866baaf..5728bdf1e8 100644 --- a/services/inputflinger/dispatcher/InputTarget.h +++ b/services/inputflinger/dispatcher/InputTarget.h @@ -19,10 +19,11 @@ #include <ftl/flags.h> #include <gui/WindowInfo.h> #include <gui/constants.h> -#include <input/InputTransport.h> #include <ui/Transform.h> #include <utils/BitSet.h> #include <bitset> +#include "Connection.h" +#include "InputTargetFlags.h" namespace android::inputdispatcher { @@ -33,29 +34,7 @@ namespace android::inputdispatcher { * window area. */ struct InputTarget { - enum class Flags : uint32_t { - /* This flag indicates that the event is being delivered to a foreground application. */ - FOREGROUND = 1 << 0, - - /* This flag indicates that the MotionEvent falls within the area of the target - * obscured by another visible window above it. The motion event should be - * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */ - WINDOW_IS_OBSCURED = 1 << 1, - - /* This flag indicates that a motion event is being split across multiple windows. */ - SPLIT = 1 << 2, - - /* This flag indicates that the pointer coordinates dispatched to the application - * will be zeroed out to avoid revealing information to an application. This is - * used in conjunction with FLAG_DISPATCH_AS_OUTSIDE to prevent apps not sharing - * the same UID from watching all touches. */ - ZERO_COORDS = 1 << 3, - - /* This flag indicates that the target of a MotionEvent is partly or wholly - * obscured by another visible window above it. The motion event should be - * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED. */ - WINDOW_IS_PARTIALLY_OBSCURED = 1 << 14, - }; + using Flags = InputTargetFlags; enum class DispatchMode { /* This flag indicates that the event should be sent as is. @@ -85,8 +64,8 @@ struct InputTarget { ftl_last = SLIPPERY_ENTER, }; - // The input channel to be targeted. - std::shared_ptr<InputChannel> inputChannel; + // The input connection to be targeted. + std::shared_ptr<Connection> connection; // Flags for the input target. ftl::Flags<Flags> flags; diff --git a/services/inputflinger/dispatcher/InputTargetFlags.h b/services/inputflinger/dispatcher/InputTargetFlags.h new file mode 100644 index 0000000000..efebb18144 --- /dev/null +++ b/services/inputflinger/dispatcher/InputTargetFlags.h @@ -0,0 +1,53 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <ftl/flags.h> + +namespace android::inputdispatcher { + +enum class InputTargetFlags : uint32_t { + /* This flag indicates that the event is being delivered to a foreground application. */ + FOREGROUND = 1 << 0, + + /* This flag indicates that the MotionEvent falls within the area of the target + * obscured by another visible window above it. The motion event should be + * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */ + WINDOW_IS_OBSCURED = 1 << 1, + + /* This flag indicates that a motion event is being split across multiple windows. */ + SPLIT = 1 << 2, + + /* This flag indicates that the pointer coordinates dispatched to the application + * will be zeroed out to avoid revealing information to an application. This is + * used in conjunction with FLAG_DISPATCH_AS_OUTSIDE to prevent apps not sharing + * the same UID from watching all touches. */ + ZERO_COORDS = 1 << 3, + + /* This flag indicates that the event will not cause a focus change if it is directed to an + * unfocused window, even if it an ACTION_DOWN. This is typically used to allow gestures to be + * directed to an unfocused window without bringing it into focus. The motion event should be + * delivered with flag AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE. */ + NO_FOCUS_CHANGE = 1 << 4, + + /* This flag indicates that the target of a MotionEvent is partly or wholly + * obscured by another visible window above it. The motion event should be + * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED. */ + WINDOW_IS_PARTIALLY_OBSCURED = 1 << 14, +}; + +} // namespace android::inputdispatcher diff --git a/services/inputflinger/dispatcher/Monitor.cpp b/services/inputflinger/dispatcher/Monitor.cpp index 204791eb03..4e77d905aa 100644 --- a/services/inputflinger/dispatcher/Monitor.cpp +++ b/services/inputflinger/dispatcher/Monitor.cpp @@ -19,7 +19,7 @@ namespace android::inputdispatcher { // --- Monitor --- -Monitor::Monitor(const std::shared_ptr<InputChannel>& inputChannel, gui::Pid pid) - : inputChannel(inputChannel), pid(pid) {} +Monitor::Monitor(const std::shared_ptr<Connection>& connection, gui::Pid pid) + : connection(connection), pid(pid) {} } // namespace android::inputdispatcher diff --git a/services/inputflinger/dispatcher/Monitor.h b/services/inputflinger/dispatcher/Monitor.h index 1b1eb3a593..d15a222752 100644 --- a/services/inputflinger/dispatcher/Monitor.h +++ b/services/inputflinger/dispatcher/Monitor.h @@ -17,16 +17,16 @@ #pragma once #include <gui/PidUid.h> -#include <input/InputTransport.h> +#include "Connection.h" namespace android::inputdispatcher { struct Monitor { - std::shared_ptr<InputChannel> inputChannel; // never null + std::shared_ptr<Connection> connection; // never null gui::Pid pid; - explicit Monitor(const std::shared_ptr<InputChannel>& inputChannel, gui::Pid pid); + explicit Monitor(const std::shared_ptr<Connection>& connection, gui::Pid pid); }; } // namespace android::inputdispatcher diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index e8d8c18e4e..f8aa62500e 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -73,9 +73,9 @@ void TouchState::clearWindowsWithoutPointers() { void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle, InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId, - std::bitset<MAX_POINTER_ID + 1> touchingPointerIds, + const std::vector<PointerProperties>& touchingPointers, std::optional<nsecs_t> firstDownTimeInTarget) { - if (touchingPointerIds.none()) { + if (touchingPointers.empty()) { LOG(FATAL) << __func__ << "No pointers specified for " << windowHandle->getName(); return; } @@ -91,7 +91,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 a pointer is down for the // window. - touchedWindow.addTouchingPointers(deviceId, touchingPointerIds); + touchedWindow.addTouchingPointers(deviceId, touchingPointers); if (firstDownTimeInTarget) { touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget); } @@ -102,7 +102,7 @@ void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle, touchedWindow.windowHandle = windowHandle; touchedWindow.dispatchMode = dispatchMode; touchedWindow.targetFlags = targetFlags; - touchedWindow.addTouchingPointers(deviceId, touchingPointerIds); + touchedWindow.addTouchingPointers(deviceId, touchingPointers); if (firstDownTimeInTarget) { touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget); } @@ -110,17 +110,17 @@ void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle, } void TouchState::addHoveringPointerToWindow(const sp<WindowInfoHandle>& windowHandle, - DeviceId deviceId, int32_t hoveringPointerId) { + DeviceId deviceId, const PointerProperties& pointer) { for (TouchedWindow& touchedWindow : windows) { if (touchedWindow.windowHandle == windowHandle) { - touchedWindow.addHoveringPointer(deviceId, hoveringPointerId); + touchedWindow.addHoveringPointer(deviceId, pointer); return; } } TouchedWindow touchedWindow; touchedWindow.windowHandle = windowHandle; - touchedWindow.addHoveringPointer(deviceId, hoveringPointerId); + touchedWindow.addHoveringPointer(deviceId, pointer); windows.push_back(touchedWindow); } @@ -234,6 +234,11 @@ bool TouchState::hasHoveringPointers(DeviceId deviceId) const { }); } +bool TouchState::hasActiveStylus() const { + return std::any_of(windows.begin(), windows.end(), + [](const TouchedWindow& window) { return window.hasActiveStylus(); }); +} + std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(DeviceId deviceId, int32_t pointerId) const { std::set<sp<WindowInfoHandle>> out; diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index e0a84e8f45..3d534bc71d 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -46,11 +46,11 @@ struct TouchState { void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle, InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId, - std::bitset<MAX_POINTER_ID + 1> touchingPointerIds, + const std::vector<PointerProperties>& touchingPointers, std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt); void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle, - DeviceId deviceId, int32_t hoveringPointerId); - void removeHoveringPointer(DeviceId deviceId, int32_t hoveringPointerId); + DeviceId deviceId, const PointerProperties& pointer); + void removeHoveringPointer(DeviceId deviceId, int32_t pointerId); void clearHoveringPointers(DeviceId deviceId); void removeAllPointersForDevice(DeviceId deviceId); @@ -73,6 +73,8 @@ struct TouchState { bool isDown(DeviceId deviceId) const; bool hasHoveringPointers(DeviceId deviceId) const; + bool hasActiveStylus() const; + std::set<sp<android::gui::WindowInfoHandle>> getWindowsWithHoveringPointer( DeviceId deviceId, int32_t pointerId) const; std::string dump() const; diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp index cd0500c872..037d7c8e99 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.cpp +++ b/services/inputflinger/dispatcher/TouchedWindow.cpp @@ -26,9 +26,20 @@ namespace android { namespace inputdispatcher { +namespace { + +bool hasPointerId(const std::vector<PointerProperties>& pointers, int32_t pointerId) { + return std::find_if(pointers.begin(), pointers.end(), + [&pointerId](const PointerProperties& properties) { + return properties.id == pointerId; + }) != pointers.end(); +} + +} // namespace + bool TouchedWindow::hasHoveringPointers() const { for (const auto& [_, state] : mDeviceStates) { - if (state.hoveringPointerIds.any()) { + if (!state.hoveringPointers.empty()) { return true; } } @@ -42,7 +53,7 @@ bool TouchedWindow::hasHoveringPointers(DeviceId deviceId) const { } const DeviceState& state = stateIt->second; - return state.hoveringPointerIds.any(); + return !state.hoveringPointers.empty(); } void TouchedWindow::clearHoveringPointers(DeviceId deviceId) { @@ -51,7 +62,7 @@ void TouchedWindow::clearHoveringPointers(DeviceId deviceId) { return; } DeviceState& state = stateIt->second; - state.hoveringPointerIds.reset(); + state.hoveringPointers.clear(); if (!state.hasPointers()) { mDeviceStates.erase(stateIt); } @@ -63,22 +74,40 @@ bool TouchedWindow::hasHoveringPointer(DeviceId deviceId, int32_t pointerId) con return false; } const DeviceState& state = stateIt->second; - - return state.hoveringPointerIds.test(pointerId); + return hasPointerId(state.hoveringPointers, pointerId); } -void TouchedWindow::addHoveringPointer(DeviceId deviceId, int32_t pointerId) { - mDeviceStates[deviceId].hoveringPointerIds.set(pointerId); +void TouchedWindow::addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer) { + std::vector<PointerProperties>& hoveringPointers = mDeviceStates[deviceId].hoveringPointers; + const size_t initialSize = hoveringPointers.size(); + std::erase_if(hoveringPointers, [&pointer](const PointerProperties& properties) { + return properties.id == pointer.id; + }); + if (hoveringPointers.size() != initialSize) { + LOG(ERROR) << __func__ << ": " << pointer << ", device " << deviceId << " was in " << *this; + } + hoveringPointers.push_back(pointer); } void TouchedWindow::addTouchingPointers(DeviceId deviceId, - std::bitset<MAX_POINTER_ID + 1> pointers) { - mDeviceStates[deviceId].touchingPointerIds |= pointers; + const std::vector<PointerProperties>& pointers) { + std::vector<PointerProperties>& touchingPointers = mDeviceStates[deviceId].touchingPointers; + const size_t initialSize = touchingPointers.size(); + for (const PointerProperties& pointer : pointers) { + std::erase_if(touchingPointers, [&pointer](const PointerProperties& properties) { + return properties.id == pointer.id; + }); + } + if (touchingPointers.size() != initialSize) { + LOG(ERROR) << __func__ << ": " << dumpVector(pointers, streamableToString) << ", device " + << deviceId << " already in " << *this; + } + touchingPointers.insert(touchingPointers.end(), pointers.begin(), pointers.end()); } bool TouchedWindow::hasTouchingPointers() const { for (const auto& [_, state] : mDeviceStates) { - if (state.touchingPointerIds.any()) { + if (!state.touchingPointers.empty()) { return true; } } @@ -86,21 +115,25 @@ bool TouchedWindow::hasTouchingPointers() const { } bool TouchedWindow::hasTouchingPointers(DeviceId deviceId) const { - return getTouchingPointers(deviceId).any(); + return !getTouchingPointers(deviceId).empty(); } bool TouchedWindow::hasTouchingPointer(DeviceId deviceId, int32_t pointerId) const { - return getTouchingPointers(deviceId).test(pointerId); + const auto stateIt = mDeviceStates.find(deviceId); + if (stateIt == mDeviceStates.end()) { + return false; + } + const DeviceState& state = stateIt->second; + return hasPointerId(state.touchingPointers, pointerId); } -std::bitset<MAX_POINTER_ID + 1> TouchedWindow::getTouchingPointers(DeviceId deviceId) const { +std::vector<PointerProperties> TouchedWindow::getTouchingPointers(DeviceId deviceId) const { const auto stateIt = mDeviceStates.find(deviceId); if (stateIt == mDeviceStates.end()) { return {}; } const DeviceState& state = stateIt->second; - - return state.touchingPointerIds; + return state.touchingPointers; } void TouchedWindow::removeTouchingPointer(DeviceId deviceId, int32_t pointerId) { @@ -118,7 +151,10 @@ void TouchedWindow::removeTouchingPointers(DeviceId deviceId, } DeviceState& state = stateIt->second; - state.touchingPointerIds &= ~pointers; + std::erase_if(state.touchingPointers, [&pointers](const PointerProperties& properties) { + return pointers.test(properties.id); + }); + state.pilferingPointerIds &= ~pointers; if (!state.hasPointers()) { @@ -126,10 +162,26 @@ void TouchedWindow::removeTouchingPointers(DeviceId deviceId, } } +bool TouchedWindow::hasActiveStylus() const { + for (const auto& [_, state] : mDeviceStates) { + for (const PointerProperties& properties : state.touchingPointers) { + if (properties.toolType == ToolType::STYLUS) { + return true; + } + } + for (const PointerProperties& properties : state.hoveringPointers) { + if (properties.toolType == ToolType::STYLUS) { + return true; + } + } + } + return false; +} + std::set<DeviceId> TouchedWindow::getTouchingDeviceIds() const { std::set<DeviceId> deviceIds; for (const auto& [deviceId, deviceState] : mDeviceStates) { - if (deviceState.touchingPointerIds.any()) { + if (!deviceState.touchingPointers.empty()) { deviceIds.insert(deviceId); } } @@ -198,7 +250,7 @@ void TouchedWindow::removeAllTouchingPointersForDevice(DeviceId deviceId) { } DeviceState& state = stateIt->second; - state.touchingPointerIds.reset(); + state.touchingPointers.clear(); state.pilferingPointerIds.reset(); state.downTimeInTarget.reset(); @@ -214,7 +266,9 @@ void TouchedWindow::removeHoveringPointer(DeviceId deviceId, int32_t pointerId) } DeviceState& state = stateIt->second; - state.hoveringPointerIds.set(pointerId, false); + std::erase_if(state.hoveringPointers, [&pointerId](const PointerProperties& properties) { + return properties.id == pointerId; + }); if (!state.hasPointers()) { mDeviceStates.erase(stateIt); @@ -228,7 +282,7 @@ void TouchedWindow::removeAllHoveringPointersForDevice(DeviceId deviceId) { } DeviceState& state = stateIt->second; - state.hoveringPointerIds.reset(); + state.hoveringPointers.clear(); if (!state.hasPointers()) { mDeviceStates.erase(stateIt); @@ -236,11 +290,11 @@ void TouchedWindow::removeAllHoveringPointersForDevice(DeviceId deviceId) { } std::string TouchedWindow::deviceStateToString(const TouchedWindow::DeviceState& state) { - return StringPrintf("[touchingPointerIds=%s, " - "downTimeInTarget=%s, hoveringPointerIds=%s, pilferingPointerIds=%s]", - bitsetToString(state.touchingPointerIds).c_str(), + return StringPrintf("[touchingPointers=%s, " + "downTimeInTarget=%s, hoveringPointers=%s, pilferingPointerIds=%s]", + dumpVector(state.touchingPointers, streamableToString).c_str(), toString(state.downTimeInTarget).c_str(), - bitsetToString(state.hoveringPointerIds).c_str(), + dumpVector(state.hoveringPointers, streamableToString).c_str(), bitsetToString(state.pilferingPointerIds).c_str()); } diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h index c604353593..0d1531f8ff 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -38,17 +38,18 @@ struct TouchedWindow { bool hasHoveringPointers() const; bool hasHoveringPointers(DeviceId deviceId) const; bool hasHoveringPointer(DeviceId deviceId, int32_t pointerId) const; - void addHoveringPointer(DeviceId deviceId, int32_t pointerId); + void addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer); void removeHoveringPointer(DeviceId deviceId, int32_t pointerId); // Touching bool hasTouchingPointer(DeviceId deviceId, int32_t pointerId) const; bool hasTouchingPointers() const; bool hasTouchingPointers(DeviceId deviceId) const; - std::bitset<MAX_POINTER_ID + 1> getTouchingPointers(DeviceId deviceId) const; - void addTouchingPointers(DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> pointers); + std::vector<PointerProperties> getTouchingPointers(DeviceId deviceId) const; + void addTouchingPointers(DeviceId deviceId, const std::vector<PointerProperties>& pointers); void removeTouchingPointer(DeviceId deviceId, int32_t pointerId); void removeTouchingPointers(DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> pointers); + bool hasActiveStylus() const; std::set<DeviceId> getTouchingDeviceIds() const; // Pilfering pointers @@ -69,16 +70,16 @@ struct TouchedWindow { private: struct DeviceState { - std::bitset<MAX_POINTER_ID + 1> touchingPointerIds; + std::vector<PointerProperties> touchingPointers; // The pointer ids of the pointers that this window is currently pilfering, by device std::bitset<MAX_POINTER_ID + 1> pilferingPointerIds; // Time at which the first action down occurred on this window, for each device // NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE // scenario. std::optional<nsecs_t> downTimeInTarget; - std::bitset<MAX_POINTER_ID + 1> hoveringPointerIds; + std::vector<PointerProperties> hoveringPointers; - bool hasPointers() const { return touchingPointerIds.any() || hoveringPointerIds.any(); }; + bool hasPointers() const { return !touchingPointers.empty() || !hoveringPointers.empty(); }; }; std::map<DeviceId, DeviceState> mDeviceStates; diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h index 001dc6cf7b..36cebcc966 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h @@ -101,6 +101,9 @@ public: */ virtual void setFocusedDisplay(int32_t displayId) = 0; + /** Sets the minimum time between user activity pokes. */ + virtual void setMinTimeBetweenUserActivityPokes(std::chrono::milliseconds interval) = 0; + /* Sets the input dispatching mode. * * This method may be called on any thread (usually by the input manager). @@ -137,7 +140,9 @@ public: */ virtual void setMaximumObscuringOpacityForTouch(float opacity) = 0; - /* Transfers touch focus from one window to another window. + /** + * Transfers touch focus from one window to another window. Transferring touch focus will not + * have any effect on the focused window. * * Returns true on success. False if the window did not actually have touch focus. */ @@ -146,6 +151,7 @@ public: /** * Transfer touch focus to the provided channel, no matter where the current touch is. + * Transferring touch focus will not have any effect on the focused window. * * Return true on success, false if there was no on-going touch. */ diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h index 1c23720ecb..62c2b02967 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h @@ -99,8 +99,8 @@ public: * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event * should be dispatched to applications. */ - virtual void interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when, - uint32_t& policyFlags) = 0; + virtual void interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, int32_t action, + nsecs_t when, uint32_t& policyFlags) = 0; /* Allows the policy a chance to intercept a key before dispatching. */ virtual nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token, @@ -131,6 +131,18 @@ public: return std::chrono::nanoseconds(currentTime - eventTime) >= STALE_EVENT_TIMEOUT; } + /** + * Get the additional latency to add while waiting for other input events to process before + * dispatching the pending key. + * If there are unprocessed events, the pending key will not be dispatched immediately. Instead, + * the dispatcher will wait for this timeout, to account for the possibility that the focus + * might change due to touch or other events (such as another app getting launched by keys). + * This would give the pending key the opportunity to go to a newly focused window instead. + */ + virtual std::chrono::nanoseconds getKeyWaitingForEventsTimeout() { + return KEY_WAITING_FOR_EVENTS_TIMEOUT; + } + /* Notifies the policy that a pointer down event has occurred outside the current focused * window. * @@ -150,6 +162,13 @@ public: /* Notifies the policy that there was an input device interaction with apps. */ virtual void notifyDeviceInteraction(DeviceId deviceId, nsecs_t timestamp, const std::set<gui::Uid>& uids) = 0; + +private: + // Additional key latency in case a connection is still processing some motion events. + // This will help with the case when a user touched a button that opens a new window, + // and gives us the chance to dispatch the key to this new window. + static constexpr std::chrono::nanoseconds KEY_WAITING_FOR_EVENTS_TIMEOUT = + std::chrono::milliseconds(500); }; } // namespace android diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp new file mode 100644 index 0000000000..a61fa85d6e --- /dev/null +++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2024 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 "AndroidInputEventProtoConverter.h" + +#include <android-base/logging.h> +#include <perfetto/trace/android/android_input_event.pbzero.h> + +namespace android::inputdispatcher::trace { + +void AndroidInputEventProtoConverter::toProtoMotionEvent(const TracedMotionEvent& event, + proto::AndroidMotionEvent& outProto) { + outProto.set_event_id(event.id); + outProto.set_event_time_nanos(event.eventTime); + outProto.set_down_time_nanos(event.downTime); + outProto.set_source(event.source); + outProto.set_action(event.action); + outProto.set_device_id(event.deviceId); + outProto.set_display_id(event.displayId); + outProto.set_classification(static_cast<int32_t>(event.classification)); + outProto.set_cursor_position_x(event.xCursorPosition); + outProto.set_cursor_position_y(event.yCursorPosition); + outProto.set_flags(event.flags); + outProto.set_policy_flags(event.policyFlags); + + for (uint32_t i = 0; i < event.pointerProperties.size(); i++) { + auto* pointer = outProto.add_pointer(); + + const auto& props = event.pointerProperties[i]; + pointer->set_pointer_id(props.id); + pointer->set_tool_type(static_cast<int32_t>(props.toolType)); + + const auto& coords = event.pointerCoords[i]; + auto bits = BitSet64(coords.bits); + for (int32_t axisIndex = 0; !bits.isEmpty(); axisIndex++) { + const auto axis = bits.clearFirstMarkedBit(); + auto axisEntry = pointer->add_axis_value(); + axisEntry->set_axis(axis); + axisEntry->set_value(coords.values[axisIndex]); + } + } +} + +void AndroidInputEventProtoConverter::toProtoKeyEvent(const TracedKeyEvent& event, + proto::AndroidKeyEvent& outProto) { + outProto.set_event_id(event.id); + outProto.set_event_time_nanos(event.eventTime); + outProto.set_down_time_nanos(event.downTime); + outProto.set_source(event.source); + outProto.set_action(event.action); + outProto.set_device_id(event.deviceId); + outProto.set_display_id(event.displayId); + outProto.set_key_code(event.keyCode); + outProto.set_scan_code(event.scanCode); + outProto.set_meta_state(event.metaState); + outProto.set_repeat_count(event.repeatCount); + outProto.set_flags(event.flags); + outProto.set_policy_flags(event.policyFlags); +} + +void AndroidInputEventProtoConverter::toProtoWindowDispatchEvent( + const InputTracingBackendInterface::WindowDispatchArgs& args, + proto::AndroidWindowInputDispatchEvent& outProto) { + std::visit([&](auto entry) { outProto.set_event_id(entry.id); }, args.eventEntry); + outProto.set_vsync_id(args.vsyncId); + outProto.set_window_id(args.windowId); + outProto.set_resolved_flags(args.resolvedFlags); + + if (auto* motion = std::get_if<TracedMotionEvent>(&args.eventEntry); motion != nullptr) { + for (size_t i = 0; i < motion->pointerProperties.size(); i++) { + auto* pointerProto = outProto.add_dispatched_pointer(); + pointerProto->set_pointer_id(motion->pointerProperties[i].id); + const auto rawXY = + MotionEvent::calculateTransformedXY(motion->source, args.rawTransform, + motion->pointerCoords[i].getXYValue()); + pointerProto->set_x_in_display(rawXY.x); + pointerProto->set_y_in_display(rawXY.y); + + const auto& coords = motion->pointerCoords[i]; + const auto coordsInWindow = + MotionEvent::calculateTransformedCoords(motion->source, args.transform, coords); + auto bits = BitSet64(coords.bits); + for (int32_t axisIndex = 0; !bits.isEmpty(); axisIndex++) { + const uint32_t axis = bits.clearFirstMarkedBit(); + const float axisValueInWindow = coordsInWindow.values[axisIndex]; + if (coords.values[axisIndex] != axisValueInWindow) { + auto* axisEntry = pointerProto->add_axis_value_in_window(); + axisEntry->set_axis(axis); + axisEntry->set_value(axisValueInWindow); + } + } + } + } +} + +} // namespace android::inputdispatcher::trace diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h new file mode 100644 index 0000000000..8a46f1518b --- /dev/null +++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h @@ -0,0 +1,39 @@ +/* + * Copyright 2024 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 <perfetto/trace/android/android_input_event.pbzero.h> + +#include "InputTracingBackendInterface.h" + +namespace proto = perfetto::protos::pbzero; + +namespace android::inputdispatcher::trace { + +/** + * Write traced events into Perfetto protos. + */ +class AndroidInputEventProtoConverter { +public: + static void toProtoMotionEvent(const TracedMotionEvent& event, + proto::AndroidMotionEvent& outProto); + static void toProtoKeyEvent(const TracedKeyEvent& event, proto::AndroidKeyEvent& outProto); + static void toProtoWindowDispatchEvent(const InputTracingBackendInterface::WindowDispatchArgs&, + proto::AndroidWindowInputDispatchEvent& outProto); +}; + +} // namespace android::inputdispatcher::trace diff --git a/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h b/services/inputflinger/dispatcher/trace/EventTrackerInterface.h index b48529f4ac..929820e0a6 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h +++ b/services/inputflinger/dispatcher/trace/EventTrackerInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Android Open Source Project + * Copyright 2024 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,17 +16,17 @@ #pragma once -#include <gmock/gmock.h> +namespace android::inputdispatcher::trace { -#include "Scheduler/VSyncTracker.h" - -namespace android::scheduler::mock { - -struct VsyncTrackerCallback final : IVsyncTrackerCallback { - MOCK_METHOD(void, onVsyncGenerated, (TimePoint, ftl::NonNull<DisplayModePtr>, Fps), (override)); +/** + * A tracker used to track the lifecycle of a traced input event. + * The tracker should be stored inside the traced event. When the event goes out of scope after + * the dispatcher has finished processing it, the tracker will call back into the tracer to + * initiate cleanup. + */ +class EventTrackerInterface { +public: + virtual ~EventTrackerInterface() = default; }; -struct NoOpVsyncTrackerCallback final : IVsyncTrackerCallback { - void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override{}; -}; -} // namespace android::scheduler::mock +} // namespace android::inputdispatcher::trace diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp new file mode 100644 index 0000000000..8a855c2035 --- /dev/null +++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp @@ -0,0 +1,224 @@ +/* + * Copyright 2024 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 "InputTracer" + +#include "InputTracer.h" + +#include <android-base/logging.h> +#include <utils/AndroidThreads.h> + +namespace android::inputdispatcher::trace::impl { + +namespace { + +TracedEvent createTracedEvent(const MotionEntry& e) { + return TracedMotionEvent{e.id, + e.eventTime, + e.policyFlags, + e.deviceId, + e.source, + e.displayId, + e.action, + e.actionButton, + e.flags, + e.metaState, + e.buttonState, + e.classification, + e.edgeFlags, + e.xPrecision, + e.yPrecision, + e.xCursorPosition, + e.yCursorPosition, + e.downTime, + e.pointerProperties, + e.pointerCoords}; +} + +TracedEvent createTracedEvent(const KeyEntry& e) { + return TracedKeyEvent{e.id, e.eventTime, e.policyFlags, e.deviceId, e.source, + e.displayId, e.action, e.keyCode, e.scanCode, e.metaState, + e.downTime, e.flags, e.repeatCount}; +} + +} // namespace + +// --- InputTracer --- + +InputTracer::InputTracer(std::unique_ptr<InputTracingBackendInterface> backend) + : mTracerThread(&InputTracer::threadLoop, this), mBackend(std::move(backend)) {} + +InputTracer::~InputTracer() { + { + std::scoped_lock lock(mLock); + mThreadExit = true; + } + mThreadWakeCondition.notify_all(); + mTracerThread.join(); +} + +std::unique_ptr<EventTrackerInterface> InputTracer::traceInboundEvent(const EventEntry& entry) { + std::scoped_lock lock(mLock); + TracedEvent traced; + + if (entry.type == EventEntry::Type::MOTION) { + const auto& motion = static_cast<const MotionEntry&>(entry); + traced = createTracedEvent(motion); + } else if (entry.type == EventEntry::Type::KEY) { + const auto& key = static_cast<const KeyEntry&>(entry); + traced = createTracedEvent(key); + } else { + LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type); + } + + return std::make_unique<EventTrackerImpl>(*this, std::move(traced)); +} + +void InputTracer::dispatchToTargetHint(const EventTrackerInterface& cookie, + const InputTarget& target) { + std::scoped_lock lock(mLock); + auto& cookieState = getState(cookie); + if (!cookieState) { + LOG(FATAL) << "dispatchToTargetHint() should not be called after eventProcessingComplete()"; + } + // TODO(b/210460522): Determine if the event is sensitive based on the target. +} + +void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) { + { + std::scoped_lock lock(mLock); + auto& cookieState = getState(cookie); + if (!cookieState) { + LOG(FATAL) << "Traced event was already logged. " + "eventProcessingComplete() was likely called more than once."; + } + mTraceQueue.emplace_back(std::move(*cookieState)); + cookieState.reset(); + } // release lock + + mThreadWakeCondition.notify_all(); +} + +void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, + const EventTrackerInterface* cookie) { + { + std::scoped_lock lock(mLock); + const EventEntry& entry = *dispatchEntry.eventEntry; + + TracedEvent traced; + if (entry.type == EventEntry::Type::MOTION) { + const auto& motion = static_cast<const MotionEntry&>(entry); + traced = createTracedEvent(motion); + } else if (entry.type == EventEntry::Type::KEY) { + const auto& key = static_cast<const KeyEntry&>(entry); + traced = createTracedEvent(key); + } else { + LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type); + } + + if (!cookie) { + // This event was not tracked as an inbound event, so trace it now. + mTraceQueue.emplace_back(traced); + } + + // The vsyncId only has meaning if the event is targeting a window. + const int32_t windowId = dispatchEntry.windowId.value_or(0); + const int32_t vsyncId = dispatchEntry.windowId.has_value() ? dispatchEntry.vsyncId : 0; + + mDispatchTraceQueue.emplace_back(std::move(traced), dispatchEntry.deliveryTime, + dispatchEntry.resolvedFlags, dispatchEntry.targetUid, + vsyncId, windowId, dispatchEntry.transform, + dispatchEntry.rawTransform); + } // release lock + + mThreadWakeCondition.notify_all(); +} + +std::optional<InputTracer::EventState>& InputTracer::getState(const EventTrackerInterface& cookie) { + return static_cast<const EventTrackerImpl&>(cookie).mLockedState; +} + +void InputTracer::threadLoop() { + androidSetThreadName("InputTracer"); + + std::vector<const EventState> eventsToTrace; + std::vector<const WindowDispatchArgs> dispatchEventsToTrace; + + while (true) { + { // acquire lock + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + + // Wait until we need to process more events or exit. + mThreadWakeCondition.wait(lock, [&]() REQUIRES(mLock) { + return mThreadExit || !mTraceQueue.empty() || !mDispatchTraceQueue.empty(); + }); + if (mThreadExit) { + return; + } + + mTraceQueue.swap(eventsToTrace); + mDispatchTraceQueue.swap(dispatchEventsToTrace); + } // release lock + + // Trace the events into the backend without holding the lock to reduce the amount of + // work performed in the critical section. + writeEventsToBackend(eventsToTrace, dispatchEventsToTrace); + eventsToTrace.clear(); + dispatchEventsToTrace.clear(); + } +} + +void InputTracer::writeEventsToBackend( + const std::vector<const EventState>& events, + const std::vector<const WindowDispatchArgs>& dispatchEvents) { + for (const auto& event : events) { + if (auto* motion = std::get_if<TracedMotionEvent>(&event.event); motion != nullptr) { + mBackend->traceMotionEvent(*motion); + } else { + mBackend->traceKeyEvent(std::get<TracedKeyEvent>(event.event)); + } + } + + for (const auto& dispatchArgs : dispatchEvents) { + mBackend->traceWindowDispatch(dispatchArgs); + } +} + +// --- InputTracer::EventTrackerImpl --- + +InputTracer::EventTrackerImpl::EventTrackerImpl(InputTracer& tracer, TracedEvent&& event) + : mTracer(tracer), mLockedState(event) {} + +InputTracer::EventTrackerImpl::~EventTrackerImpl() { + { + std::scoped_lock lock(mTracer.mLock); + if (!mLockedState) { + // This event has already been written to the trace as expected. + return; + } + // We're still holding on to the state, which means it hasn't yet been written to the trace. + // Write it to the trace now. + // TODO(b/210460522): Determine why/where the event is being destroyed before + // eventProcessingComplete() is called. + mTracer.mTraceQueue.emplace_back(std::move(*mLockedState)); + mLockedState.reset(); + } // release lock + + mTracer.mThreadWakeCondition.notify_all(); +} + +} // namespace android::inputdispatcher::trace::impl diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h new file mode 100644 index 0000000000..9fe395d397 --- /dev/null +++ b/services/inputflinger/dispatcher/trace/InputTracer.h @@ -0,0 +1,99 @@ +/* + * Copyright 2024 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 "InputTracerInterface.h" + +#include <android-base/thread_annotations.h> +#include <gui/WindowInfo.h> + +#include <memory> +#include <mutex> +#include <thread> +#include <unordered_set> +#include <vector> + +#include "../Entry.h" +#include "InputTracingBackendInterface.h" + +namespace android::inputdispatcher::trace::impl { + +/** + * The tracer implementation for InputDispatcher. + * + * InputTracer is thread-safe, so it can be called from any thread. Upon construction, InputTracer + * will start its own thread that it uses for write events into the tracing backend. That is the + * one and only thread that will interact with the tracing backend, since the Perfetto backend + * uses thread-local storage. + * + * See the documentation in InputTracerInterface for the API surface. + */ +class InputTracer : public InputTracerInterface { +public: + explicit InputTracer(std::unique_ptr<InputTracingBackendInterface>); + ~InputTracer() override; + InputTracer(const InputTracer&) = delete; + InputTracer& operator=(const InputTracer&) = delete; + + std::unique_ptr<EventTrackerInterface> traceInboundEvent(const EventEntry&) override; + void dispatchToTargetHint(const EventTrackerInterface&, const InputTarget&) override; + void eventProcessingComplete(const EventTrackerInterface&) override; + void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface*) override; + +private: + std::mutex mLock; + std::thread mTracerThread; + bool mThreadExit GUARDED_BY(mLock){false}; + std::condition_variable mThreadWakeCondition; + std::unique_ptr<InputTracingBackendInterface> mBackend; + + // The state of a tracked event. + struct EventState { + const TracedEvent event; + // TODO(b/210460522): Add additional args for tracking event sensitivity and + // dispatch target UIDs. + }; + std::vector<const EventState> mTraceQueue GUARDED_BY(mLock); + using WindowDispatchArgs = InputTracingBackendInterface::WindowDispatchArgs; + std::vector<const WindowDispatchArgs> mDispatchTraceQueue GUARDED_BY(mLock); + + // Provides thread-safe access to the state from an event tracker cookie. + std::optional<EventState>& getState(const EventTrackerInterface&) REQUIRES(mLock); + + // Implementation of the event tracker cookie. + class EventTrackerImpl : public EventTrackerInterface { + public: + explicit EventTrackerImpl(InputTracer&, TracedEvent&& entry); + virtual ~EventTrackerImpl() override; + + private: + InputTracer& mTracer; + // This event tracker cookie will only hold the state as long as it has not been written + // to the trace. The state is released when the event is written to the trace. + mutable std::optional<EventState> mLockedState; + + // Only allow InputTracer access to the locked state through getTrackerState() to ensure + // that the InputTracer lock is held when this is accessed. + friend std::optional<EventState>& InputTracer::getState(const EventTrackerInterface&); + }; + + void threadLoop(); + void writeEventsToBackend(const std::vector<const EventState>& events, + const std::vector<const WindowDispatchArgs>& dispatchEvents); +}; + +} // namespace android::inputdispatcher::trace::impl diff --git a/services/inputflinger/dispatcher/trace/InputTracerInterface.h b/services/inputflinger/dispatcher/trace/InputTracerInterface.h new file mode 100644 index 0000000000..c6cd7de4b6 --- /dev/null +++ b/services/inputflinger/dispatcher/trace/InputTracerInterface.h @@ -0,0 +1,87 @@ +/* + * Copyright 2024 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 "../Entry.h" +#include "../InputTarget.h" +#include "EventTrackerInterface.h" + +namespace android::inputdispatcher::trace { + +/** + * InputTracerInterface is the tracing interface for InputDispatcher. + * + * The tracer is responsible for tracing information about input events and where they are + * dispatched. The trace is logged to the backend using the InputTracingBackendInterface. + * + * A normal traced event should have the following lifecycle: + * - The EventTracker is obtained from traceInboundEvent(), after which point the event + * should not change. + * - While the event is being processed, dispatchToTargetHint() is called for each target that + * the event will be eventually sent to. + * - Once all targets have been determined, eventProcessingComplete() is called, at which point + * the tracer will have enough information to commit the event to the trace. + * - For each event that is dispatched to the client, traceEventDispatch() is called, and the + * tracer will record that the event was sent to the client. + */ +class InputTracerInterface { +public: + InputTracerInterface() = default; + virtual ~InputTracerInterface() = default; + InputTracerInterface(const InputTracerInterface&) = delete; + InputTracerInterface& operator=(const InputTracerInterface&) = delete; + + /** + * Trace an input event that is being processed by InputDispatcher. The event must not be + * modified after it is traced to keep the traced event consistent with the event that is + * eventually dispatched. An EventTracker is returned for each traced event that should be used + * to track the event's lifecycle inside InputDispatcher. + */ + virtual std::unique_ptr<EventTrackerInterface> traceInboundEvent(const EventEntry&) = 0; + + /** + * Notify the tracer that the traced event will be sent to the given InputTarget. + * The tracer may change how the event is logged depending on the target. For example, + * events targeting certain UIDs may be logged as sensitive events. + * This may be called 0 or more times for each tracked event before event processing is + * completed. + */ + virtual void dispatchToTargetHint(const EventTrackerInterface&, const InputTarget&) = 0; + + /** + * Notify the tracer that the event processing is complete. This may be called at most once + * for each traced event. If a tracked event is dropped before it can be processed, it is + * possible that this is never called before the EventTracker is destroyed. + * + * This is used to commit the event to the trace in a timely manner, rather than always + * waiting for the event to go out of scope (and thus for the EventTracker to be destroyed) + * before committing. The point at which the event is destroyed can depend on several factors + * outside of our control, such as how long apps take to respond, so we don't want to depend on + * that. + */ + virtual void eventProcessingComplete(const EventTrackerInterface&) = 0; + + /** + * Trace an input event being successfully dispatched to a window. The dispatched event may + * be a previously traced inbound event, or it may be a synthesized event that has not been + * previously traced. For inbound events that were previously traced, the EventTracker cookie + * must be provided. For events that were not previously traced, the cookie must be null. + */ + virtual void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface*) = 0; +}; + +} // namespace android::inputdispatcher::trace diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h new file mode 100644 index 0000000000..bc47817817 --- /dev/null +++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h @@ -0,0 +1,105 @@ +/* + * Copyright 2024 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 <gui/PidUid.h> +#include <input/Input.h> +#include <ui/Transform.h> + +#include <array> +#include <variant> +#include <vector> + +namespace android::inputdispatcher::trace { + +/** + * A representation of an Android KeyEvent used by the tracing backend. + */ +struct TracedKeyEvent { + int32_t id; + nsecs_t eventTime; + uint32_t policyFlags; + int32_t deviceId; + uint32_t source; + int32_t displayId; + int32_t action; + int32_t keyCode; + int32_t scanCode; + int32_t metaState; + nsecs_t downTime; + int32_t flags; + int32_t repeatCount; +}; + +/** + * A representation of an Android MotionEvent used by the tracing backend. + */ +struct TracedMotionEvent { + int32_t id; + nsecs_t eventTime; + uint32_t policyFlags; + int32_t deviceId; + uint32_t source; + int32_t displayId; + int32_t action; + int32_t actionButton; + int32_t flags; + int32_t metaState; + int32_t buttonState; + MotionClassification classification; + int32_t edgeFlags; + float xPrecision; + float yPrecision; + float xCursorPosition; + float yCursorPosition; + nsecs_t downTime; + std::vector<PointerProperties> pointerProperties; + std::vector<PointerCoords> pointerCoords; +}; + +/** A representation of a traced input event. */ +using TracedEvent = std::variant<TracedKeyEvent, TracedMotionEvent>; + +/** + * An interface for the tracing backend, used for setting a custom backend for testing. + */ +class InputTracingBackendInterface { +public: + virtual ~InputTracingBackendInterface() = default; + + /** Trace a KeyEvent. */ + virtual void traceKeyEvent(const TracedKeyEvent&) const = 0; + + /** Trace a MotionEvent. */ + virtual void traceMotionEvent(const TracedMotionEvent&) const = 0; + + /** Trace an event being sent to a window. */ + struct WindowDispatchArgs { + TracedEvent eventEntry; + nsecs_t deliveryTime; + int32_t resolvedFlags; + gui::Uid targetUid; + int64_t vsyncId; + int32_t windowId; + ui::Transform transform; + ui::Transform rawTransform; + std::array<uint8_t, 32> hmac; + }; + virtual void traceWindowDispatch(const WindowDispatchArgs&) const = 0; +}; + +} // namespace android::inputdispatcher::trace diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp new file mode 100644 index 0000000000..4442ad8586 --- /dev/null +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp @@ -0,0 +1,94 @@ +/* + * Copyright 2024 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 "InputTracer" + +#include "InputTracingPerfettoBackend.h" + +#include "AndroidInputEventProtoConverter.h" + +#include <android-base/logging.h> +#include <perfetto/trace/android/android_input_event.pbzero.h> + +namespace android::inputdispatcher::trace::impl { + +namespace { + +constexpr auto INPUT_EVENT_TRACE_DATA_SOURCE_NAME = "android.input.inputevent"; + +} // namespace + +// --- PerfettoBackend::InputEventDataSource --- + +void PerfettoBackend::InputEventDataSource::OnStart(const perfetto::DataSourceBase::StartArgs&) { + LOG(INFO) << "Starting perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME; +} + +void PerfettoBackend::InputEventDataSource::OnStop(const perfetto::DataSourceBase::StopArgs&) { + LOG(INFO) << "Stopping perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME; + InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { ctx.Flush(); }); +} + +// --- PerfettoBackend --- + +std::once_flag PerfettoBackend::sDataSourceRegistrationFlag{}; + +PerfettoBackend::PerfettoBackend() { + // Use a once-flag to ensure that the data source is only registered once per boot, since + // we never unregister the InputEventDataSource. + std::call_once(sDataSourceRegistrationFlag, []() { + perfetto::TracingInitArgs args; + args.backends = perfetto::kSystemBackend; + perfetto::Tracing::Initialize(args); + + // Register our custom data source for input event tracing. + perfetto::DataSourceDescriptor dsd; + dsd.set_name(INPUT_EVENT_TRACE_DATA_SOURCE_NAME); + InputEventDataSource::Register(dsd); + LOG(INFO) << "InputTracer initialized for data source: " + << INPUT_EVENT_TRACE_DATA_SOURCE_NAME; + }); +} + +void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event) const { + InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { + auto tracePacket = ctx.NewTracePacket(); + auto* inputEvent = tracePacket->set_android_input_event(); + auto* dispatchMotion = inputEvent->set_dispatcher_motion_event(); + AndroidInputEventProtoConverter::toProtoMotionEvent(event, *dispatchMotion); + }); +} + +void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event) const { + InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { + auto tracePacket = ctx.NewTracePacket(); + auto* inputEvent = tracePacket->set_android_input_event(); + auto* dispatchKey = inputEvent->set_dispatcher_key_event(); + AndroidInputEventProtoConverter::toProtoKeyEvent(event, *dispatchKey); + }); +} + +void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs) const { + InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { + auto tracePacket = ctx.NewTracePacket(); + auto* inputEventProto = tracePacket->set_android_input_event(); + auto* dispatchEventProto = inputEventProto->set_dispatcher_window_dispatch_event(); + AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(dispatchArgs, + *dispatchEventProto); + }); +} + +} // namespace android::inputdispatcher::trace::impl diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h new file mode 100644 index 0000000000..2777cfe9fe --- /dev/null +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h @@ -0,0 +1,66 @@ +/* + * Copyright 2024 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 "InputTracingBackendInterface.h" + +#include <perfetto/tracing.h> +#include <mutex> + +namespace android::inputdispatcher::trace::impl { + +/** + * The tracing backend that writes events into ongoing Perfetto traces. + * + * Example shell command to take an input trace from Perfetto: + * + * adb shell perfetto \ + * -c - --txt \ + * -o /data/misc/perfetto-traces/trace.input-trace \ + * <<END + * buffers: { + * size_kb: 5000 + * fill_policy: RING_BUFFER + * } + * data_sources: { + * config { + * name: "android.input.inputevent" + * } + * } + * END + */ +class PerfettoBackend : public InputTracingBackendInterface { +public: + PerfettoBackend(); + ~PerfettoBackend() override = default; + + void traceKeyEvent(const TracedKeyEvent&) const override; + void traceMotionEvent(const TracedMotionEvent&) const override; + void traceWindowDispatch(const WindowDispatchArgs&) const override; + + class InputEventDataSource : public perfetto::DataSource<InputEventDataSource> { + public: + void OnSetup(const SetupArgs&) override {} + void OnStart(const StartArgs&) override; + void OnStop(const StopArgs&) override; + }; + +private: + static std::once_flag sDataSourceRegistrationFlag; +}; + +} // namespace android::inputdispatcher::trace::impl diff --git a/services/inputflinger/include/InputFilterPolicyInterface.h b/services/inputflinger/include/InputFilterPolicyInterface.h new file mode 100644 index 0000000000..4d39b977d5 --- /dev/null +++ b/services/inputflinger/include/InputFilterPolicyInterface.h @@ -0,0 +1,47 @@ +/* + * Copyright 2024 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 + +namespace android { + +/** + * The InputFilter policy interface. + * + * This is the interface that InputFilter uses to talk to Input Manager and other system components. + */ +class InputFilterPolicyInterface { +public: + virtual ~InputFilterPolicyInterface() = default; + + /** + * A callback to notify about sticky modifier state changes when Sticky keys feature is enabled. + * + * modifierState: Current sticky modifier state which will be sent with all subsequent + * KeyEvents. This only includes modifiers that can be 'Sticky' which includes: Meta, Ctrl, + * Shift, Alt and AltGr. + * + * lockedModifierState: Current locked modifier state representing modifiers that don't get + * cleared after non-modifier key press. This only includes modifiers that can be 'Sticky' which + * includes: Meta, Ctrl, Shift, Alt and AltGr. + * + * For more information {@see sticky_keys_filter.rs} + */ + virtual void notifyStickyModifierStateChanged(uint32_t modifierState, + uint32_t lockedModifierState) = 0; +}; + +} // namespace android diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index efc8b260f3..79c8a4ba76 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -126,7 +126,20 @@ struct InputReaderConfiguration { // The suggested display ID to show the cursor. int32_t defaultPointerDisplayId; + // The mouse pointer speed, as a number from -7 (slowest) to 7 (fastest). + // + // Currently only used when the enable_new_mouse_pointer_ballistics flag is enabled. + int32_t mousePointerSpeed; + + // Displays on which an acceleration curve shouldn't be applied for pointer movements from mice. + // + // Currently only used when the enable_new_mouse_pointer_ballistics flag is enabled. + std::set<int32_t> displaysWithMousePointerAccelerationDisabled; + // Velocity control parameters for mouse pointer movements. + // + // If the enable_new_mouse_pointer_ballistics flag is enabled, these are ignored and the values + // of mousePointerSpeed and mousePointerAccelerationEnabled used instead. VelocityControlParameters pointerVelocityControlParameters; // Velocity control parameters for mouse wheel movements. @@ -213,6 +226,9 @@ struct InputReaderConfiguration { // True to enable tap-to-click on touchpads. bool touchpadTapToClickEnabled; + // True to enable tap dragging on touchpads. + bool touchpadTapDraggingEnabled; + // True to enable a zone on the right-hand side of touchpads where clicks will be turned into // context (a.k.a. "right") clicks. bool touchpadRightClickZoneEnabled; @@ -229,6 +245,8 @@ struct InputReaderConfiguration { InputReaderConfiguration() : virtualKeyQuietTime(0), + mousePointerSpeed(0), + displaysWithMousePointerAccelerationDisabled(), pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f, static_cast<float>( android::os::IInputConstants:: @@ -251,6 +269,7 @@ struct InputReaderConfiguration { touchpadPointerSpeed(0), touchpadNaturalScrollingEnabled(true), touchpadTapToClickEnabled(true), + touchpadTapDraggingEnabled(false), touchpadRightClickZoneEnabled(false), stylusButtonMotionEventsEnabled(true), stylusPointerIconEnabled(false) {} diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index f954370928..ba586d7a18 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -13,6 +13,7 @@ // limitations under the License. package { + default_team: "trendy_team_input_framework", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_native_license" diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index f7bbc511f9..3ca691efba 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -810,14 +810,20 @@ void EventHub::Device::trackInputEvent(const struct input_event& event) { case EV_SYN: { switch (event.code) { case SYN_REPORT: - currentFrameDropped = false; + if (currentFrameDropped) { + // To recover after a SYN_DROPPED, we need to query the state of the device + // to synchronize our device state with the kernel's to account for the + // dropped events on receiving the next SYN_REPORT. + // Note we don't drop the SYN_REPORT at this point but it is used by the + // InputDevice to reset and repopulate mapper state + readDeviceState(); + currentFrameDropped = false; + } break; case SYN_DROPPED: // When we receive SYN_DROPPED, all events in the current frame should be - // dropped. We query the state of the device to synchronize our device state - // with the kernel's to account for the dropped events. + // dropped up to and including next SYN_REPORT currentFrameDropped = true; - readDeviceState(); break; default: break; @@ -1141,6 +1147,22 @@ status_t EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* return OK; } +base::Result<std::vector<int32_t>> EventHub::getMtSlotValues(int32_t deviceId, int32_t axis, + size_t slotCount) const { + std::scoped_lock _l(mLock); + const Device* device = getDeviceLocked(deviceId); + if (device == nullptr || !device->hasValidFd() || !device->absBitmask.test(axis)) { + return base::ResultError("device problem or axis not supported", NAME_NOT_FOUND); + } + std::vector<int32_t> outValues(slotCount + 1); + outValues[0] = axis; + const size_t bufferSize = outValues.size() * sizeof(int32_t); + if (ioctl(device->fd, EVIOCGMTSLOTS(bufferSize), outValues.data()) != OK) { + return base::ErrnoError(); + } + return std::move(outValues); +} + bool EventHub::markSupportedKeyCodes(int32_t deviceId, const std::vector<int32_t>& keyCodes, uint8_t* outFlags) const { std::scoped_lock _l(mLock); diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index fb32f96482..2baf576903 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -251,6 +251,7 @@ std::list<NotifyArgs> InputDevice::configureInternal(nsecs_t when, mAssociatedDeviceType = getValueByKey(readerConfig.deviceTypeAssociations, mIdentifier.location); mIsWaking = mConfiguration.getBool("device.wake").value_or(false); + mShouldSmoothScroll = mConfiguration.getBool("device.viewBehavior_smoothScroll"); } if (!changes.any() || changes.test(Change::DEVICE_ALIAS)) { @@ -264,6 +265,8 @@ std::list<NotifyArgs> InputDevice::configureInternal(nsecs_t when, } if (!changes.any() || changes.test(Change::DISPLAY_INFO)) { + const auto oldAssociatedDisplayId = getAssociatedDisplayId(); + // In most situations, no port or name will be specified. mAssociatedDisplayPort = std::nullopt; mAssociatedDisplayUniqueId = std::nullopt; @@ -305,6 +308,10 @@ std::list<NotifyArgs> InputDevice::configureInternal(nsecs_t when, getName().c_str(), mAssociatedDisplayUniqueId->c_str()); } } + + if (getAssociatedDisplayId() != oldAssociatedDisplayId) { + bumpGeneration(); + } } for_each_mapper([this, when, &readerConfig, changes, &out](InputMapper& mapper) { @@ -350,6 +357,7 @@ std::list<NotifyArgs> InputDevice::process(const RawEvent* rawEvents, size_t cou if (mDropUntilNextSync) { if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { + out += reset(rawEvent->when); mDropUntilNextSync = false; ALOGD_IF(debugRawEvents(), "Recovered from input event buffer overrun."); } else { @@ -359,7 +367,6 @@ std::list<NotifyArgs> InputDevice::process(const RawEvent* rawEvents, size_t cou } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) { ALOGI("Detected input event buffer overrun for device %s.", getName().c_str()); mDropUntilNextSync = true; - out += reset(rawEvent->when); } else { for_each_mapper_in_subdevice(rawEvent->deviceId, [&](InputMapper& mapper) { out += mapper.process(rawEvent); @@ -401,7 +408,8 @@ std::list<NotifyArgs> InputDevice::updateExternalStylusState(const StylusState& InputDeviceInfo InputDevice::getDeviceInfo() { InputDeviceInfo outDeviceInfo; outDeviceInfo.initialize(mId, mGeneration, mControllerNumber, mIdentifier, mAlias, mIsExternal, - mHasMic, getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE)); + mHasMic, getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE), + {mShouldSmoothScroll}); for_each_mapper( [&outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(outDeviceInfo); }); diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 0582649d38..9608210ca0 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -164,19 +164,6 @@ void InputReader::loopOnce() { std::swap(notifyArgs, mPendingArgs); } // release lock - // Send out a message that the describes the changed input devices. - if (inputDevicesChanged) { - mPolicy->notifyInputDevicesChanged(inputDevices); - } - - // Notify the policy of the start of every new stylus gesture outside the lock. - for (const auto& args : notifyArgs) { - const auto* motionArgs = std::get_if<NotifyMotionArgs>(&args); - if (motionArgs != nullptr && isStylusPointerGestureStart(*motionArgs)) { - mPolicy->notifyStylusGestureStarted(motionArgs->deviceId, motionArgs->eventTime); - } - } - // Flush queued events out to the listener. // This must happen outside of the lock because the listener could potentially call // back into the InputReader's methods, such as getScanCodeState, or become blocked @@ -187,6 +174,21 @@ void InputReader::loopOnce() { for (const NotifyArgs& args : notifyArgs) { mNextListener.notify(args); } + + // Notify the policy that input devices have changed. + // This must be done after flushing events down the listener chain to ensure that the rest of + // the listeners are synchronized with the changes before the policy reacts to them. + if (inputDevicesChanged) { + mPolicy->notifyInputDevicesChanged(inputDevices); + } + + // Notify the policy of the start of every new stylus gesture. + for (const auto& args : notifyArgs) { + const auto* motionArgs = std::get_if<NotifyMotionArgs>(&args); + if (motionArgs != nullptr && isStylusPointerGestureStart(*motionArgs)) { + mPolicy->notifyStylusGestureStarted(motionArgs->deviceId, motionArgs->eventTime); + } + } } std::list<NotifyArgs> InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) { diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index 0bcab42417..a7e06756d0 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -336,6 +336,10 @@ public: virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0; virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const = 0; + /* Query Multi-Touch slot values for an axis. Returns error or an 1 indexed array of size + * (slotCount + 1). The value at the 0 index is set to queried axis. */ + virtual base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis, + size_t slotCount) const = 0; virtual int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const = 0; /* @@ -552,6 +556,8 @@ public: int32_t locationKeyCode) const override final; status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const override final; + base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis, + size_t slotCount) const override final; bool markSupportedKeyCodes(int32_t deviceId, const std::vector<int32_t>& keyCodes, uint8_t* outFlags) const override final; diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 31dcb2e7a8..0719b0ce3c 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -199,6 +199,7 @@ private: std::optional<DisplayViewport> mAssociatedViewport; bool mHasMic; bool mDropUntilNextSync; + std::optional<bool> mShouldSmoothScroll; typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code); int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc); @@ -280,7 +281,7 @@ private: class InputDeviceContext { public: InputDeviceContext(InputDevice& device, int32_t eventHubId); - ~InputDeviceContext(); + virtual ~InputDeviceContext(); inline InputReaderContext* getContext() { return mContext; } inline int32_t getId() { return mDeviceId; } @@ -372,6 +373,10 @@ public: inline status_t getAbsoluteAxisValue(int32_t code, int32_t* outValue) const { return mEventHub->getAbsoluteAxisValue(mId, code, outValue); } + inline base::Result<std::vector<int32_t>> getMtSlotValues(int32_t axis, + size_t slotCount) const { + return mEventHub->getMtSlotValues(mId, axis, slotCount); + } inline bool markSupportedKeyCodes(const std::vector<int32_t>& keyCodes, uint8_t* outFlags) const { return mEventHub->markSupportedKeyCodes(mId, keyCodes, outFlags); @@ -450,7 +455,7 @@ public: inline std::optional<std::string> getDeviceTypeAssociation() const { return mDevice.getDeviceTypeAssociation(); } - inline std::optional<DisplayViewport> getAssociatedViewport() const { + virtual std::optional<DisplayViewport> getAssociatedViewport() const { return mDevice.getAssociatedViewport(); } [[nodiscard]] inline std::list<NotifyArgs> cancelTouch(nsecs_t when, nsecs_t readTime) { diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index 58e35a6aba..45f09ae32a 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -20,9 +20,12 @@ #include "CursorInputMapper.h" -#include <com_android_input_flags.h> #include <optional> +#include <com_android_input_flags.h> +#include <ftl/enum.h> +#include <input/AccelerationCurve.h> + #include "CursorButtonAccumulator.h" #include "CursorScrollAccumulator.h" #include "PointerControllerInterface.h" @@ -75,7 +78,8 @@ CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig) : InputMapper(deviceContext, readerConfig), mLastEventTime(std::numeric_limits<nsecs_t>::min()), - mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()) {} + mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()), + mEnableNewMousePointerBallistics(input_flags::enable_new_mouse_pointer_ballistics()) {} CursorInputMapper::~CursorInputMapper() { if (mPointerController != nullptr) { @@ -133,7 +137,7 @@ void CursorInputMapper::dump(std::string& dump) { dump += StringPrintf(INDENT3 "VWheelScale: %0.3f\n", mVWheelScale); dump += StringPrintf(INDENT3 "HWheelScale: %0.3f\n", mHWheelScale); dump += StringPrintf(INDENT3 "DisplayId: %s\n", toString(mDisplayId).c_str()); - dump += StringPrintf(INDENT3 "Orientation: %d\n", mOrientation); + dump += StringPrintf(INDENT3 "Orientation: %s\n", ftl::enum_string(mOrientation).c_str()); dump += StringPrintf(INDENT3 "ButtonState: 0x%08x\n", mButtonState); dump += StringPrintf(INDENT3 "Down: %s\n", toString(isPointerDown(mButtonState))); dump += StringPrintf(INDENT3 "DownTime: %" PRId64 "\n", mDownTime); @@ -156,15 +160,16 @@ std::list<NotifyArgs> CursorInputMapper::reconfigure(nsecs_t when, out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId())); } - if (!changes.any() || changes.test(InputReaderConfiguration::Change::POINTER_SPEED) || - configurePointerCapture) { - configureOnChangePointerSpeed(readerConfig); - } - if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO) || configurePointerCapture) { configureOnChangeDisplayInfo(readerConfig); } + + // Pointer speed settings depend on display settings. + if (!changes.any() || changes.test(InputReaderConfiguration::Change::POINTER_SPEED) || + changes.test(InputReaderConfiguration::Change::DISPLAY_INFO) || configurePointerCapture) { + configureOnChangePointerSpeed(readerConfig); + } return out; } @@ -204,7 +209,8 @@ std::list<NotifyArgs> CursorInputMapper::reset(nsecs_t when) { mDownTime = 0; mLastEventTime = std::numeric_limits<nsecs_t>::min(); - mPointerVelocityControl.reset(); + mOldPointerVelocityControl.reset(); + mNewPointerVelocityControl.reset(); mWheelXVelocityControl.reset(); mWheelYVelocityControl.reset(); @@ -282,7 +288,11 @@ std::list<NotifyArgs> CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { mWheelYVelocityControl.move(when, nullptr, &vscroll); mWheelXVelocityControl.move(when, &hscroll, nullptr); - mPointerVelocityControl.move(when, &deltaX, &deltaY); + if (mEnableNewMousePointerBallistics) { + mNewPointerVelocityControl.move(when, &deltaX, &deltaY); + } else { + mOldPointerVelocityControl.move(when, &deltaX, &deltaY); + } float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; @@ -492,11 +502,27 @@ void CursorInputMapper::configureOnPointerCapture(const InputReaderConfiguration void CursorInputMapper::configureOnChangePointerSpeed(const InputReaderConfiguration& config) { if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) { // Disable any acceleration or scaling for the pointer when Pointer Capture is enabled. - mPointerVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); + if (mEnableNewMousePointerBallistics) { + mNewPointerVelocityControl.setAccelerationEnabled(false); + } else { + mOldPointerVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); + } mWheelXVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); mWheelYVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); } else { - mPointerVelocityControl.setParameters(config.pointerVelocityControlParameters); + if (mEnableNewMousePointerBallistics) { + mNewPointerVelocityControl.setAccelerationEnabled( + config.displaysWithMousePointerAccelerationDisabled.count( + mDisplayId.value_or(ADISPLAY_ID_NONE)) == 0); + mNewPointerVelocityControl.setCurve( + createAccelerationCurveForPointerSensitivity(config.mousePointerSpeed)); + } else { + mOldPointerVelocityControl.setParameters( + (config.displaysWithMousePointerAccelerationDisabled.count( + mDisplayId.value_or(ADISPLAY_ID_NONE)) == 0) + ? config.pointerVelocityControlParameters + : FLAT_VELOCITY_CONTROL_PARAMS); + } mWheelXVelocityControl.setParameters(config.wheelVelocityControlParameters); mWheelYVelocityControl.setParameters(config.wheelVelocityControlParameters); } diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index 308adaa463..1ddf6f2b5b 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -26,7 +26,6 @@ namespace android { -class VelocityControl; class PointerControllerInterface; class CursorButtonAccumulator; @@ -111,9 +110,10 @@ private: // Velocity controls for mouse pointer and wheel movements. // The controls for X and Y wheel movements are separate to keep them decoupled. - VelocityControl mPointerVelocityControl; - VelocityControl mWheelXVelocityControl; - VelocityControl mWheelYVelocityControl; + SimpleVelocityControl mOldPointerVelocityControl; + CurvedVelocityControl mNewPointerVelocityControl; + SimpleVelocityControl mWheelXVelocityControl; + SimpleVelocityControl mWheelYVelocityControl; // The display that events generated by this mapper should target. This can be set to // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e. @@ -129,6 +129,7 @@ private: nsecs_t mLastEventTime; const bool mEnablePointerChoreographer; + const bool mEnableNewMousePointerBallistics; explicit CursorInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig); diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index f068cc8aa4..738517b67e 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -20,6 +20,7 @@ #include "KeyboardInputMapper.h" +#include <ftl/enum.h> #include <ui/Rotation.h> namespace android { @@ -61,6 +62,36 @@ static bool isSupportedScanCode(int32_t scanCode) { scanCode >= BTN_WHEEL; } +static bool isMediaKey(int32_t keyCode) { + switch (keyCode) { + case AKEYCODE_MEDIA_PLAY: + case AKEYCODE_MEDIA_PAUSE: + case AKEYCODE_MEDIA_PLAY_PAUSE: + case AKEYCODE_MUTE: + case AKEYCODE_HEADSETHOOK: + case AKEYCODE_MEDIA_STOP: + case AKEYCODE_MEDIA_NEXT: + case AKEYCODE_MEDIA_PREVIOUS: + case AKEYCODE_MEDIA_REWIND: + case AKEYCODE_MEDIA_RECORD: + case AKEYCODE_MEDIA_FAST_FORWARD: + case AKEYCODE_MEDIA_SKIP_FORWARD: + case AKEYCODE_MEDIA_SKIP_BACKWARD: + case AKEYCODE_MEDIA_STEP_FORWARD: + case AKEYCODE_MEDIA_STEP_BACKWARD: + case AKEYCODE_MEDIA_AUDIO_TRACK: + case AKEYCODE_VOLUME_UP: + case AKEYCODE_VOLUME_DOWN: + case AKEYCODE_VOLUME_MUTE: + case AKEYCODE_TV_AUDIO_DESCRIPTION: + case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP: + case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN: + return true; + default: + return false; + } +} + // --- KeyboardInputMapper --- KeyboardInputMapper::KeyboardInputMapper(InputDeviceContext& deviceContext, @@ -113,7 +144,7 @@ void KeyboardInputMapper::dump(std::string& dump) { dump += INDENT2 "Keyboard Input Mapper:\n"; dumpParameters(dump); dump += StringPrintf(INDENT3 "KeyboardType: %d\n", mKeyboardType); - dump += StringPrintf(INDENT3 "Orientation: %d\n", getOrientation()); + dump += StringPrintf(INDENT3 "Orientation: %s\n", ftl::enum_string(getOrientation()).c_str()); dump += StringPrintf(INDENT3 "KeyDowns: %zu keys currently down\n", mKeyDowns.size()); dump += StringPrintf(INDENT3 "MetaState: 0x%0x\n", mMetaState); dump += INDENT3 "KeyboardLayoutInfo: "; @@ -301,7 +332,8 @@ std::list<NotifyArgs> KeyboardInputMapper::processKey(nsecs_t when, nsecs_t read // For internal keyboards and devices for which the default wake behavior is explicitly // prevented (e.g. TV remotes), the key layout file should specify the policy flags for each // wake key individually. - if (down && getDeviceContext().isExternal() && !mParameters.doNotWakeByDefault) { + if (down && getDeviceContext().isExternal() && !mParameters.doNotWakeByDefault && + !(mKeyboardType != AINPUT_KEYBOARD_TYPE_ALPHABETIC && isMediaKey(keyCode))) { policyFlags |= POLICY_FLAG_WAKE; } diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp index 5a74a42446..0c58dabd55 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp @@ -35,10 +35,8 @@ MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext, MultiTouchInputMapper::~MultiTouchInputMapper() {} std::list<NotifyArgs> MultiTouchInputMapper::reset(nsecs_t when) { - // TODO(b/291626046): Sync the MT state with the kernel using EVIOCGMTSLOTS. - mMultiTouchMotionAccumulator.reset(getDeviceContext()); mPointerIdBits.clear(); - + mMultiTouchMotionAccumulator.reset(mDeviceContext); return TouchInputMapper::reset(when); } diff --git a/services/inputflinger/reader/mapper/SlopController.cpp b/services/inputflinger/reader/mapper/SlopController.cpp index f79219f151..9ec02a6f86 100644 --- a/services/inputflinger/reader/mapper/SlopController.cpp +++ b/services/inputflinger/reader/mapper/SlopController.cpp @@ -54,11 +54,13 @@ float SlopController::consumeEvent(nsecs_t eventTimeNanos, float value) { mCumulativeValue += value; if (abs(mCumulativeValue) >= mSlopThreshold) { + ALOGD("SlopController: did not drop event with value .%3f", value); mHasSlopBeenMet = true; // Return the amount of value that exceeds the slop. return signOf(value) * (abs(mCumulativeValue) - mSlopThreshold); } + ALOGD("SlopController: dropping event with value .%3f", value); return 0; } diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index bd9371d263..4b39e4099c 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -708,9 +708,9 @@ private: } mPointerSimple; // The pointer and scroll velocity controls. - VelocityControl mPointerVelocityControl; - VelocityControl mWheelXVelocityControl; - VelocityControl mWheelYVelocityControl; + SimpleVelocityControl mPointerVelocityControl; + SimpleVelocityControl mWheelXVelocityControl; + SimpleVelocityControl mWheelYVelocityControl; std::optional<DisplayViewport> findViewport(); diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 34ca0b3767..b990dd5e79 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -21,12 +21,15 @@ #include <iterator> #include <limits> #include <map> +#include <mutex> #include <optional> #include <android-base/stringprintf.h> +#include <android-base/thread_annotations.h> #include <android/input.h> #include <com_android_input_flags.h> #include <ftl/enum.h> +#include <input/AccelerationCurve.h> #include <input/PrintTools.h> #include <linux/input-event-codes.h> #include <log/log_main.h> @@ -53,27 +56,10 @@ 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 = { - {32.002, 3.19, 0}, - {52.83, 4.79, -51.254}, - {119.124, 7.28, -182.737}, - {std::numeric_limits<double>::infinity(), 15.04, -1107.556}, -}; - -const std::vector<double> sensitivityFactors = {1, 2, 4, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 16, 18, 20}; - std::vector<double> createAccelerationCurveForSensitivity(int32_t sensitivity, size_t propertySize) { + std::vector<AccelerationCurveSegment> segments = + createAccelerationCurveForPointerSensitivity(sensitivity); LOG_ALWAYS_FATAL_IF(propertySize < 4 * segments.size()); std::vector<double> output(propertySize, 0); @@ -83,31 +69,23 @@ std::vector<double> createAccelerationCurveForSensitivity(int32_t sensitivity, // // (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) + // createAccelerationCurveForPointerSensitivity gives us parameters for a function of the form: + // gain(input_speed_mm) = baseGain + reciprocal / 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 + // output_speed(input_speed_mm) = input_speed_mm * (baseGain + reciprocal / input_speed_mm) + // then expand the brackets so that input_speed_mm cancels out for the reciprocal term: + // gain(input_speed_mm) = baseGain * input_speed_mm + reciprocal // // 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; + // b = baseGain + // c = reciprocal size_t i = 0; - for (CurveSegment seg : segments) { + for (AccelerationCurveSegment 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") @@ -116,8 +94,8 @@ std::vector<double> createAccelerationCurveForSensitivity(int32_t sensitivity, // (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; + output[i + 2] = seg.baseGain; + output[i + 3] = seg.reciprocal; i += 4; } @@ -156,13 +134,20 @@ public: return sAccumulator; } - void recordFinger(const TouchpadInputMapper::MetricsIdentifier& id) { mCounters[id].fingers++; } + void recordFinger(const TouchpadInputMapper::MetricsIdentifier& id) { + std::scoped_lock lock(mLock); + mCounters[id].fingers++; + } - void recordPalm(const TouchpadInputMapper::MetricsIdentifier& id) { mCounters[id].palms++; } + void recordPalm(const TouchpadInputMapper::MetricsIdentifier& id) { + std::scoped_lock lock(mLock); + mCounters[id].palms++; + } // Checks whether a Gesture struct is for the end of a gesture that we log metrics for, and // records it if so. void processGesture(const TouchpadInputMapper::MetricsIdentifier& id, const Gesture& gesture) { + std::scoped_lock lock(mLock); switch (gesture.type) { case kGestureTypeFling: if (gesture.details.fling.fling_state == GESTURES_FLING_START) { @@ -200,15 +185,20 @@ private: void* cookie) { LOG_ALWAYS_FATAL_IF(atomTag != android::util::TOUCHPAD_USAGE); MetricsAccumulator& accumulator = MetricsAccumulator::getInstance(); - accumulator.produceAtoms(outEventList); - accumulator.resetCounters(); + accumulator.produceAtomsAndReset(*outEventList); return AStatsManager_PULL_SUCCESS; } - void produceAtoms(AStatsEventList* outEventList) const { + void produceAtomsAndReset(AStatsEventList& outEventList) { + std::scoped_lock lock(mLock); + produceAtomsLocked(outEventList); + resetCountersLocked(); + } + + void produceAtomsLocked(AStatsEventList& outEventList) const REQUIRES(mLock) { for (auto& [id, counters] : mCounters) { auto [busId, vendorId, productId, versionId] = id; - addAStatsEvent(outEventList, android::util::TOUCHPAD_USAGE, vendorId, productId, + addAStatsEvent(&outEventList, android::util::TOUCHPAD_USAGE, vendorId, productId, versionId, linuxBusToInputDeviceBusEnum(busId, /*isUsi=*/false), counters.fingers, counters.palms, counters.twoFingerSwipeGestures, counters.threeFingerSwipeGestures, counters.fourFingerSwipeGestures, @@ -216,7 +206,7 @@ private: } } - void resetCounters() { mCounters.clear(); } + void resetCountersLocked() REQUIRES(mLock) { mCounters.clear(); } // Stores the counters for a specific touchpad model. Fields have the same meanings as those of // the TouchpadUsage atom; see that definition for detailed documentation. @@ -232,7 +222,10 @@ private: // Metrics are aggregated by device model and version, so if two devices of the same model and // version are connected at once, they will have the same counters. - std::map<TouchpadInputMapper::MetricsIdentifier, Counters> mCounters; + std::map<TouchpadInputMapper::MetricsIdentifier, Counters> mCounters GUARDED_BY(mLock); + + // Metrics are pulled by a binder thread, so we need to guard them with a mutex. + mutable std::mutex mLock; }; } // namespace @@ -382,6 +375,8 @@ std::list<NotifyArgs> TouchpadInputMapper::reconfigure(nsecs_t when, : FloatRect{0, 0, 0, 0}; } mGestureConverter.setBoundsInLogicalDisplay(*boundsInLogicalDisplay); + + bumpGeneration(); } if (!changes.any() || changes.test(InputReaderConfiguration::Change::TOUCHPAD_SETTINGS)) { mPropertyProvider.getProperty("Use Custom Touchpad Pointer Accel Curve") @@ -402,6 +397,8 @@ std::list<NotifyArgs> TouchpadInputMapper::reconfigure(nsecs_t when, .setBoolValues({config.touchpadNaturalScrollingEnabled}); mPropertyProvider.getProperty("Tap Enable") .setBoolValues({config.touchpadTapToClickEnabled}); + mPropertyProvider.getProperty("Tap Drag Enable") + .setBoolValues({config.touchpadTapDraggingEnabled}); mPropertyProvider.getProperty("Button Right Click Zone Enable") .setBoolValues({config.touchpadRightClickZoneEnabled}); } @@ -449,6 +446,9 @@ std::list<NotifyArgs> TouchpadInputMapper::process(const RawEvent* rawEvent) { if (mPointerCaptured) { return mCapturedEventConverter.process(*rawEvent); } + if (mMotionAccumulator.getActiveSlotsCount() == 0) { + mGestureStartTime = rawEvent->when; + } std::optional<SelfContainedHardwareState> state = mStateConverter.processRawEvent(rawEvent); if (state) { updatePalmDetectionMetrics(); @@ -514,7 +514,7 @@ std::list<NotifyArgs> TouchpadInputMapper::processGestures(nsecs_t when, nsecs_t if (mDisplayId) { MetricsAccumulator& metricsAccumulator = MetricsAccumulator::getInstance(); for (Gesture& gesture : mGesturesToProcess) { - out += mGestureConverter.handleGesture(when, readTime, gesture); + out += mGestureConverter.handleGesture(when, readTime, mGestureStartTime, gesture); metricsAccumulator.processGesture(mMetricsId, gesture); } } diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index ece0eca0e7..897edca4e1 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -113,6 +113,8 @@ private: // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e. // std::nullopt), all events will be ignored. std::optional<int32_t> mDisplayId; + + nsecs_t mGestureStartTime{0}; }; } // namespace android diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp index d06514acab..b3f170075c 100644 --- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp @@ -30,30 +30,12 @@ void MultiTouchMotionAccumulator::configure(const InputDeviceContext& deviceCont size_t slotCount, bool usingSlotsProtocol) { mUsingSlotsProtocol = usingSlotsProtocol; mSlots = std::vector<Slot>(slotCount); - reset(deviceContext); + populateCurrentSlot(deviceContext); } void MultiTouchMotionAccumulator::reset(const InputDeviceContext& deviceContext) { resetSlots(); - - if (!mUsingSlotsProtocol) { - return; - } - - // Query the driver for the current slot index and use it as the initial slot before we - // start reading events from the device. It is possible that the current slot index will - // not be the same as it was when the first event was written into the evdev buffer, which - // means the input mapper could start out of sync with the initial state of the events in - // the evdev buffer. In the extremely unlikely case that this happens, the data from two - // slots will be confused until the next ABS_MT_SLOT event is received. This can cause the - // touch point to "jump", but at least there will be no stuck touches. - int32_t initialSlot; - if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); - status == OK) { - mCurrentSlot = initialSlot; - } else { - ALOGD("Could not retrieve current multi-touch slot index. status=%d", status); - } + syncSlots(deviceContext); } void MultiTouchMotionAccumulator::resetSlots() { @@ -90,54 +72,10 @@ void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { if (!mUsingSlotsProtocol) { slot.mInUse = true; } - - switch (rawEvent->code) { - case ABS_MT_POSITION_X: - slot.mAbsMtPositionX = rawEvent->value; - warnIfNotInUse(*rawEvent, slot); - break; - case ABS_MT_POSITION_Y: - slot.mAbsMtPositionY = rawEvent->value; - warnIfNotInUse(*rawEvent, slot); - break; - case ABS_MT_TOUCH_MAJOR: - slot.mAbsMtTouchMajor = rawEvent->value; - break; - case ABS_MT_TOUCH_MINOR: - slot.mAbsMtTouchMinor = rawEvent->value; - slot.mHaveAbsMtTouchMinor = true; - break; - case ABS_MT_WIDTH_MAJOR: - slot.mAbsMtWidthMajor = rawEvent->value; - break; - case ABS_MT_WIDTH_MINOR: - slot.mAbsMtWidthMinor = rawEvent->value; - slot.mHaveAbsMtWidthMinor = true; - break; - case ABS_MT_ORIENTATION: - slot.mAbsMtOrientation = rawEvent->value; - break; - case ABS_MT_TRACKING_ID: - if (mUsingSlotsProtocol && rawEvent->value < 0) { - // The slot is no longer in use but it retains its previous contents, - // which may be reused for subsequent touches. - slot.mInUse = false; - } else { - slot.mInUse = true; - slot.mAbsMtTrackingId = rawEvent->value; - } - break; - case ABS_MT_PRESSURE: - slot.mAbsMtPressure = rawEvent->value; - break; - case ABS_MT_DISTANCE: - slot.mAbsMtDistance = rawEvent->value; - break; - case ABS_MT_TOOL_TYPE: - slot.mAbsMtToolType = rawEvent->value; - slot.mHaveAbsMtToolType = true; - break; + if (rawEvent->code == ABS_MT_POSITION_X || rawEvent->code == ABS_MT_POSITION_Y) { + warnIfNotInUse(*rawEvent, slot); } + slot.populateAxisValue(rawEvent->code, rawEvent->value); } } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) { // MultiTouch Sync: The driver has returned all data for *one* of the pointers. @@ -145,6 +83,36 @@ void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { } } +void MultiTouchMotionAccumulator::syncSlots(const InputDeviceContext& deviceContext) { + if (!mUsingSlotsProtocol) { + return; + } + constexpr std::array<int32_t, 11> axisCodes = {ABS_MT_POSITION_X, ABS_MT_POSITION_Y, + ABS_MT_TOUCH_MAJOR, ABS_MT_TOUCH_MINOR, + ABS_MT_WIDTH_MAJOR, ABS_MT_WIDTH_MINOR, + ABS_MT_ORIENTATION, ABS_MT_TRACKING_ID, + ABS_MT_PRESSURE, ABS_MT_DISTANCE, + ABS_MT_TOOL_TYPE}; + const size_t numSlots = mSlots.size(); + for (int32_t axisCode : axisCodes) { + if (!deviceContext.hasAbsoluteAxis(axisCode)) { + continue; + } + const auto result = deviceContext.getMtSlotValues(axisCode, numSlots); + if (result.ok()) { + const std::vector<int32_t>& mtSlotValues = result.value(); + for (size_t i = 1; i <= numSlots; ++i) { + // The returned slot values are in a 1-indexed vector of size numSlots + 1. + mSlots[i - 1].populateAxisValue(axisCode, mtSlotValues[i]); + } + } else { + ALOGE("Could not retrieve multi-touch slot value for axis=%d error=%s status=%d", + axisCode, result.error().message().c_str(), result.error().code().value()); + } + } + populateCurrentSlot(deviceContext); +} + void MultiTouchMotionAccumulator::finishSync() { if (!mUsingSlotsProtocol) { resetSlots(); @@ -166,6 +134,21 @@ size_t MultiTouchMotionAccumulator::getActiveSlotsCount() const { [](const Slot& slot) { return slot.mInUse; }); } +void MultiTouchMotionAccumulator::populateCurrentSlot( + const android::InputDeviceContext& deviceContext) { + if (!mUsingSlotsProtocol) { + return; + } + int32_t initialSlot; + if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); + status == OK) { + mCurrentSlot = initialSlot; + } else { + ALOGE("Could not retrieve current multi-touch slot index. status=%s", + statusToString(status).c_str()); + } +} + // --- MultiTouchMotionAccumulator::Slot --- ToolType MultiTouchMotionAccumulator::Slot::getToolType() const { @@ -182,4 +165,52 @@ ToolType MultiTouchMotionAccumulator::Slot::getToolType() const { return ToolType::UNKNOWN; } +void MultiTouchMotionAccumulator::Slot::populateAxisValue(int32_t axisCode, int32_t value) { + switch (axisCode) { + case ABS_MT_POSITION_X: + mAbsMtPositionX = value; + break; + case ABS_MT_POSITION_Y: + mAbsMtPositionY = value; + break; + case ABS_MT_TOUCH_MAJOR: + mAbsMtTouchMajor = value; + break; + case ABS_MT_TOUCH_MINOR: + mAbsMtTouchMinor = value; + mHaveAbsMtTouchMinor = true; + break; + case ABS_MT_WIDTH_MAJOR: + mAbsMtWidthMajor = value; + break; + case ABS_MT_WIDTH_MINOR: + mAbsMtWidthMinor = value; + mHaveAbsMtWidthMinor = true; + break; + case ABS_MT_ORIENTATION: + mAbsMtOrientation = value; + break; + case ABS_MT_TRACKING_ID: + if (value < 0) { + // The slot is no longer in use but it retains its previous contents, + // which may be reused for subsequent touches. + mInUse = false; + } else { + mInUse = true; + mAbsMtTrackingId = value; + } + break; + case ABS_MT_PRESSURE: + mAbsMtPressure = value; + break; + case ABS_MT_DISTANCE: + mAbsMtDistance = value; + break; + case ABS_MT_TOOL_TYPE: + mAbsMtToolType = value; + mHaveAbsMtToolType = true; + break; + } +} + } // namespace android diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h index 5b55e3d599..a0f21470c4 100644 --- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h @@ -68,12 +68,14 @@ public: int32_t mAbsMtToolType = 0; void clear() { *this = Slot(); } + void populateAxisValue(int32_t axisCode, int32_t value); }; MultiTouchMotionAccumulator(); void configure(const InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol); + void reset(const InputDeviceContext& deviceContext); void process(const RawEvent* rawEvent); void finishSync(); @@ -83,15 +85,16 @@ public: LOG_ALWAYS_FATAL_IF(index < 0 || index >= mSlots.size(), "Invalid index: %zu", index); return mSlots[index]; } - void reset(const InputDeviceContext& deviceContext); private: - int32_t mCurrentSlot; + int32_t mCurrentSlot{-1}; std::vector<Slot> mSlots; bool mUsingSlotsProtocol; void resetSlots(); + void syncSlots(const InputDeviceContext& deviceContext); void warnIfNotInUse(const RawEvent& event, const Slot& slot); + void populateCurrentSlot(const android::InputDeviceContext& deviceContext); }; } // namespace android diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp index 955210479f..19788cee40 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp @@ -35,8 +35,14 @@ namespace android { namespace { +// This will disable the tap to click while the user is typing on a physical keyboard const bool ENABLE_TOUCHPAD_PALM_REJECTION = input_flags::enable_touchpad_typing_palm_rejection(); +// In addition to v1, v2 will also cancel ongoing move gestures while typing and add delay in +// re-enabling the tap to click. +const bool ENABLE_TOUCHPAD_PALM_REJECTION_V2 = + input_flags::enable_v2_touchpad_typing_palm_rejection(); + uint32_t gesturesButtonToMotionEventButton(uint32_t gesturesButton) { switch (gesturesButton) { case GESTURES_BUTTON_LEFT: @@ -75,6 +81,8 @@ std::string GestureConverter::dump() const { out << StringPrintf("Button state: 0x%08x\n", mButtonState); out << "Down time: " << mDownTime << "\n"; out << "Current classification: " << ftl::enum_string(mCurrentClassification) << "\n"; + out << "Is hovering: " << mIsHovering << "\n"; + out << "Enable Tap Timestamp: " << mWhenToEnableTapToClick << "\n"; return out.str(); } @@ -82,7 +90,7 @@ std::list<NotifyArgs> GestureConverter::reset(nsecs_t when) { std::list<NotifyArgs> out; switch (mCurrentClassification) { case MotionClassification::TWO_FINGER_SWIPE: - out.push_back(endScroll(when, when)); + out += endScroll(when, when); break; case MotionClassification::MULTI_FINGER_SWIPE: out += handleMultiFingerSwipeLift(when, when); @@ -109,8 +117,6 @@ std::list<NotifyArgs> GestureConverter::reset(nsecs_t when) { void GestureConverter::populateMotionRanges(InputDeviceInfo& info) const { info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0.0f, 1.0f, 0, 0, 0); - // TODO(b/259547750): set this using the raw axis ranges from the touchpad when pointer capture - // is enabled. if (!mBoundsInLogicalDisplay.isEmpty()) { info.addMotionRange(AMOTION_EVENT_AXIS_X, SOURCE, mBoundsInLogicalDisplay.left, mBoundsInLogicalDisplay.right, 0, 0, 0); @@ -131,6 +137,7 @@ void GestureConverter::populateMotionRanges(InputDeviceInfo& info) const { } std::list<NotifyArgs> GestureConverter::handleGesture(nsecs_t when, nsecs_t readTime, + nsecs_t gestureStartTime, const Gesture& gesture) { if (!mDisplayId) { // Ignore gestures when there is no target display configured. @@ -139,13 +146,13 @@ std::list<NotifyArgs> GestureConverter::handleGesture(nsecs_t when, nsecs_t read switch (gesture.type) { case kGestureTypeMove: - return {handleMove(when, readTime, gesture)}; + return handleMove(when, readTime, gestureStartTime, gesture); case kGestureTypeButtonsChange: return handleButtonsChange(when, readTime, gesture); case kGestureTypeScroll: return handleScroll(when, readTime, gesture); case kGestureTypeFling: - return handleFling(when, readTime, gesture); + return handleFling(when, readTime, gestureStartTime, gesture); case kGestureTypeSwipe: return handleMultiFingerSwipe(when, readTime, 3, gesture.details.swipe.dx, gesture.details.swipe.dy); @@ -162,35 +169,66 @@ std::list<NotifyArgs> GestureConverter::handleGesture(nsecs_t when, nsecs_t read } } -NotifyMotionArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime, - const Gesture& gesture) { +std::list<NotifyArgs> GestureConverter::handleMove(nsecs_t when, nsecs_t readTime, + nsecs_t gestureStartTime, + const Gesture& gesture) { float deltaX = gesture.details.move.dx; float deltaY = gesture.details.move.dy; - if (ENABLE_TOUCHPAD_PALM_REJECTION && (std::abs(deltaX) > 0 || std::abs(deltaY) > 0)) { - enableTapToClick(); + const auto [oldXCursorPosition, oldYCursorPosition] = + mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition(); + if (ENABLE_TOUCHPAD_PALM_REJECTION_V2) { + bool wasHoverCancelled = mIsHoverCancelled; + // Gesture will be cancelled if it started before the user started typing and + // there is a active IME connection. + mIsHoverCancelled = gestureStartTime <= mReaderContext.getLastKeyDownTimestamp() && + mReaderContext.getPolicy()->isInputMethodConnectionActive(); + + if (!wasHoverCancelled && mIsHoverCancelled) { + // This is the first event of the cancelled gesture, we won't return because we need to + // generate a HOVER_EXIT event + mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); + return exitHover(when, readTime, oldXCursorPosition, oldYCursorPosition); + } else if (mIsHoverCancelled) { + return {}; + } } + rotateDelta(mOrientation, &deltaX, &deltaY); - mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); - mPointerController->move(deltaX, deltaY); - mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); + // Update the cursor, and enable tap to click if the gesture is not cancelled + if (!mIsHoverCancelled) { + // handleFling calls hoverMove with zero delta on FLING_TAP_DOWN. Don't enable tap to click + // for this case as subsequent handleButtonsChange may choose to ignore this tap. + if ((ENABLE_TOUCHPAD_PALM_REJECTION || ENABLE_TOUCHPAD_PALM_REJECTION_V2) && + (std::abs(deltaX) > 0 || std::abs(deltaY) > 0)) { + enableTapToClick(when); + } + mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); + mPointerController->move(deltaX, deltaY); + mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); + } - const auto [xCursorPosition, yCursorPosition] = + std::list<NotifyArgs> out; + const bool down = isPointerDown(mButtonState); + if (!down) { + out += enterHover(when, readTime, oldXCursorPosition, oldYCursorPosition); + } + const auto [newXCursorPosition, newYCursorPosition] = mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition(); PointerCoords coords; coords.clear(); - coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); - coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); + coords.setAxisValue(AMOTION_EVENT_AXIS_X, newXCursorPosition); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, newYCursorPosition); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY); - const bool down = isPointerDown(mButtonState); coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f); const int32_t action = down ? AMOTION_EVENT_ACTION_MOVE : AMOTION_EVENT_ACTION_HOVER_MOVE; - return makeMotionArgs(when, readTime, action, /* actionButton= */ 0, mButtonState, - /* pointerCount= */ 1, mFingerProps.data(), &coords, xCursorPosition, - yCursorPosition); + out.push_back(makeMotionArgs(when, readTime, action, /*actionButton=*/0, mButtonState, + /*pointerCount=*/1, &coords, newXCursorPosition, + newYCursorPosition)); + return out; } std::list<NotifyArgs> GestureConverter::handleButtonsChange(nsecs_t when, nsecs_t readTime, @@ -210,8 +248,15 @@ std::list<NotifyArgs> GestureConverter::handleButtonsChange(nsecs_t when, nsecs_ coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0); - if (ENABLE_TOUCHPAD_PALM_REJECTION && mReaderContext.isPreventingTouchpadTaps()) { - enableTapToClick(); + // V2 palm rejection should override V1 + if (ENABLE_TOUCHPAD_PALM_REJECTION_V2) { + enableTapToClick(when); + if (gesture.details.buttons.is_tap && when <= mWhenToEnableTapToClick) { + // return early to prevent this tap + return out; + } + } else if (ENABLE_TOUCHPAD_PALM_REJECTION && mReaderContext.isPreventingTouchpadTaps()) { + enableTapToClick(when); if (gesture.details.buttons.is_tap) { // return early to prevent this tap return out; @@ -232,16 +277,16 @@ std::list<NotifyArgs> GestureConverter::handleButtonsChange(nsecs_t when, nsecs_ newButtonState |= actionButton; pressEvents.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, newButtonState, - /* pointerCount= */ 1, mFingerProps.data(), - &coords, xCursorPosition, yCursorPosition)); + /*pointerCount=*/1, &coords, xCursorPosition, + yCursorPosition)); } } if (!isPointerDown(mButtonState) && isPointerDown(newButtonState)) { mDownTime = when; + out += exitHover(when, readTime, xCursorPosition, yCursorPosition); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0, newButtonState, /* pointerCount= */ 1, - mFingerProps.data(), &coords, xCursorPosition, - yCursorPosition)); + &coords, xCursorPosition, yCursorPosition)); } out.splice(out.end(), pressEvents); @@ -257,20 +302,16 @@ std::list<NotifyArgs> GestureConverter::handleButtonsChange(nsecs_t when, nsecs_ newButtonState &= ~actionButton; out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, newButtonState, /* pointerCount= */ 1, - mFingerProps.data(), &coords, xCursorPosition, - yCursorPosition)); + &coords, xCursorPosition, yCursorPosition)); } } if (isPointerDown(mButtonState) && !isPointerDown(newButtonState)) { coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0, - newButtonState, /* pointerCount= */ 1, mFingerProps.data(), - &coords, xCursorPosition, yCursorPosition)); - // Send a HOVER_MOVE to tell the application that the mouse is hovering again. - out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_HOVER_MOVE, - /*actionButton=*/0, newButtonState, /*pointerCount=*/1, - mFingerProps.data(), &coords, xCursorPosition, - yCursorPosition)); + newButtonState, /* pointerCount= */ 1, &coords, + xCursorPosition, yCursorPosition)); + mButtonState = newButtonState; + out += enterHover(when, readTime, xCursorPosition, yCursorPosition); } mButtonState = newButtonState; return out; @@ -295,18 +336,18 @@ std::list<NotifyArgs> GestureConverter::releaseAllButtons(nsecs_t when, nsecs_t if (mButtonState & button) { newButtonState &= ~button; out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE, - button, newButtonState, /*pointerCount=*/1, - mFingerProps.data(), &coords, xCursorPosition, - yCursorPosition)); + button, newButtonState, /*pointerCount=*/1, &coords, + xCursorPosition, yCursorPosition)); } } + mButtonState = 0; if (pointerDown) { coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0, - newButtonState, /*pointerCount=*/1, mFingerProps.data(), - &coords, xCursorPosition, yCursorPosition)); + mButtonState, /*pointerCount=*/1, &coords, xCursorPosition, + yCursorPosition)); + out += enterHover(when, readTime, xCursorPosition, yCursorPosition); } - mButtonState = 0; return out; } @@ -317,6 +358,8 @@ std::list<NotifyArgs> GestureConverter::handleScroll(nsecs_t when, nsecs_t readT const auto [xCursorPosition, yCursorPosition] = mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition(); if (mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) { + out += exitHover(when, readTime, xCursorPosition, yCursorPosition); + mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE; coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); @@ -324,8 +367,8 @@ std::list<NotifyArgs> GestureConverter::handleScroll(nsecs_t when, nsecs_t readT mDownTime = when; NotifyMotionArgs args = makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0, - mButtonState, /* pointerCount= */ 1, mFingerProps.data(), - mFakeFingerCoords.data(), xCursorPosition, yCursorPosition); + mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data(), + xCursorPosition, yCursorPosition); args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE; out.push_back(args); } @@ -340,14 +383,15 @@ std::list<NotifyArgs> GestureConverter::handleScroll(nsecs_t when, nsecs_t readT coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, -gesture.details.scroll.dy); NotifyMotionArgs args = makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0, - mButtonState, /* pointerCount= */ 1, mFingerProps.data(), - mFakeFingerCoords.data(), xCursorPosition, yCursorPosition); + mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data(), + xCursorPosition, yCursorPosition); args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE; out.push_back(args); return out; } std::list<NotifyArgs> GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, + nsecs_t gestureStartTime, const Gesture& gesture) { switch (gesture.details.fling.fling_state) { case GESTURES_FLING_START: @@ -356,7 +400,7 @@ std::list<NotifyArgs> GestureConverter::handleFling(nsecs_t when, nsecs_t readTi // ensure consistency between touchscreen and touchpad flings), so we're just using // the "start fling" gestures as a marker for the end of a two-finger scroll // gesture. - return {endScroll(when, readTime)}; + return endScroll(when, readTime); } break; case GESTURES_FLING_TAP_DOWN: @@ -366,13 +410,10 @@ std::list<NotifyArgs> GestureConverter::handleFling(nsecs_t when, nsecs_t readTi // magnitude, which will also result in the pointer icon being updated. // TODO(b/282023644): Add a signal in libgestures for when a stable contact has been // initiated with a touchpad. - if (!mReaderContext.isPreventingTouchpadTaps()) { - enableTapToClick(); - } - return {handleMove(when, readTime, - Gesture(kGestureMove, gesture.start_time, gesture.end_time, - /*dx=*/0.f, - /*dy=*/0.f))}; + return handleMove(when, readTime, gestureStartTime, + Gesture(kGestureMove, gesture.start_time, gesture.end_time, + /*dx=*/0.f, + /*dy=*/0.f)); } break; default: @@ -382,18 +423,21 @@ std::list<NotifyArgs> GestureConverter::handleFling(nsecs_t when, nsecs_t readTi return {}; } -NotifyMotionArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) { +std::list<NotifyArgs> GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) { + std::list<NotifyArgs> out; const auto [xCursorPosition, yCursorPosition] = mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition(); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, 0); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, 0); NotifyMotionArgs args = makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0, - mButtonState, /* pointerCount= */ 1, mFingerProps.data(), - mFakeFingerCoords.data(), xCursorPosition, yCursorPosition); + mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data(), + xCursorPosition, yCursorPosition); args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE; + out.push_back(args); mCurrentClassification = MotionClassification::NONE; - return args; + out += enterHover(when, readTime, xCursorPosition, yCursorPosition); + return out; } [[nodiscard]] std::list<NotifyArgs> GestureConverter::handleMultiFingerSwipe(nsecs_t when, @@ -409,7 +453,11 @@ NotifyMotionArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) { // three and then put a fourth finger down), the gesture library will treat it as two // separate swipes with an appropriate lift event between them, so we don't have to worry // about the finger count changing mid-swipe. + + out += exitHover(when, readTime, xCursorPosition, yCursorPosition); + mCurrentClassification = MotionClassification::MULTI_FINGER_SWIPE; + mSwipeFingerCount = fingerCount; constexpr float FAKE_FINGER_SPACING = 100; @@ -428,16 +476,14 @@ NotifyMotionArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) { fingerCount); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1, - mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, - yCursorPosition)); + mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); for (size_t i = 1; i < mSwipeFingerCount; i++) { out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_POINTER_DOWN | (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), /* actionButton= */ 0, mButtonState, - /* pointerCount= */ i + 1, mFingerProps.data(), - mFakeFingerCoords.data(), xCursorPosition, - yCursorPosition)); + /* pointerCount= */ i + 1, mFakeFingerCoords.data(), + xCursorPosition, yCursorPosition)); } } float rotatedDeltaX = dx, rotatedDeltaY = -dy; @@ -455,8 +501,7 @@ NotifyMotionArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) { mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0, mButtonState, /* pointerCount= */ mSwipeFingerCount, - mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, - yCursorPosition)); + mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); return out; } @@ -476,15 +521,14 @@ NotifyMotionArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) { AMOTION_EVENT_ACTION_POINTER_UP | ((i - 1) << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), /* actionButton= */ 0, mButtonState, /* pointerCount= */ i, - mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, - yCursorPosition)); + mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); } out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1, - mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, - yCursorPosition)); + mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SWIPE_FINGER_COUNT, 0); mCurrentClassification = MotionClassification::NONE; + out += enterHover(when, readTime, xCursorPosition, yCursorPosition); mSwipeFingerCount = 0; return out; } @@ -503,6 +547,10 @@ NotifyMotionArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) { LOG_ALWAYS_FATAL_IF(gesture.details.pinch.zoom_state != GESTURES_ZOOM_START, "First pinch gesture does not have the START zoom state (%d instead).", gesture.details.pinch.zoom_state); + std::list<NotifyArgs> out; + + out += exitHover(when, readTime, xCursorPosition, yCursorPosition); + mCurrentClassification = MotionClassification::PINCH; mPinchFingerSeparation = INITIAL_PINCH_SEPARATION_PX; mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0); @@ -515,17 +563,14 @@ NotifyMotionArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) { mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); mDownTime = when; - std::list<NotifyArgs> out; out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1, - mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, - yCursorPosition)); + mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_POINTER_DOWN | 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT, /* actionButton= */ 0, mButtonState, /* pointerCount= */ 2, - mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, - yCursorPosition)); + mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); return out; } @@ -543,8 +588,8 @@ NotifyMotionArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) { xCursorPosition + mPinchFingerSeparation / 2); mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); return {makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /*actionButton=*/0, - mButtonState, /*pointerCount=*/2, mFingerProps.data(), - mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)}; + mButtonState, /*pointerCount=*/2, mFakeFingerCoords.data(), + xCursorPosition, yCursorPosition)}; } std::list<NotifyArgs> GestureConverter::endPinch(nsecs_t when, nsecs_t readTime) { @@ -557,20 +602,53 @@ std::list<NotifyArgs> GestureConverter::endPinch(nsecs_t when, nsecs_t readTime) AMOTION_EVENT_ACTION_POINTER_UP | 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT, /*actionButton=*/0, mButtonState, /*pointerCount=*/2, - mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, - yCursorPosition)); - out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0, - mButtonState, /*pointerCount=*/1, mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); - mCurrentClassification = MotionClassification::NONE; + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0, + mButtonState, /*pointerCount=*/1, mFakeFingerCoords.data(), + xCursorPosition, yCursorPosition)); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 0); + mCurrentClassification = MotionClassification::NONE; + out += enterHover(when, readTime, xCursorPosition, yCursorPosition); return out; } +std::list<NotifyArgs> GestureConverter::enterHover(nsecs_t when, nsecs_t readTime, + float xCursorPosition, float yCursorPosition) { + if (!mIsHovering) { + mIsHovering = true; + return {makeHoverEvent(when, readTime, AMOTION_EVENT_ACTION_HOVER_ENTER, xCursorPosition, + yCursorPosition)}; + } else { + return {}; + } +} + +std::list<NotifyArgs> GestureConverter::exitHover(nsecs_t when, nsecs_t readTime, + float xCursorPosition, float yCursorPosition) { + if (mIsHovering) { + mIsHovering = false; + return {makeHoverEvent(when, readTime, AMOTION_EVENT_ACTION_HOVER_EXIT, xCursorPosition, + yCursorPosition)}; + } else { + return {}; + } +} + +NotifyMotionArgs GestureConverter::makeHoverEvent(nsecs_t when, nsecs_t readTime, int32_t action, + float xCursorPosition, float yCursorPosition) { + PointerCoords coords; + coords.clear(); + coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); + coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0); + coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0); + return makeMotionArgs(when, readTime, action, /*actionButton=*/0, mButtonState, + /*pointerCount=*/1, &coords, xCursorPosition, yCursorPosition); +} + NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, int32_t actionButton, int32_t buttonState, uint32_t pointerCount, - const PointerProperties* pointerProperties, const PointerCoords* pointerCoords, float xCursorPosition, float yCursorPosition) { return {mReaderContext.getNextId(), @@ -588,7 +666,7 @@ NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime mCurrentClassification, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, - pointerProperties, + mFingerProps.data(), pointerCoords, /* xPrecision= */ 1.0f, /* yPrecision= */ 1.0f, @@ -598,8 +676,11 @@ NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime /* videoFrames= */ {}}; } -void GestureConverter::enableTapToClick() { - mReaderContext.setPreventingTouchpadTaps(false); +void GestureConverter::enableTapToClick(nsecs_t when) { + if (mReaderContext.isPreventingTouchpadTaps()) { + mWhenToEnableTapToClick = when + TAP_ENABLE_DELAY_NANOS.count(); + mReaderContext.setPreventingTouchpadTaps(false); + } } } // namespace android diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h index 732a4b2ffb..07cc56c618 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h @@ -34,6 +34,13 @@ namespace android { +using std::chrono_literals::operator""ms; +/** + * This duration is decided based on internal team testing, it may be updated after testing with + * larger groups + */ +constexpr std::chrono::nanoseconds TAP_ENABLE_DELAY_NANOS = 400ms; + // Converts Gesture structs from the gestures library into NotifyArgs and the appropriate // PointerController calls. class GestureConverter { @@ -53,19 +60,22 @@ public: void populateMotionRanges(InputDeviceInfo& info) const; [[nodiscard]] std::list<NotifyArgs> handleGesture(nsecs_t when, nsecs_t readTime, + nsecs_t gestureStartTime, const Gesture& gesture); private: - [[nodiscard]] NotifyMotionArgs handleMove(nsecs_t when, nsecs_t readTime, - const Gesture& gesture); + [[nodiscard]] std::list<NotifyArgs> handleMove(nsecs_t when, nsecs_t readTime, + nsecs_t gestureStartTime, + const Gesture& gesture); [[nodiscard]] std::list<NotifyArgs> handleButtonsChange(nsecs_t when, nsecs_t readTime, const Gesture& gesture); [[nodiscard]] std::list<NotifyArgs> releaseAllButtons(nsecs_t when, nsecs_t readTime); [[nodiscard]] std::list<NotifyArgs> handleScroll(nsecs_t when, nsecs_t readTime, const Gesture& gesture); [[nodiscard]] std::list<NotifyArgs> handleFling(nsecs_t when, nsecs_t readTime, + nsecs_t gestureStartTime, const Gesture& gesture); - [[nodiscard]] NotifyMotionArgs endScroll(nsecs_t when, nsecs_t readTime); + [[nodiscard]] std::list<NotifyArgs> endScroll(nsecs_t when, nsecs_t readTime); [[nodiscard]] std::list<NotifyArgs> handleMultiFingerSwipe(nsecs_t when, nsecs_t readTime, uint32_t fingerCount, float dx, @@ -75,14 +85,22 @@ private: const Gesture& gesture); [[nodiscard]] std::list<NotifyArgs> endPinch(nsecs_t when, nsecs_t readTime); + [[nodiscard]] std::list<NotifyArgs> enterHover(nsecs_t when, nsecs_t readTime, + float xCursorPosition, float yCursorPosition); + [[nodiscard]] std::list<NotifyArgs> exitHover(nsecs_t when, nsecs_t readTime, + float xCursorPosition, float yCursorPosition); + + NotifyMotionArgs makeHoverEvent(nsecs_t when, nsecs_t readTime, int32_t action, + float xCursorPosition, float yCursorPosition); + NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, int32_t actionButton, int32_t buttonState, - uint32_t pointerCount, - const PointerProperties* pointerProperties, - const PointerCoords* pointerCoords, float xCursorPosition, - float yCursorPosition); + uint32_t pointerCount, const PointerCoords* pointerCoords, + float xCursorPosition, float yCursorPosition); - void enableTapToClick(); + void enableTapToClick(nsecs_t when); + bool mIsHoverCancelled{false}; + nsecs_t mWhenToEnableTapToClick{0}; const int32_t mDeviceId; InputReaderContext& mReaderContext; @@ -99,6 +117,9 @@ private: // button values (AMOTION_EVENT_BUTTON_...). uint32_t mButtonState = 0; nsecs_t mDownTime = 0; + // Whether we are currently in a hover state (i.e. a HOVER_ENTER event has been sent without a + // matching HOVER_EXIT). + bool mIsHovering = false; MotionClassification mCurrentClassification = MotionClassification::NONE; // Only used when mCurrentClassification is MULTI_FINGER_SWIPE. diff --git a/services/inputflinger/reporter/Android.bp b/services/inputflinger/reporter/Android.bp index b1e1aee02a..e85a10428d 100644 --- a/services/inputflinger/reporter/Android.bp +++ b/services/inputflinger/reporter/Android.bp @@ -13,6 +13,7 @@ // limitations under the License. package { + default_team: "trendy_team_input_framework", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_native_license" diff --git a/services/inputflinger/rust/Android.bp b/services/inputflinger/rust/Android.bp index 2803805619..255c7eb679 100644 --- a/services/inputflinger/rust/Android.bp +++ b/services/inputflinger/rust/Android.bp @@ -13,6 +13,10 @@ // limitations under the License. // Generate the C++ code that Rust calls into. +package { + default_team: "trendy_team_input_framework", +} + genrule { name: "inputflinger_rs_bootstrap_bridge_code", tools: ["cxxbridge"], @@ -42,6 +46,7 @@ rust_defaults { "libbinder_rs", "liblog_rust", "liblogger", + "libnix", ], host_supported: true, } diff --git a/services/inputflinger/rust/bounce_keys_filter.rs b/services/inputflinger/rust/bounce_keys_filter.rs index 894b881638..2d5039a1b1 100644 --- a/services/inputflinger/rust/bounce_keys_filter.rs +++ b/services/inputflinger/rust/bounce_keys_filter.rs @@ -118,6 +118,10 @@ impl Filter for BounceKeysFilter { } self.next.notify_devices_changed(device_infos); } + + fn destroy(&mut self) { + self.next.destroy(); + } } #[cfg(test)] diff --git a/services/inputflinger/rust/input_filter.rs b/services/inputflinger/rust/input_filter.rs index 340ff8e296..a544fa36ae 100644 --- a/services/inputflinger/rust/input_filter.rs +++ b/services/inputflinger/rust/input_filter.rs @@ -22,11 +22,15 @@ use binder::{Interface, Strong}; use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{ DeviceInfo::DeviceInfo, IInputFilter::{IInputFilter, IInputFilterCallbacks::IInputFilterCallbacks}, + IInputThread::{IInputThread, IInputThreadCallback::IInputThreadCallback}, InputFilterConfiguration::InputFilterConfiguration, KeyEvent::KeyEvent, }; use crate::bounce_keys_filter::BounceKeysFilter; +use crate::input_filter_thread::InputFilterThread; +use crate::slow_keys_filter::SlowKeysFilter; +use crate::sticky_keys_filter::StickyKeysFilter; use log::{error, info}; use std::sync::{Arc, Mutex, RwLock}; @@ -34,6 +38,7 @@ use std::sync::{Arc, Mutex, RwLock}; pub trait Filter { fn notify_key(&mut self, event: &KeyEvent); fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]); + fn destroy(&mut self); } struct InputFilterState { @@ -49,6 +54,7 @@ pub struct InputFilter { // Access to mutable references to mutable state (includes access to filters, enabled, etc.) is // guarded by Mutex for thread safety state: Mutex<InputFilterState>, + input_filter_thread: InputFilterThread, } impl Interface for InputFilter {} @@ -66,7 +72,11 @@ impl InputFilter { first_filter: Box<dyn Filter + Send + Sync>, callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>, ) -> InputFilter { - Self { callbacks, state: Mutex::new(InputFilterState { first_filter, enabled: false }) } + Self { + callbacks: callbacks.clone(), + state: Mutex::new(InputFilterState { first_filter, enabled: false }), + input_filter_thread: InputFilterThread::new(InputFilterThreadCreator::new(callbacks)), + } } } @@ -88,16 +98,36 @@ impl IInputFilter for InputFilter { } fn notifyConfigurationChanged(&self, config: &InputFilterConfiguration) -> binder::Result<()> { - let mut state = self.state.lock().unwrap(); - let mut first_filter: Box<dyn Filter + Send + Sync> = - Box::new(BaseFilter::new(self.callbacks.clone())); - if config.bounceKeysThresholdNs > 0 { - first_filter = - Box::new(BounceKeysFilter::new(first_filter, config.bounceKeysThresholdNs)); - state.enabled = true; - info!("Bounce keys filter is installed"); + { + let mut state = self.state.lock().unwrap(); + state.first_filter.destroy(); + let mut first_filter: Box<dyn Filter + Send + Sync> = + Box::new(BaseFilter::new(self.callbacks.clone())); + if config.stickyKeysEnabled { + first_filter = Box::new(StickyKeysFilter::new( + first_filter, + ModifierStateListener::new(self.callbacks.clone()), + )); + state.enabled = true; + info!("Sticky keys filter is installed"); + } + if config.slowKeysThresholdNs > 0 { + first_filter = Box::new(SlowKeysFilter::new( + first_filter, + config.slowKeysThresholdNs, + self.input_filter_thread.clone(), + )); + state.enabled = true; + info!("Slow keys filter is installed"); + } + if config.bounceKeysThresholdNs > 0 { + first_filter = + Box::new(BounceKeysFilter::new(first_filter, config.bounceKeysThresholdNs)); + state.enabled = true; + info!("Bounce keys filter is installed"); + } + state.first_filter = first_filter; } - state.first_filter = first_filter; Result::Ok(()) } } @@ -123,36 +153,69 @@ impl Filter for BaseFilter { fn notify_devices_changed(&mut self, _device_infos: &[DeviceInfo]) { // do nothing } + + fn destroy(&mut self) { + // do nothing + } +} + +/// This struct wraps around IInputFilterCallbacks restricting access to only +/// {@code onModifierStateChanged()} method of the callback. +#[derive(Clone)] +pub struct ModifierStateListener(Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>); + +impl ModifierStateListener { + pub fn new(callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>) -> ModifierStateListener { + Self(callbacks) + } + + pub fn modifier_state_changed(&self, modifier_state: u32, locked_modifier_state: u32) { + let _ = self + .0 + .read() + .unwrap() + .onModifierStateChanged(modifier_state as i32, locked_modifier_state as i32); + } +} + +/// This struct wraps around IInputFilterCallbacks restricting access to only +/// {@code createInputFilterThread()} method of the callback. +#[derive(Clone)] +pub struct InputFilterThreadCreator(Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>); + +impl InputFilterThreadCreator { + pub fn new( + callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>, + ) -> InputFilterThreadCreator { + Self(callbacks) + } + + pub fn create( + &self, + input_thread_callback: &Strong<dyn IInputThreadCallback>, + ) -> Strong<dyn IInputThread> { + self.0.read().unwrap().createInputFilterThread(input_thread_callback).unwrap() + } } #[cfg(test)] mod tests { - use crate::input_filter::{test_filter::TestFilter, Filter, InputFilter}; + use crate::input_filter::{ + test_callbacks::TestCallbacks, test_filter::TestFilter, InputFilter, + }; use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source; - use binder::{Interface, Strong}; + use binder::Strong; use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{ DeviceInfo::DeviceInfo, IInputFilter::IInputFilter, - IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks, InputFilterConfiguration::InputFilterConfiguration, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction, }; use std::sync::{Arc, RwLock}; - struct FakeCallbacks {} - - impl Interface for FakeCallbacks {} - - impl IInputFilterCallbacks for FakeCallbacks { - fn sendKeyEvent(&self, _event: &KeyEvent) -> binder::Result<()> { - Result::Ok(()) - } - } - #[test] fn test_not_enabled_with_default_filter() { - let fake_callbacks: Strong<dyn IInputFilterCallbacks> = - Strong::new(Box::new(FakeCallbacks {})); - let input_filter = InputFilter::new(fake_callbacks); + let test_callbacks = TestCallbacks::new(); + let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks))); let result = input_filter.isEnabled(); assert!(result.is_ok()); assert!(!result.unwrap()); @@ -160,17 +223,21 @@ mod tests { #[test] fn test_notify_key_with_no_filters() { - let fake_callbacks: Strong<dyn IInputFilterCallbacks> = - Strong::new(Box::new(FakeCallbacks {})); - let input_filter = InputFilter::new(fake_callbacks); + let test_callbacks = TestCallbacks::new(); + let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks.clone()))); let event = create_key_event(); assert!(input_filter.notifyKey(&event).is_ok()); + assert_eq!(test_callbacks.last_event().unwrap(), event); } #[test] fn test_notify_key_with_filter() { let test_filter = TestFilter::new(); - let input_filter = create_input_filter(Box::new(test_filter.clone())); + let test_callbacks = TestCallbacks::new(); + let input_filter = InputFilter::create_input_filter( + Box::new(test_filter.clone()), + Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks)))), + ); let event = create_key_event(); assert!(input_filter.notifyKey(&event).is_ok()); assert_eq!(test_filter.last_event().unwrap(), event); @@ -179,7 +246,11 @@ mod tests { #[test] fn test_notify_devices_changed() { let test_filter = TestFilter::new(); - let input_filter = create_input_filter(Box::new(test_filter.clone())); + let test_callbacks = TestCallbacks::new(); + let input_filter = InputFilter::create_input_filter( + Box::new(test_filter.clone()), + Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks)))), + ); assert!(input_filter .notifyInputDevicesChanged(&[DeviceInfo { deviceId: 0, external: true }]) .is_ok()); @@ -188,21 +259,57 @@ mod tests { #[test] fn test_notify_configuration_changed_enabled_bounce_keys() { - let fake_callbacks: Strong<dyn IInputFilterCallbacks> = - Strong::new(Box::new(FakeCallbacks {})); - let input_filter = InputFilter::new(fake_callbacks); - let result = input_filter - .notifyConfigurationChanged(&InputFilterConfiguration { bounceKeysThresholdNs: 100 }); + let test_callbacks = TestCallbacks::new(); + let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks))); + let result = input_filter.notifyConfigurationChanged(&InputFilterConfiguration { + bounceKeysThresholdNs: 100, + ..Default::default() + }); + assert!(result.is_ok()); + let result = input_filter.isEnabled(); + assert!(result.is_ok()); + assert!(result.unwrap()); + } + + #[test] + fn test_notify_configuration_changed_enabled_sticky_keys() { + let test_callbacks = TestCallbacks::new(); + let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks))); + let result = input_filter.notifyConfigurationChanged(&InputFilterConfiguration { + stickyKeysEnabled: true, + ..Default::default() + }); + assert!(result.is_ok()); + let result = input_filter.isEnabled(); + assert!(result.is_ok()); + assert!(result.unwrap()); + } + + #[test] + fn test_notify_configuration_changed_enabled_slow_keys() { + let test_callbacks = TestCallbacks::new(); + let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks))); + let result = input_filter.notifyConfigurationChanged(&InputFilterConfiguration { + slowKeysThresholdNs: 100, + ..Default::default() + }); assert!(result.is_ok()); let result = input_filter.isEnabled(); assert!(result.is_ok()); assert!(result.unwrap()); } - fn create_input_filter(filter: Box<dyn Filter + Send + Sync>) -> InputFilter { - let fake_callbacks: Strong<dyn IInputFilterCallbacks> = - Strong::new(Box::new(FakeCallbacks {})); - InputFilter::create_input_filter(filter, Arc::new(RwLock::new(fake_callbacks))) + #[test] + fn test_notify_configuration_changed_destroys_existing_filters() { + let test_filter = TestFilter::new(); + let test_callbacks = TestCallbacks::new(); + let input_filter = InputFilter::create_input_filter( + Box::new(test_filter.clone()), + Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks)))), + ); + let _ = input_filter + .notifyConfigurationChanged(&InputFilterConfiguration { ..Default::default() }); + assert!(test_filter.is_destroy_called()); } fn create_key_event() -> KeyEvent { @@ -236,6 +343,7 @@ pub mod test_filter { struct TestFilterInner { is_device_changed_called: bool, last_event: Option<KeyEvent>, + is_destroy_called: bool, } #[derive(Default, Clone)] @@ -261,6 +369,10 @@ pub mod test_filter { pub fn is_device_changed_called(&self) -> bool { self.0.read().unwrap().is_device_changed_called } + + pub fn is_destroy_called(&self) -> bool { + self.0.read().unwrap().is_destroy_called + } } impl Filter for TestFilter { @@ -270,5 +382,128 @@ pub mod test_filter { fn notify_devices_changed(&mut self, _device_infos: &[DeviceInfo]) { self.inner().is_device_changed_called = true; } + fn destroy(&mut self) { + self.inner().is_destroy_called = true; + } + } +} + +#[cfg(test)] +pub mod test_callbacks { + use binder::{BinderFeatures, Interface, Strong}; + use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{ + IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks, + IInputThread::{BnInputThread, IInputThread, IInputThreadCallback::IInputThreadCallback}, + KeyEvent::KeyEvent, + }; + use std::sync::{Arc, RwLock, RwLockWriteGuard}; + + #[derive(Default)] + struct TestCallbacksInner { + last_modifier_state: u32, + last_locked_modifier_state: u32, + last_event: Option<KeyEvent>, + test_thread: Option<TestThread>, + } + + #[derive(Default, Clone)] + pub struct TestCallbacks(Arc<RwLock<TestCallbacksInner>>); + + impl Interface for TestCallbacks {} + + impl TestCallbacks { + pub fn new() -> Self { + Default::default() + } + + fn inner(&self) -> RwLockWriteGuard<'_, TestCallbacksInner> { + self.0.write().unwrap() + } + + pub fn last_event(&self) -> Option<KeyEvent> { + self.0.read().unwrap().last_event + } + + pub fn clear(&mut self) { + self.inner().last_event = None; + self.inner().last_modifier_state = 0; + self.inner().last_locked_modifier_state = 0; + } + + pub fn get_last_modifier_state(&self) -> u32 { + self.0.read().unwrap().last_modifier_state + } + + pub fn get_last_locked_modifier_state(&self) -> u32 { + self.0.read().unwrap().last_locked_modifier_state + } + + pub fn is_thread_created(&self) -> bool { + self.0.read().unwrap().test_thread.is_some() + } + + pub fn is_thread_finished(&self) -> bool { + if let Some(test_thread) = &self.0.read().unwrap().test_thread { + return test_thread.is_finish_called(); + } + false + } + } + + impl IInputFilterCallbacks for TestCallbacks { + fn sendKeyEvent(&self, event: &KeyEvent) -> binder::Result<()> { + self.inner().last_event = Some(*event); + Result::Ok(()) + } + + fn onModifierStateChanged( + &self, + modifier_state: i32, + locked_modifier_state: i32, + ) -> std::result::Result<(), binder::Status> { + self.inner().last_modifier_state = modifier_state as u32; + self.inner().last_locked_modifier_state = locked_modifier_state as u32; + Result::Ok(()) + } + + fn createInputFilterThread( + &self, + _callback: &Strong<dyn IInputThreadCallback>, + ) -> std::result::Result<Strong<dyn IInputThread>, binder::Status> { + let test_thread = TestThread::new(); + self.inner().test_thread = Some(test_thread.clone()); + Result::Ok(BnInputThread::new_binder(test_thread, BinderFeatures::default())) + } + } + + #[derive(Default)] + struct TestThreadInner { + is_finish_called: bool, + } + + #[derive(Default, Clone)] + struct TestThread(Arc<RwLock<TestThreadInner>>); + + impl Interface for TestThread {} + + impl TestThread { + pub fn new() -> Self { + Default::default() + } + + fn inner(&self) -> RwLockWriteGuard<'_, TestThreadInner> { + self.0.write().unwrap() + } + + pub fn is_finish_called(&self) -> bool { + self.0.read().unwrap().is_finish_called + } + } + + impl IInputThread for TestThread { + fn finish(&self) -> binder::Result<()> { + self.inner().is_finish_called = true; + Result::Ok(()) + } } } diff --git a/services/inputflinger/rust/input_filter_thread.rs b/services/inputflinger/rust/input_filter_thread.rs new file mode 100644 index 0000000000..2d503aee70 --- /dev/null +++ b/services/inputflinger/rust/input_filter_thread.rs @@ -0,0 +1,452 @@ +/* + * Copyright 2024 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. + */ + +//! Input filter thread implementation in rust. +//! Using IInputFilter.aidl interface to create ever looping thread with JNI support, rest of +//! thread handling is done from rust side. +//! +//! NOTE: Tried using rust provided threading infrastructure but that uses std::thread which doesn't +//! have JNI support and can't call into Java policy that we use currently. libutils provided +//! Thread.h also recommends against using std::thread and using the provided infrastructure that +//! already provides way of attaching JniEnv to the created thread. So, we are using an AIDL +//! interface to expose the InputThread infrastructure to rust. + +use crate::input_filter::InputFilterThreadCreator; +use binder::{BinderFeatures, Interface, Strong}; +use com_android_server_inputflinger::aidl::com::android::server::inputflinger::IInputThread::{ + IInputThread, IInputThreadCallback::BnInputThreadCallback, + IInputThreadCallback::IInputThreadCallback, +}; +use log::{debug, error}; +use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId}; +use std::sync::{Arc, RwLock, RwLockWriteGuard}; +use std::time::Duration; +use std::{thread, thread::Thread}; + +/// Interface to receive callback from Input filter thread +pub trait ThreadCallback { + /// Calls back after the requested timeout expires. + /// {@see InputFilterThread.request_timeout_at_time(...)} + /// + /// NOTE: In case of multiple requests, the timeout request which is earliest in time, will be + /// fulfilled and notified to all the listeners. It's up to the listeners to re-request another + /// timeout in the future. + fn notify_timeout_expired(&self, when_nanos: i64); + /// Unique name for the listener, which will be used to uniquely identify the listener. + fn name(&self) -> &str; +} + +#[derive(Clone)] +pub struct InputFilterThread { + thread_creator: InputFilterThreadCreator, + thread_callback_handler: ThreadCallbackHandler, + inner: Arc<RwLock<InputFilterThreadInner>>, +} + +struct InputFilterThreadInner { + cpp_thread: Option<Strong<dyn IInputThread>>, + looper: Option<Thread>, + next_timeout: i64, + is_finishing: bool, +} + +impl InputFilterThread { + /// Create a new InputFilterThread instance. + /// NOTE: This will create a new thread. Clone the existing instance to reuse the same thread. + pub fn new(thread_creator: InputFilterThreadCreator) -> InputFilterThread { + Self { + thread_creator, + thread_callback_handler: ThreadCallbackHandler::new(), + inner: Arc::new(RwLock::new(InputFilterThreadInner { + cpp_thread: None, + looper: None, + next_timeout: i64::MAX, + is_finishing: false, + })), + } + } + + /// Listener requesting a timeout in future will receive a callback at or before the requested + /// time on the input filter thread. + /// {@see ThreadCallback.notify_timeout_expired(...)} + pub fn request_timeout_at_time(&self, when_nanos: i64) { + let filter_thread = &mut self.filter_thread(); + if when_nanos < filter_thread.next_timeout { + filter_thread.next_timeout = when_nanos; + if let Some(looper) = &filter_thread.looper { + looper.unpark(); + } + } + } + + /// Registers a callback listener. + /// + /// NOTE: If a listener with the same name already exists when registering using + /// {@see InputFilterThread.register_thread_callback(...)}, we will ignore the listener. You + /// must clear any previously registered listeners using + /// {@see InputFilterThread.unregister_thread_callback(...) before registering the new listener. + /// + /// NOTE: Also, registering a callback will start the looper if not already started. + pub fn register_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) { + self.thread_callback_handler.register_thread_callback(callback); + self.start(); + } + + /// Unregisters a callback listener. + /// + /// NOTE: Unregistering a callback will stop the looper if not other callback registered. + pub fn unregister_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) { + self.thread_callback_handler.unregister_thread_callback(callback); + // Stop the thread if no registered callbacks exist. We will recreate the thread when new + // callbacks are registered. + let has_callbacks = self.thread_callback_handler.has_callbacks(); + if !has_callbacks { + self.stop(); + } + } + + fn start(&self) { + debug!("InputFilterThread: start thread"); + let filter_thread = &mut self.filter_thread(); + if filter_thread.cpp_thread.is_none() { + filter_thread.cpp_thread = Some(self.thread_creator.create( + &BnInputThreadCallback::new_binder(self.clone(), BinderFeatures::default()), + )); + filter_thread.looper = None; + filter_thread.is_finishing = false; + } + } + + fn stop(&self) { + debug!("InputFilterThread: stop thread"); + let filter_thread = &mut self.filter_thread(); + filter_thread.is_finishing = true; + if let Some(looper) = &filter_thread.looper { + looper.unpark(); + } + if let Some(cpp_thread) = &filter_thread.cpp_thread { + let _ = cpp_thread.finish(); + } + // Clear all references + filter_thread.cpp_thread = None; + filter_thread.looper = None; + } + + fn loop_once(&self, now: i64) { + let mut wake_up_time = i64::MAX; + let mut timeout_expired = false; + { + // acquire thread lock + let filter_thread = &mut self.filter_thread(); + if filter_thread.is_finishing { + // Thread is finishing so don't block processing on it and let it loop. + return; + } + if filter_thread.next_timeout != i64::MAX { + if filter_thread.next_timeout <= now { + timeout_expired = true; + filter_thread.next_timeout = i64::MAX; + } else { + wake_up_time = filter_thread.next_timeout; + } + } + if filter_thread.looper.is_none() { + filter_thread.looper = Some(std::thread::current()); + } + } // release thread lock + if timeout_expired { + self.thread_callback_handler.notify_timeout_expired(now); + } + if wake_up_time == i64::MAX { + thread::park(); + } else { + let duration_now = Duration::from_nanos(now as u64); + let duration_wake_up = Duration::from_nanos(wake_up_time as u64); + thread::park_timeout(duration_wake_up - duration_now); + } + } + + fn filter_thread(&self) -> RwLockWriteGuard<'_, InputFilterThreadInner> { + self.inner.write().unwrap() + } +} + +impl Interface for InputFilterThread {} + +impl IInputThreadCallback for InputFilterThread { + fn loopOnce(&self) -> binder::Result<()> { + self.loop_once(clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds()); + Result::Ok(()) + } +} + +#[derive(Default, Clone)] +struct ThreadCallbackHandler(Arc<RwLock<ThreadCallbackHandlerInner>>); + +#[derive(Default)] +struct ThreadCallbackHandlerInner { + callbacks: Vec<Box<dyn ThreadCallback + Send + Sync>>, +} + +impl ThreadCallbackHandler { + fn new() -> Self { + Default::default() + } + + fn has_callbacks(&self) -> bool { + !&self.0.read().unwrap().callbacks.is_empty() + } + + fn register_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) { + let callbacks = &mut self.0.write().unwrap().callbacks; + if callbacks.iter().any(|x| x.name() == callback.name()) { + error!( + "InputFilterThread: register_thread_callback, callback {:?} already exists!", + callback.name() + ); + return; + } + debug!( + "InputFilterThread: register_thread_callback, callback {:?} added!", + callback.name() + ); + callbacks.push(callback); + } + + fn unregister_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) { + let callbacks = &mut self.0.write().unwrap().callbacks; + if let Some(index) = callbacks.iter().position(|x| x.name() == callback.name()) { + callbacks.remove(index); + debug!( + "InputFilterThread: unregister_thread_callback, callback {:?} removed!", + callback.name() + ); + return; + } + error!( + "InputFilterThread: unregister_thread_callback, callback {:?} doesn't exist", + callback.name() + ); + } + + fn notify_timeout_expired(&self, when_nanos: i64) { + let callbacks = &self.0.read().unwrap().callbacks; + for callback in callbacks.iter() { + callback.notify_timeout_expired(when_nanos); + } + } +} + +#[cfg(test)] +mod tests { + use crate::input_filter::test_callbacks::TestCallbacks; + use crate::input_filter_thread::{ + test_thread::TestThread, test_thread_callback::TestThreadCallback, + }; + + #[test] + fn test_register_callback_creates_cpp_thread() { + let test_callbacks = TestCallbacks::new(); + let test_thread = TestThread::new(test_callbacks.clone()); + let test_thread_callback = TestThreadCallback::new(); + test_thread.register_thread_callback(test_thread_callback); + assert!(test_callbacks.is_thread_created()); + } + + #[test] + fn test_unregister_callback_finishes_cpp_thread() { + let test_callbacks = TestCallbacks::new(); + let test_thread = TestThread::new(test_callbacks.clone()); + let test_thread_callback = TestThreadCallback::new(); + test_thread.register_thread_callback(test_thread_callback.clone()); + test_thread.unregister_thread_callback(test_thread_callback); + assert!(test_callbacks.is_thread_finished()); + } + + #[test] + fn test_notify_timeout_called_after_timeout_expired() { + let test_callbacks = TestCallbacks::new(); + let test_thread = TestThread::new(test_callbacks.clone()); + let test_thread_callback = TestThreadCallback::new(); + test_thread.register_thread_callback(test_thread_callback.clone()); + test_thread.start_looper(); + + test_thread.request_timeout_at_time(500); + test_thread.dispatch_next(); + + test_thread.move_time_forward(500); + + test_thread.stop_looper(); + assert!(test_thread_callback.is_notify_timeout_called()); + } + + #[test] + fn test_notify_timeout_not_called_before_timeout_expired() { + let test_callbacks = TestCallbacks::new(); + let test_thread = TestThread::new(test_callbacks.clone()); + let test_thread_callback = TestThreadCallback::new(); + test_thread.register_thread_callback(test_thread_callback.clone()); + test_thread.start_looper(); + + test_thread.request_timeout_at_time(500); + test_thread.dispatch_next(); + + test_thread.move_time_forward(100); + + test_thread.stop_looper(); + assert!(!test_thread_callback.is_notify_timeout_called()); + } +} + +#[cfg(test)] +pub mod test_thread { + + use crate::input_filter::{test_callbacks::TestCallbacks, InputFilterThreadCreator}; + use crate::input_filter_thread::{test_thread_callback::TestThreadCallback, InputFilterThread}; + use binder::Strong; + use std::sync::{ + atomic::AtomicBool, atomic::AtomicI64, atomic::Ordering, Arc, RwLock, RwLockWriteGuard, + }; + use std::time::Duration; + + #[derive(Clone)] + pub struct TestThread { + input_thread: InputFilterThread, + inner: Arc<RwLock<TestThreadInner>>, + exit_flag: Arc<AtomicBool>, + now: Arc<AtomicI64>, + } + + struct TestThreadInner { + join_handle: Option<std::thread::JoinHandle<()>>, + } + + impl TestThread { + pub fn new(callbacks: TestCallbacks) -> TestThread { + Self { + input_thread: InputFilterThread::new(InputFilterThreadCreator::new(Arc::new( + RwLock::new(Strong::new(Box::new(callbacks))), + ))), + inner: Arc::new(RwLock::new(TestThreadInner { join_handle: None })), + exit_flag: Arc::new(AtomicBool::new(false)), + now: Arc::new(AtomicI64::new(0)), + } + } + + fn inner(&self) -> RwLockWriteGuard<'_, TestThreadInner> { + self.inner.write().unwrap() + } + + pub fn get_input_thread(&self) -> InputFilterThread { + self.input_thread.clone() + } + + pub fn register_thread_callback(&self, thread_callback: TestThreadCallback) { + self.input_thread.register_thread_callback(Box::new(thread_callback)); + } + + pub fn unregister_thread_callback(&self, thread_callback: TestThreadCallback) { + self.input_thread.unregister_thread_callback(Box::new(thread_callback)); + } + + pub fn start_looper(&self) { + self.exit_flag.store(false, Ordering::Relaxed); + let clone = self.clone(); + let join_handle = std::thread::Builder::new() + .name("test_thread".to_string()) + .spawn(move || { + while !clone.exit_flag.load(Ordering::Relaxed) { + clone.loop_once(); + } + }) + .unwrap(); + self.inner().join_handle = Some(join_handle); + // Sleep until the looper thread starts + std::thread::sleep(Duration::from_millis(10)); + } + + pub fn stop_looper(&self) { + self.exit_flag.store(true, Ordering::Relaxed); + { + let mut inner = self.inner(); + if let Some(join_handle) = &inner.join_handle { + join_handle.thread().unpark(); + } + inner.join_handle.take().map(std::thread::JoinHandle::join); + inner.join_handle = None; + } + self.exit_flag.store(false, Ordering::Relaxed); + } + + pub fn move_time_forward(&self, value: i64) { + let _ = self.now.fetch_add(value, Ordering::Relaxed); + self.dispatch_next(); + } + + pub fn dispatch_next(&self) { + if let Some(join_handle) = &self.inner().join_handle { + join_handle.thread().unpark(); + } + // Sleep until the looper thread runs a loop + std::thread::sleep(Duration::from_millis(10)); + } + + fn loop_once(&self) { + self.input_thread.loop_once(self.now.load(Ordering::Relaxed)); + } + + pub fn request_timeout_at_time(&self, when_nanos: i64) { + self.input_thread.request_timeout_at_time(when_nanos); + } + } +} + +#[cfg(test)] +pub mod test_thread_callback { + use crate::input_filter_thread::ThreadCallback; + use std::sync::{Arc, RwLock, RwLockWriteGuard}; + + #[derive(Default)] + struct TestThreadCallbackInner { + is_notify_timeout_called: bool, + } + + #[derive(Default, Clone)] + pub struct TestThreadCallback(Arc<RwLock<TestThreadCallbackInner>>); + + impl TestThreadCallback { + pub fn new() -> Self { + Default::default() + } + + fn inner(&self) -> RwLockWriteGuard<'_, TestThreadCallbackInner> { + self.0.write().unwrap() + } + + pub fn is_notify_timeout_called(&self) -> bool { + self.0.read().unwrap().is_notify_timeout_called + } + } + + impl ThreadCallback for TestThreadCallback { + fn notify_timeout_expired(&self, _when_nanos: i64) { + self.inner().is_notify_timeout_called = true; + } + fn name(&self) -> &str { + "TestThreadCallback" + } + } +} diff --git a/services/inputflinger/rust/lib.rs b/services/inputflinger/rust/lib.rs index 5abef68a75..4af7b84a21 100644 --- a/services/inputflinger/rust/lib.rs +++ b/services/inputflinger/rust/lib.rs @@ -21,6 +21,9 @@ mod bounce_keys_filter; mod input_filter; +mod input_filter_thread; +mod slow_keys_filter; +mod sticky_keys_filter; use crate::input_filter::InputFilter; use binder::{ @@ -62,9 +65,9 @@ mod ffi { /// /// # Safety /// -/// This function is safe iff `callback` is a valid pointer to an `AIBinder` interface of type -/// `IInputFlingerRustBootstrapCallback`. The pointer must have had its reference count manually -/// incremented using `AIBinder_incStrong`. See `binder::unstable_api::new_spibinder`. +/// The provided `callback` must be a valid pointer to an `AIBinder` interface of type +/// `IInputFlingerRustBootstrapCallback`, and the caller must give this function ownership of one +/// strong refcount to the interface. See `binder::unstable_api::new_spibinder`. unsafe fn create_inputflinger_rust(callback: *mut ffi::IInputFlingerRustBootstrapCallbackAIBinder) { logger::init( logger::Config::default() diff --git a/services/inputflinger/rust/slow_keys_filter.rs b/services/inputflinger/rust/slow_keys_filter.rs new file mode 100644 index 0000000000..01165b57fa --- /dev/null +++ b/services/inputflinger/rust/slow_keys_filter.rs @@ -0,0 +1,382 @@ +/* + * Copyright 2024 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. + */ + +//! Slow keys input filter implementation. +//! Slow keys is an accessibility feature to aid users who have physical disabilities, that allows +//! the user to specify the duration for which one must press-and-hold a key before the system +//! accepts the keypress. +use crate::input_filter::Filter; +use crate::input_filter_thread::{InputFilterThread, ThreadCallback}; +use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source; +use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{ + DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction, +}; +use log::debug; +use std::collections::HashSet; +use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; + +#[derive(Debug)] +struct OngoingKeyDown { + scancode: i32, + device_id: i32, + down_time: i64, +} + +struct SlowKeysFilterInner { + next: Box<dyn Filter + Send + Sync>, + slow_key_threshold_ns: i64, + external_devices: HashSet<i32>, + // This tracks KeyEvents that are blocked by Slow keys filter and will be passed through if the + // press duration exceeds the slow keys threshold. + pending_down_events: Vec<KeyEvent>, + // This tracks KeyEvent streams that have press duration greater than the slow keys threshold, + // hence any future ACTION_DOWN (if repeats are handled on HW side) or ACTION_UP are allowed to + // pass through without waiting. + ongoing_down_events: Vec<OngoingKeyDown>, + input_filter_thread: InputFilterThread, +} + +#[derive(Clone)] +pub struct SlowKeysFilter(Arc<RwLock<SlowKeysFilterInner>>); + +impl SlowKeysFilter { + /// Create a new SlowKeysFilter instance. + pub fn new( + next: Box<dyn Filter + Send + Sync>, + slow_key_threshold_ns: i64, + input_filter_thread: InputFilterThread, + ) -> SlowKeysFilter { + let filter = Self(Arc::new(RwLock::new(SlowKeysFilterInner { + next, + slow_key_threshold_ns, + external_devices: HashSet::new(), + pending_down_events: Vec::new(), + ongoing_down_events: Vec::new(), + input_filter_thread: input_filter_thread.clone(), + }))); + input_filter_thread.register_thread_callback(Box::new(filter.clone())); + filter + } + + fn read_inner(&self) -> RwLockReadGuard<'_, SlowKeysFilterInner> { + self.0.read().unwrap() + } + + fn write_inner(&self) -> RwLockWriteGuard<'_, SlowKeysFilterInner> { + self.0.write().unwrap() + } + + fn request_next_callback(&self) { + let slow_filter = &self.read_inner(); + if slow_filter.pending_down_events.is_empty() { + return; + } + if let Some(event) = slow_filter.pending_down_events.iter().min_by_key(|x| x.downTime) { + slow_filter.input_filter_thread.request_timeout_at_time(event.downTime); + } + } +} + +impl Filter for SlowKeysFilter { + fn notify_key(&mut self, event: &KeyEvent) { + { + // acquire write lock + let mut slow_filter = self.write_inner(); + if !(slow_filter.external_devices.contains(&event.deviceId) + && event.source == Source::KEYBOARD) + { + slow_filter.next.notify_key(event); + return; + } + // Pass all events through if key down has already been processed + // Do update the downtime before sending the events through + if let Some(index) = slow_filter + .ongoing_down_events + .iter() + .position(|x| x.device_id == event.deviceId && x.scancode == event.scanCode) + { + let mut new_event = *event; + new_event.downTime = slow_filter.ongoing_down_events[index].down_time; + slow_filter.next.notify_key(&new_event); + if event.action == KeyEventAction::UP { + slow_filter.ongoing_down_events.remove(index); + } + return; + } + match event.action { + KeyEventAction::DOWN => { + if slow_filter + .pending_down_events + .iter() + .any(|x| x.deviceId == event.deviceId && x.scanCode == event.scanCode) + { + debug!("Dropping key down event since another pending down event exists"); + return; + } + let mut pending_event = *event; + pending_event.downTime += slow_filter.slow_key_threshold_ns; + pending_event.eventTime = pending_event.downTime; + slow_filter.pending_down_events.push(pending_event); + } + KeyEventAction::UP => { + debug!("Dropping key up event due to insufficient press duration"); + if let Some(index) = slow_filter + .pending_down_events + .iter() + .position(|x| x.deviceId == event.deviceId && x.scanCode == event.scanCode) + { + slow_filter.pending_down_events.remove(index); + } + } + _ => (), + } + } // release write lock + self.request_next_callback(); + } + + fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]) { + let mut slow_filter = self.write_inner(); + slow_filter + .pending_down_events + .retain(|event| device_infos.iter().any(|x| event.deviceId == x.deviceId)); + slow_filter + .ongoing_down_events + .retain(|event| device_infos.iter().any(|x| event.device_id == x.deviceId)); + slow_filter.external_devices.clear(); + for device_info in device_infos { + if device_info.external { + slow_filter.external_devices.insert(device_info.deviceId); + } + } + slow_filter.next.notify_devices_changed(device_infos); + } + + fn destroy(&mut self) { + let mut slow_filter = self.write_inner(); + slow_filter.input_filter_thread.unregister_thread_callback(Box::new(self.clone())); + slow_filter.next.destroy(); + } +} + +impl ThreadCallback for SlowKeysFilter { + fn notify_timeout_expired(&self, when_nanos: i64) { + { + // acquire write lock + let slow_filter = &mut self.write_inner(); + for event in slow_filter.pending_down_events.clone() { + if event.downTime <= when_nanos { + slow_filter.next.notify_key(&event); + slow_filter.ongoing_down_events.push(OngoingKeyDown { + scancode: event.scanCode, + device_id: event.deviceId, + down_time: event.downTime, + }); + } + } + slow_filter.pending_down_events.retain(|event| event.downTime > when_nanos); + } // release write lock + self.request_next_callback(); + } + + fn name(&self) -> &str { + "slow_keys_filter" + } +} + +#[cfg(test)] +mod tests { + use crate::input_filter::{test_callbacks::TestCallbacks, test_filter::TestFilter, Filter}; + use crate::input_filter_thread::test_thread::TestThread; + use crate::slow_keys_filter::SlowKeysFilter; + use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source; + use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{ + DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction, + }; + + static BASE_KEY_EVENT: KeyEvent = KeyEvent { + id: 1, + deviceId: 1, + downTime: 0, + readTime: 0, + eventTime: 0, + source: Source::KEYBOARD, + displayId: 0, + policyFlags: 0, + action: KeyEventAction::DOWN, + flags: 0, + keyCode: 1, + scanCode: 0, + metaState: 0, + }; + + #[test] + fn test_is_notify_key_for_internal_keyboard_not_blocked() { + let test_callbacks = TestCallbacks::new(); + let test_thread = TestThread::new(test_callbacks.clone()); + let next = TestFilter::new(); + let mut filter = setup_filter_with_internal_device( + Box::new(next.clone()), + test_thread.clone(), + 1, /* device_id */ + 100, /* threshold */ + ); + test_thread.start_looper(); + + let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT }; + filter.notify_key(&event); + assert_eq!(next.last_event().unwrap(), event); + } + + #[test] + fn test_is_notify_key_for_external_stylus_not_blocked() { + let test_callbacks = TestCallbacks::new(); + let test_thread = TestThread::new(test_callbacks.clone()); + let next = TestFilter::new(); + let mut filter = setup_filter_with_external_device( + Box::new(next.clone()), + test_thread.clone(), + 1, /* device_id */ + 100, /* threshold */ + ); + test_thread.start_looper(); + + let event = + KeyEvent { action: KeyEventAction::DOWN, source: Source::STYLUS, ..BASE_KEY_EVENT }; + filter.notify_key(&event); + assert_eq!(next.last_event().unwrap(), event); + } + + #[test] + fn test_notify_key_for_external_keyboard_when_key_pressed_for_threshold_time() { + let test_callbacks = TestCallbacks::new(); + let test_thread = TestThread::new(test_callbacks.clone()); + let next = TestFilter::new(); + let mut filter = setup_filter_with_external_device( + Box::new(next.clone()), + test_thread.clone(), + 1, /* device_id */ + 100, /* threshold */ + ); + test_thread.start_looper(); + + filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT }); + assert!(next.last_event().is_none()); + test_thread.dispatch_next(); + + test_thread.move_time_forward(100); + + test_thread.stop_looper(); + assert_eq!( + next.last_event().unwrap(), + KeyEvent { + action: KeyEventAction::DOWN, + downTime: 100, + eventTime: 100, + ..BASE_KEY_EVENT + } + ); + } + + #[test] + fn test_notify_key_for_external_keyboard_when_key_not_pressed_for_threshold_time() { + let test_callbacks = TestCallbacks::new(); + let test_thread = TestThread::new(test_callbacks.clone()); + let next = TestFilter::new(); + let mut filter = setup_filter_with_external_device( + Box::new(next.clone()), + test_thread.clone(), + 1, /* device_id */ + 100, /* threshold */ + ); + test_thread.start_looper(); + + filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT }); + test_thread.dispatch_next(); + + test_thread.move_time_forward(10); + + filter.notify_key(&KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT }); + test_thread.dispatch_next(); + + test_thread.stop_looper(); + assert!(next.last_event().is_none()); + } + + #[test] + fn test_notify_key_for_external_keyboard_when_device_removed_before_threshold_time() { + let test_callbacks = TestCallbacks::new(); + let test_thread = TestThread::new(test_callbacks.clone()); + let next = TestFilter::new(); + let mut filter = setup_filter_with_external_device( + Box::new(next.clone()), + test_thread.clone(), + 1, /* device_id */ + 100, /* threshold */ + ); + test_thread.start_looper(); + + filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT }); + assert!(next.last_event().is_none()); + test_thread.dispatch_next(); + + filter.notify_devices_changed(&[]); + test_thread.dispatch_next(); + + test_thread.move_time_forward(100); + + test_thread.stop_looper(); + assert!(next.last_event().is_none()); + } + + fn setup_filter_with_external_device( + next: Box<dyn Filter + Send + Sync>, + test_thread: TestThread, + device_id: i32, + threshold: i64, + ) -> SlowKeysFilter { + setup_filter_with_devices( + next, + test_thread, + &[DeviceInfo { deviceId: device_id, external: true }], + threshold, + ) + } + + fn setup_filter_with_internal_device( + next: Box<dyn Filter + Send + Sync>, + test_thread: TestThread, + device_id: i32, + threshold: i64, + ) -> SlowKeysFilter { + setup_filter_with_devices( + next, + test_thread, + &[DeviceInfo { deviceId: device_id, external: false }], + threshold, + ) + } + + fn setup_filter_with_devices( + next: Box<dyn Filter + Send + Sync>, + test_thread: TestThread, + devices: &[DeviceInfo], + threshold: i64, + ) -> SlowKeysFilter { + let mut filter = SlowKeysFilter::new(next, threshold, test_thread.get_input_thread()); + filter.notify_devices_changed(devices); + filter + } +} diff --git a/services/inputflinger/rust/sticky_keys_filter.rs b/services/inputflinger/rust/sticky_keys_filter.rs new file mode 100644 index 0000000000..6c2277c813 --- /dev/null +++ b/services/inputflinger/rust/sticky_keys_filter.rs @@ -0,0 +1,519 @@ +/* + * 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. + */ + +//! Sticky keys input filter implementation. +//! Sticky keys is an accessibility feature that assists users who have physical disabilities or +//! helps users reduce repetitive strain injury. It serializes keystrokes instead of pressing +//! multiple keys at a time, allowing the user to press and release a modifier key, such as Shift, +//! Ctrl, Alt, or any other modifier key, and have it remain active until any other key is pressed. +use crate::input_filter::{Filter, ModifierStateListener}; +use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{ + DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction, +}; +use std::collections::HashSet; + +// Modifier keycodes: values are from /frameworks/native/include/android/keycodes.h +const KEYCODE_ALT_LEFT: i32 = 57; +const KEYCODE_ALT_RIGHT: i32 = 58; +const KEYCODE_SHIFT_LEFT: i32 = 59; +const KEYCODE_SHIFT_RIGHT: i32 = 60; +const KEYCODE_SYM: i32 = 63; +const KEYCODE_CTRL_LEFT: i32 = 113; +const KEYCODE_CTRL_RIGHT: i32 = 114; +const KEYCODE_CAPS_LOCK: i32 = 115; +const KEYCODE_SCROLL_LOCK: i32 = 116; +const KEYCODE_META_LEFT: i32 = 117; +const KEYCODE_META_RIGHT: i32 = 118; +const KEYCODE_FUNCTION: i32 = 119; +const KEYCODE_NUM_LOCK: i32 = 143; + +// Modifier states: values are from /frameworks/native/include/android/input.h +const META_ALT_ON: u32 = 0x02; +const META_ALT_LEFT_ON: u32 = 0x10; +const META_ALT_RIGHT_ON: u32 = 0x20; +const META_SHIFT_ON: u32 = 0x01; +const META_SHIFT_LEFT_ON: u32 = 0x40; +const META_SHIFT_RIGHT_ON: u32 = 0x80; +const META_CTRL_ON: u32 = 0x1000; +const META_CTRL_LEFT_ON: u32 = 0x2000; +const META_CTRL_RIGHT_ON: u32 = 0x4000; +const META_META_ON: u32 = 0x10000; +const META_META_LEFT_ON: u32 = 0x20000; +const META_META_RIGHT_ON: u32 = 0x40000; + +pub struct StickyKeysFilter { + next: Box<dyn Filter + Send + Sync>, + listener: ModifierStateListener, + /// Tracking devices that contributed to the modifier state. + contributing_devices: HashSet<i32>, + /// State describing the current enabled modifiers. This contain both locked and non-locked + /// modifier state bits. + modifier_state: u32, + /// State describing the current locked modifiers. These modifiers will not be cleared on a + /// non-modifier key press. They will be cleared only if the locked modifier key is pressed + /// again. + locked_modifier_state: u32, +} + +impl StickyKeysFilter { + /// Create a new StickyKeysFilter instance. + pub fn new( + next: Box<dyn Filter + Send + Sync>, + listener: ModifierStateListener, + ) -> StickyKeysFilter { + Self { + next, + listener, + contributing_devices: HashSet::new(), + modifier_state: 0, + locked_modifier_state: 0, + } + } +} + +impl Filter for StickyKeysFilter { + fn notify_key(&mut self, event: &KeyEvent) { + let up = event.action == KeyEventAction::UP; + let mut modifier_state = self.modifier_state; + let mut locked_modifier_state = self.locked_modifier_state; + if !is_ephemeral_modifier_key(event.keyCode) { + // If non-ephemeral modifier key (i.e. non-modifier keys + toggle modifier keys like + // CAPS_LOCK, NUM_LOCK etc.), don't block key and pass in the sticky modifier state with + // the KeyEvent. + let old_modifier_state = event.metaState as u32; + let mut new_event = *event; + // Send the current modifier state with the key event before clearing non-locked + // modifier state + new_event.metaState = + (clear_ephemeral_modifier_state(old_modifier_state) | modifier_state) as i32; + self.next.notify_key(&new_event); + if up && !is_modifier_key(event.keyCode) { + modifier_state = + clear_ephemeral_modifier_state(modifier_state) | locked_modifier_state; + } + } else if up { + // Update contributing devices to track keyboards + self.contributing_devices.insert(event.deviceId); + // If ephemeral modifier key, capture the key and update the sticky modifier states + let modifier_key_mask = get_ephemeral_modifier_key_mask(event.keyCode); + let symmetrical_modifier_key_mask = get_symmetrical_modifier_key_mask(event.keyCode); + if locked_modifier_state & modifier_key_mask != 0 { + locked_modifier_state &= !symmetrical_modifier_key_mask; + modifier_state &= !symmetrical_modifier_key_mask; + } else if modifier_key_mask & modifier_state != 0 { + locked_modifier_state |= modifier_key_mask; + modifier_state = + (modifier_state & !symmetrical_modifier_key_mask) | modifier_key_mask; + } else { + modifier_state |= modifier_key_mask; + } + } + if self.modifier_state != modifier_state + || self.locked_modifier_state != locked_modifier_state + { + self.modifier_state = modifier_state; + self.locked_modifier_state = locked_modifier_state; + self.listener.modifier_state_changed(modifier_state, locked_modifier_state); + } + } + + fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]) { + // Clear state if all contributing devices removed + self.contributing_devices.retain(|id| device_infos.iter().any(|x| *id == x.deviceId)); + if self.contributing_devices.is_empty() + && (self.modifier_state != 0 || self.locked_modifier_state != 0) + { + self.modifier_state = 0; + self.locked_modifier_state = 0; + self.listener.modifier_state_changed(0, 0); + } + self.next.notify_devices_changed(device_infos); + } + + fn destroy(&mut self) { + self.next.destroy(); + } +} + +fn is_modifier_key(keycode: i32) -> bool { + matches!( + keycode, + KEYCODE_ALT_LEFT + | KEYCODE_ALT_RIGHT + | KEYCODE_SHIFT_LEFT + | KEYCODE_SHIFT_RIGHT + | KEYCODE_CTRL_LEFT + | KEYCODE_CTRL_RIGHT + | KEYCODE_META_LEFT + | KEYCODE_META_RIGHT + | KEYCODE_SYM + | KEYCODE_FUNCTION + | KEYCODE_CAPS_LOCK + | KEYCODE_NUM_LOCK + | KEYCODE_SCROLL_LOCK + ) +} + +fn is_ephemeral_modifier_key(keycode: i32) -> bool { + matches!( + keycode, + KEYCODE_ALT_LEFT + | KEYCODE_ALT_RIGHT + | KEYCODE_SHIFT_LEFT + | KEYCODE_SHIFT_RIGHT + | KEYCODE_CTRL_LEFT + | KEYCODE_CTRL_RIGHT + | KEYCODE_META_LEFT + | KEYCODE_META_RIGHT + ) +} + +fn get_ephemeral_modifier_key_mask(keycode: i32) -> u32 { + match keycode { + KEYCODE_ALT_LEFT => META_ALT_LEFT_ON | META_ALT_ON, + KEYCODE_ALT_RIGHT => META_ALT_RIGHT_ON | META_ALT_ON, + KEYCODE_SHIFT_LEFT => META_SHIFT_LEFT_ON | META_SHIFT_ON, + KEYCODE_SHIFT_RIGHT => META_SHIFT_RIGHT_ON | META_SHIFT_ON, + KEYCODE_CTRL_LEFT => META_CTRL_LEFT_ON | META_CTRL_ON, + KEYCODE_CTRL_RIGHT => META_CTRL_RIGHT_ON | META_CTRL_ON, + KEYCODE_META_LEFT => META_META_LEFT_ON | META_META_ON, + KEYCODE_META_RIGHT => META_META_RIGHT_ON | META_META_ON, + _ => 0, + } +} + +/// Modifier mask including both left and right versions of a modifier key. +fn get_symmetrical_modifier_key_mask(keycode: i32) -> u32 { + match keycode { + KEYCODE_ALT_LEFT | KEYCODE_ALT_RIGHT => META_ALT_LEFT_ON | META_ALT_RIGHT_ON | META_ALT_ON, + KEYCODE_SHIFT_LEFT | KEYCODE_SHIFT_RIGHT => { + META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON | META_SHIFT_ON + } + KEYCODE_CTRL_LEFT | KEYCODE_CTRL_RIGHT => { + META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON | META_CTRL_ON + } + KEYCODE_META_LEFT | KEYCODE_META_RIGHT => { + META_META_LEFT_ON | META_META_RIGHT_ON | META_META_ON + } + _ => 0, + } +} + +fn clear_ephemeral_modifier_state(modifier_state: u32) -> u32 { + modifier_state + & !(META_ALT_LEFT_ON + | META_ALT_RIGHT_ON + | META_ALT_ON + | META_SHIFT_LEFT_ON + | META_SHIFT_RIGHT_ON + | META_SHIFT_ON + | META_CTRL_LEFT_ON + | META_CTRL_RIGHT_ON + | META_CTRL_ON + | META_META_LEFT_ON + | META_META_RIGHT_ON + | META_META_ON) +} + +#[cfg(test)] +mod tests { + use crate::input_filter::{ + test_callbacks::TestCallbacks, test_filter::TestFilter, Filter, ModifierStateListener, + }; + use crate::sticky_keys_filter::{ + StickyKeysFilter, KEYCODE_ALT_LEFT, KEYCODE_ALT_RIGHT, KEYCODE_CAPS_LOCK, + KEYCODE_CTRL_LEFT, KEYCODE_CTRL_RIGHT, KEYCODE_FUNCTION, KEYCODE_META_LEFT, + KEYCODE_META_RIGHT, KEYCODE_NUM_LOCK, KEYCODE_SCROLL_LOCK, KEYCODE_SHIFT_LEFT, + KEYCODE_SHIFT_RIGHT, KEYCODE_SYM, META_ALT_LEFT_ON, META_ALT_ON, META_ALT_RIGHT_ON, + META_CTRL_LEFT_ON, META_CTRL_ON, META_CTRL_RIGHT_ON, META_META_LEFT_ON, META_META_ON, + META_META_RIGHT_ON, META_SHIFT_LEFT_ON, META_SHIFT_ON, META_SHIFT_RIGHT_ON, + }; + use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source; + use binder::Strong; + use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{ + DeviceInfo::DeviceInfo, IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks, + KeyEvent::KeyEvent, KeyEventAction::KeyEventAction, + }; + use std::sync::{Arc, RwLock}; + + static DEVICE_ID: i32 = 1; + static KEY_A: i32 = 29; + static BASE_KEY_DOWN: KeyEvent = KeyEvent { + id: 1, + deviceId: DEVICE_ID, + downTime: 0, + readTime: 0, + eventTime: 0, + source: Source::KEYBOARD, + displayId: 0, + policyFlags: 0, + action: KeyEventAction::DOWN, + flags: 0, + keyCode: 0, + scanCode: 0, + metaState: 0, + }; + + static BASE_KEY_UP: KeyEvent = KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_DOWN }; + + #[test] + fn test_notify_key_consumes_ephemeral_modifier_keys() { + let test_filter = TestFilter::new(); + let test_callbacks = TestCallbacks::new(); + let mut sticky_keys_filter = setup_filter( + Box::new(test_filter.clone()), + Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))), + ); + let key_codes = &[ + KEYCODE_ALT_LEFT, + KEYCODE_ALT_RIGHT, + KEYCODE_CTRL_LEFT, + KEYCODE_CTRL_RIGHT, + KEYCODE_SHIFT_LEFT, + KEYCODE_SHIFT_RIGHT, + KEYCODE_META_LEFT, + KEYCODE_META_RIGHT, + ]; + for key_code in key_codes.iter() { + sticky_keys_filter.notify_key(&KeyEvent { keyCode: *key_code, ..BASE_KEY_DOWN }); + assert!(test_filter.last_event().is_none()); + + sticky_keys_filter.notify_key(&KeyEvent { keyCode: *key_code, ..BASE_KEY_UP }); + assert!(test_filter.last_event().is_none()); + } + } + + #[test] + fn test_notify_key_passes_non_ephemeral_modifier_keys() { + let test_filter = TestFilter::new(); + let test_callbacks = TestCallbacks::new(); + let mut sticky_keys_filter = setup_filter( + Box::new(test_filter.clone()), + Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))), + ); + let key_codes = &[ + KEYCODE_CAPS_LOCK, + KEYCODE_NUM_LOCK, + KEYCODE_SCROLL_LOCK, + KEYCODE_FUNCTION, + KEYCODE_SYM, + ]; + for key_code in key_codes.iter() { + let event = KeyEvent { keyCode: *key_code, ..BASE_KEY_DOWN }; + sticky_keys_filter.notify_key(&event); + assert_eq!(test_filter.last_event().unwrap(), event); + let event = KeyEvent { keyCode: *key_code, ..BASE_KEY_UP }; + sticky_keys_filter.notify_key(&event); + assert_eq!(test_filter.last_event().unwrap(), event); + } + } + + #[test] + fn test_notify_key_passes_non_modifier_keys() { + let test_filter = TestFilter::new(); + let test_callbacks = TestCallbacks::new(); + let mut sticky_keys_filter = setup_filter( + Box::new(test_filter.clone()), + Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))), + ); + let event = KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN }; + sticky_keys_filter.notify_key(&event); + assert_eq!(test_filter.last_event().unwrap(), event); + + let event = KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP }; + sticky_keys_filter.notify_key(&event); + assert_eq!(test_filter.last_event().unwrap(), event); + } + + #[test] + fn test_modifier_state_updated_on_modifier_key_press() { + let mut test_filter = TestFilter::new(); + let mut test_callbacks = TestCallbacks::new(); + let mut sticky_keys_filter = setup_filter( + Box::new(test_filter.clone()), + Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))), + ); + let test_states = &[ + (KEYCODE_ALT_LEFT, META_ALT_ON | META_ALT_LEFT_ON), + (KEYCODE_ALT_RIGHT, META_ALT_ON | META_ALT_RIGHT_ON), + (KEYCODE_CTRL_LEFT, META_CTRL_ON | META_CTRL_LEFT_ON), + (KEYCODE_CTRL_RIGHT, META_CTRL_ON | META_CTRL_RIGHT_ON), + (KEYCODE_SHIFT_LEFT, META_SHIFT_ON | META_SHIFT_LEFT_ON), + (KEYCODE_SHIFT_RIGHT, META_SHIFT_ON | META_SHIFT_RIGHT_ON), + (KEYCODE_META_LEFT, META_META_ON | META_META_LEFT_ON), + (KEYCODE_META_RIGHT, META_META_ON | META_META_RIGHT_ON), + ]; + for test_state in test_states.iter() { + test_filter.clear(); + test_callbacks.clear(); + sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN }); + assert_eq!(test_callbacks.get_last_modifier_state(), 0); + assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0); + + sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP }); + assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1); + assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0); + + // Re-send keys to lock it + sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN }); + assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1); + assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0); + + sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP }); + assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1); + assert_eq!(test_callbacks.get_last_locked_modifier_state(), test_state.1); + + // Re-send keys to clear + sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN }); + assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1); + assert_eq!(test_callbacks.get_last_locked_modifier_state(), test_state.1); + + sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP }); + assert_eq!(test_callbacks.get_last_modifier_state(), 0); + assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0); + } + } + + #[test] + fn test_modifier_state_cleared_on_non_modifier_key_press() { + let test_filter = TestFilter::new(); + let test_callbacks = TestCallbacks::new(); + let mut sticky_keys_filter = setup_filter( + Box::new(test_filter.clone()), + Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))), + ); + sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN }); + sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP }); + + assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON); + assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0); + + sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN }); + sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP }); + + assert_eq!(test_callbacks.get_last_modifier_state(), 0); + assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0); + } + + #[test] + fn test_locked_modifier_state_not_cleared_on_non_modifier_key_press() { + let test_filter = TestFilter::new(); + let test_callbacks = TestCallbacks::new(); + let mut sticky_keys_filter = setup_filter( + Box::new(test_filter.clone()), + Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))), + ); + sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN }); + sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP }); + + sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN }); + sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP }); + + sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_SHIFT_LEFT, ..BASE_KEY_DOWN }); + sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_SHIFT_LEFT, ..BASE_KEY_UP }); + + assert_eq!( + test_callbacks.get_last_modifier_state(), + META_SHIFT_LEFT_ON | META_SHIFT_ON | META_CTRL_LEFT_ON | META_CTRL_ON + ); + assert_eq!( + test_callbacks.get_last_locked_modifier_state(), + META_CTRL_LEFT_ON | META_CTRL_ON + ); + + sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN }); + sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP }); + + assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON); + assert_eq!( + test_callbacks.get_last_locked_modifier_state(), + META_CTRL_LEFT_ON | META_CTRL_ON + ); + } + + #[test] + fn test_key_events_have_sticky_modifier_state() { + let test_filter = TestFilter::new(); + let test_callbacks = TestCallbacks::new(); + let mut sticky_keys_filter = setup_filter( + Box::new(test_filter.clone()), + Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))), + ); + sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN }); + sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP }); + + sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN }); + assert_eq!( + test_filter.last_event().unwrap().metaState as u32, + META_CTRL_LEFT_ON | META_CTRL_ON + ); + + sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP }); + assert_eq!( + test_filter.last_event().unwrap().metaState as u32, + META_CTRL_LEFT_ON | META_CTRL_ON + ); + } + + #[test] + fn test_modifier_state_not_cleared_until_all_devices_removed() { + let test_filter = TestFilter::new(); + let test_callbacks = TestCallbacks::new(); + let mut sticky_keys_filter = setup_filter( + Box::new(test_filter.clone()), + Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))), + ); + sticky_keys_filter.notify_key(&KeyEvent { + deviceId: 1, + keyCode: KEYCODE_CTRL_LEFT, + ..BASE_KEY_DOWN + }); + sticky_keys_filter.notify_key(&KeyEvent { + deviceId: 1, + keyCode: KEYCODE_CTRL_LEFT, + ..BASE_KEY_UP + }); + + sticky_keys_filter.notify_key(&KeyEvent { + deviceId: 2, + keyCode: KEYCODE_CTRL_LEFT, + ..BASE_KEY_DOWN + }); + sticky_keys_filter.notify_key(&KeyEvent { + deviceId: 2, + keyCode: KEYCODE_CTRL_LEFT, + ..BASE_KEY_UP + }); + + sticky_keys_filter.notify_devices_changed(&[DeviceInfo { deviceId: 2, external: true }]); + assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON); + assert_eq!( + test_callbacks.get_last_locked_modifier_state(), + META_CTRL_LEFT_ON | META_CTRL_ON + ); + + sticky_keys_filter.notify_devices_changed(&[]); + assert_eq!(test_callbacks.get_last_modifier_state(), 0); + assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0); + } + + fn setup_filter( + next: Box<dyn Filter + Send + Sync>, + callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>, + ) -> StickyKeysFilter { + StickyKeysFilter::new(next, ModifierStateListener::new(callbacks)) + } +} diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 2a03ecc62b..a26153ec5b 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -45,6 +45,7 @@ cc_test { "EventHub_test.cpp", "FakeEventHub.cpp", "FakeInputReaderPolicy.cpp", + "FakeInputTracingBackend.cpp", "FakePointerController.cpp", "FocusResolver_test.cpp", "GestureConverter_test.cpp", @@ -69,6 +70,7 @@ cc_test { "TimerProvider_test.cpp", "TestInputListener.cpp", "TouchpadInputMapper_test.cpp", + "MultiTouchInputMapper_test.cpp", "KeyboardInputMapper_test.cpp", "UinputDevice.cpp", "UnwantedInteractionBlocker_test.cpp", diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp index 6d6b7d821c..8c17221ead 100644 --- a/services/inputflinger/tests/CursorInputMapper_test.cpp +++ b/services/inputflinger/tests/CursorInputMapper_test.cpp @@ -16,14 +16,26 @@ #include "CursorInputMapper.h" +#include <list> +#include <string> +#include <tuple> +#include <variant> + #include <android-base/logging.h> #include <com_android_input_flags.h> #include <gtest/gtest.h> +#include <input/DisplayViewport.h> +#include <linux/input-event-codes.h> +#include <linux/input.h> +#include <utils/Timers.h> #include "FakePointerController.h" #include "InputMapperTest.h" +#include "InputReaderBase.h" #include "InterfaceMocks.h" +#include "NotifyArgs.h" #include "TestEventMatchers.h" +#include "ui/Rotation.h" #define TAG "CursorInputMapper_test" @@ -40,23 +52,97 @@ constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE; constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE; constexpr auto INVALID_CURSOR_POSITION = AMOTION_EVENT_INVALID_CURSOR_POSITION; constexpr int32_t DISPLAY_ID = 0; +constexpr int32_t SECONDARY_DISPLAY_ID = DISPLAY_ID + 1; constexpr int32_t DISPLAY_WIDTH = 480; constexpr int32_t DISPLAY_HEIGHT = 800; -constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified + +constexpr int32_t TRACKBALL_MOVEMENT_THRESHOLD = 6; + +namespace { + +DisplayViewport createPrimaryViewport(ui::Rotation orientation) { + const bool isRotated = + orientation == ui::Rotation::Rotation90 || orientation == ui::Rotation::Rotation270; + DisplayViewport v; + v.displayId = DISPLAY_ID; + v.orientation = orientation; + v.logicalRight = isRotated ? DISPLAY_HEIGHT : DISPLAY_WIDTH; + v.logicalBottom = isRotated ? DISPLAY_WIDTH : DISPLAY_HEIGHT; + v.physicalRight = isRotated ? DISPLAY_HEIGHT : DISPLAY_WIDTH; + v.physicalBottom = isRotated ? DISPLAY_WIDTH : DISPLAY_HEIGHT; + v.deviceWidth = isRotated ? DISPLAY_HEIGHT : DISPLAY_WIDTH; + v.deviceHeight = isRotated ? DISPLAY_WIDTH : DISPLAY_HEIGHT; + v.isActive = true; + v.uniqueId = "local:1"; + return v; +} + +DisplayViewport createSecondaryViewport() { + DisplayViewport v; + v.displayId = SECONDARY_DISPLAY_ID; + v.orientation = ui::Rotation::Rotation0; + v.logicalRight = DISPLAY_HEIGHT; + v.logicalBottom = DISPLAY_WIDTH; + v.physicalRight = DISPLAY_HEIGHT; + v.physicalBottom = DISPLAY_WIDTH; + v.deviceWidth = DISPLAY_HEIGHT; + v.deviceHeight = DISPLAY_WIDTH; + v.isActive = true; + v.uniqueId = "local:2"; + v.type = ViewportType::EXTERNAL; + return v; +} + +/** + * A fake InputDeviceContext that allows the associated viewport to be specified for the mapper. + * + * This is currently necessary because InputMapperUnitTest doesn't register the mappers it creates + * with the InputDevice object, meaning that InputDevice::isIgnored becomes true, and the input + * device doesn't set its associated viewport when it's configured. + * + * TODO(b/319217713): work out a way to avoid this fake. + */ +class ViewportFakingInputDeviceContext : public InputDeviceContext { +public: + ViewportFakingInputDeviceContext(InputDevice& device, int32_t eventHubId, + std::optional<DisplayViewport> viewport) + : InputDeviceContext(device, eventHubId), mAssociatedViewport(viewport) {} + + ViewportFakingInputDeviceContext(InputDevice& device, int32_t eventHubId, + ui::Rotation orientation) + : ViewportFakingInputDeviceContext(device, eventHubId, + createPrimaryViewport(orientation)) {} + + std::optional<DisplayViewport> getAssociatedViewport() const override { + return mAssociatedViewport; + } + + void setViewport(const std::optional<DisplayViewport>& viewport) { + mAssociatedViewport = viewport; + } + +private: + std::optional<DisplayViewport> mAssociatedViewport; +}; + +} // namespace namespace input_flags = com::android::input::flags; /** * Unit tests for CursorInputMapper. - * This class is named 'CursorInputMapperUnitTest' to avoid name collision with the existing - * 'CursorInputMapperTest'. If all of the CursorInputMapper tests are migrated here, the name - * can be simplified to 'CursorInputMapperTest'. - * TODO(b/283812079): move CursorInputMapper tests here. + * These classes are named 'CursorInputMapperUnitTest...' to avoid name collision with the existing + * 'CursorInputMapperTest...' classes. If all of the CursorInputMapper tests are migrated here, the + * name can be simplified to 'CursorInputMapperTest'. + * + * TODO(b/283812079): move the remaining CursorInputMapper tests here. The ones that are left all + * depend on viewport association, for which we'll need to fake InputDeviceContext. */ -class CursorInputMapperUnitTest : public InputMapperUnitTest { +class CursorInputMapperUnitTestBase : public InputMapperUnitTest { protected: - void SetUp() override { - InputMapperUnitTest::SetUp(); + void SetUp() override { SetUpWithBus(BUS_USB); } + void SetUpWithBus(int bus) override { + InputMapperUnitTest::SetUpWithBus(bus); // Current scan code state - all keys are UP by default setScanCodeState(KeyState::UP, @@ -68,10 +154,11 @@ protected: .WillRepeatedly(Return(false)); mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); - mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, - /*isActive=*/true, "local:0", NO_PORT, - ViewportType::INTERNAL); + mFakePolicy->addDisplayViewport(createPrimaryViewport(ui::Rotation::Rotation0)); + } + void createMapper() { + createDevice(); mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration); } @@ -83,19 +170,56 @@ protected: mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration, InputReaderConfiguration::Change::POINTER_CAPTURE); ASSERT_THAT(args, - ElementsAre( - VariantWith<NotifyDeviceResetArgs>(AllOf(WithDeviceId(DEVICE_ID))))); + ElementsAre(VariantWith<NotifyDeviceResetArgs>( + AllOf(WithDeviceId(DEVICE_ID), WithEventTime(ARBITRARY_TIME))))); // Check that generation also got bumped ASSERT_GT(mDevice->getGeneration(), generation); } + + void testMotionRotation(int32_t originalX, int32_t originalY, int32_t rotatedX, + int32_t rotatedY) { + std::list<NotifyArgs> args; + args += process(ARBITRARY_TIME, EV_REL, REL_X, originalX); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, originalY); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(ACTION_MOVE), + WithCoords(float(rotatedX) / TRACKBALL_MOVEMENT_THRESHOLD, + float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD))))); + } }; +class CursorInputMapperUnitTest : public CursorInputMapperUnitTestBase { +protected: + void SetUp() override { + input_flags::enable_pointer_choreographer(false); + input_flags::enable_new_mouse_pointer_ballistics(false); + CursorInputMapperUnitTestBase::SetUp(); + } +}; + +TEST_F(CursorInputMapperUnitTest, GetSourcesReturnsMouseInPointerMode) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + createMapper(); + + ASSERT_EQ(AINPUT_SOURCE_MOUSE, mMapper->getSources()); +} + +TEST_F(CursorInputMapperUnitTest, GetSourcesReturnsTrackballInNavigationMode) { + mPropertyMap.addProperty("cursor.mode", "navigation"); + createMapper(); + + ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, mMapper->getSources()); +} + /** * Move the mouse and then click the button. Check whether HOVER_EXIT is generated when hovering * ends. Currently, it is not. */ TEST_F(CursorInputMapperUnitTest, HoverAndLeftButtonPress) { + createMapper(); std::list<NotifyArgs> args; // Move the cursor a little @@ -139,6 +263,7 @@ TEST_F(CursorInputMapperUnitTest, HoverAndLeftButtonPress) { * When it's not SOURCE_MOUSE, CursorInputMapper doesn't populate cursor position values. */ TEST_F(CursorInputMapperUnitTest, ProcessPointerCapture) { + createMapper(); setPointerCapture(true); std::list<NotifyArgs> args; @@ -213,4 +338,1310 @@ TEST_F(CursorInputMapperUnitTest, ProcessPointerCapture) { WithRelativeMotion(10.0f, 20.0f))))); } +TEST_F(CursorInputMapperUnitTest, + PopulateDeviceInfoReturnsRangeFromPointerControllerInPointerMode) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + mFakePolicy->clearViewports(); + mFakePointerController->clearBounds(); + createMapper(); + + InputDeviceInfo info; + mMapper->populateDeviceInfo(info); + + // Initially there should not be a valid motion range because there's no viewport or pointer + // bounds. + ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE)); + ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE, + AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f)); + + // When the bounds are set, then there should be a valid motion range. + mFakePointerController->setBounds(1, 2, 800 - 1, 480 - 1); + mFakePolicy->addDisplayViewport(createPrimaryViewport(ui::Rotation::Rotation0)); + std::list<NotifyArgs> args = + mMapper->reconfigure(systemTime(), mReaderConfiguration, + InputReaderConfiguration::Change::DISPLAY_INFO); + ASSERT_THAT(args, testing::IsEmpty()); + + InputDeviceInfo info2; + mMapper->populateDeviceInfo(info2); + + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, 1, + 800 - 1, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, 2, + 480 - 1, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_PRESSURE, + AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f)); +} + +TEST_F(CursorInputMapperUnitTest, PopulateDeviceInfoReturnsScaledRangeInNavigationMode) { + mPropertyMap.addProperty("cursor.mode", "navigation"); + createMapper(); + + InputDeviceInfo info; + mMapper->populateDeviceInfo(info); + + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_TRACKBALL, + -1.0f, 1.0f, 0.0f, + 1.0f / TRACKBALL_MOVEMENT_THRESHOLD)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_TRACKBALL, + -1.0f, 1.0f, 0.0f, + 1.0f / TRACKBALL_MOVEMENT_THRESHOLD)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE, + AINPUT_SOURCE_TRACKBALL, 0.0f, 1.0f, 0.0f, 0.0f)); +} + +TEST_F(CursorInputMapperUnitTest, ProcessShouldSetAllFieldsAndIncludeGlobalMetaState) { + mPropertyMap.addProperty("cursor.mode", "navigation"); + createMapper(); + + EXPECT_CALL(mMockInputReaderContext, getGlobalMetaState()) + .WillRepeatedly(Return(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON)); + + std::list<NotifyArgs> args; + + // Button press. + // Mostly testing non x/y behavior here so we don't need to check again elsewhere. + args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithEventTime(ARBITRARY_TIME), WithDeviceId(DEVICE_ID), + WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0), + WithEdgeFlags(0), WithPolicyFlags(0), + WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithPointerCount(1), WithPointerId(0, 0), + WithToolType(ToolType::MOUSE), WithCoords(0.0f, 0.0f), + WithPressure(1.0f), + WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD, + TRACKBALL_MOVEMENT_THRESHOLD), + WithDownTime(ARBITRARY_TIME))), + VariantWith<NotifyMotionArgs>( + AllOf(WithEventTime(ARBITRARY_TIME), WithDeviceId(DEVICE_ID), + WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0), + WithEdgeFlags(0), WithPolicyFlags(0), + WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithPointerCount(1), WithPointerId(0, 0), + WithToolType(ToolType::MOUSE), WithCoords(0.0f, 0.0f), + WithPressure(1.0f), + WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD, + TRACKBALL_MOVEMENT_THRESHOLD), + WithDownTime(ARBITRARY_TIME))))); + args.clear(); + + // Button release. Should have same down time. + args += process(ARBITRARY_TIME + 1, EV_KEY, BTN_MOUSE, 0); + args += process(ARBITRARY_TIME + 1, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithEventTime(ARBITRARY_TIME + 1), + WithDeviceId(DEVICE_ID), + WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0), + WithEdgeFlags(0), WithPolicyFlags(0), + WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON), + WithButtonState(0), WithPointerCount(1), + WithPointerId(0, 0), WithToolType(ToolType::MOUSE), + WithCoords(0.0f, 0.0f), WithPressure(0.0f), + WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD, + TRACKBALL_MOVEMENT_THRESHOLD), + WithDownTime(ARBITRARY_TIME))), + VariantWith<NotifyMotionArgs>( + AllOf(WithEventTime(ARBITRARY_TIME + 1), + WithDeviceId(DEVICE_ID), + WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0), + WithEdgeFlags(0), WithPolicyFlags(0), + WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON), + WithButtonState(0), WithPointerCount(1), + WithPointerId(0, 0), WithToolType(ToolType::MOUSE), + WithCoords(0.0f, 0.0f), WithPressure(0.0f), + WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD, + TRACKBALL_MOVEMENT_THRESHOLD), + WithDownTime(ARBITRARY_TIME))))); +} + +TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleIndependentXYUpdates) { + mPropertyMap.addProperty("cursor.mode", "navigation"); + createMapper(); + + std::list<NotifyArgs> args; + + // Motion in X but not Y. + args += process(ARBITRARY_TIME, EV_REL, REL_X, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithCoords(1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f), + WithPressure(0.0f))))); + args.clear(); + + // Motion in Y but not X. + args += process(ARBITRARY_TIME, EV_REL, REL_Y, -2); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithCoords(0.0f, -2.0f / TRACKBALL_MOVEMENT_THRESHOLD), + WithPressure(0.0f))))); + args.clear(); +} + +TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleIndependentButtonUpdates) { + mPropertyMap.addProperty("cursor.mode", "navigation"); + createMapper(); + + std::list<NotifyArgs> args; + + // Button press. + args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(0.0f, 0.0f), WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithCoords(0.0f, 0.0f), WithPressure(1.0f))))); + args.clear(); + + // Button release. + args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 0); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithCoords(0.0f, 0.0f), WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(0.0f, 0.0f), WithPressure(0.0f))))); +} + +TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleCombinedXYAndButtonUpdates) { + mPropertyMap.addProperty("cursor.mode", "navigation"); + createMapper(); + + std::list<NotifyArgs> args; + + // Combined X, Y and Button. + args += process(ARBITRARY_TIME, EV_REL, REL_X, 1); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, -2); + args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(1.0f / TRACKBALL_MOVEMENT_THRESHOLD, + -2.0f / TRACKBALL_MOVEMENT_THRESHOLD), + WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithCoords(1.0f / TRACKBALL_MOVEMENT_THRESHOLD, + -2.0f / TRACKBALL_MOVEMENT_THRESHOLD), + WithPressure(1.0f))))); + args.clear(); + + // Move X, Y a bit while pressed. + args += process(ARBITRARY_TIME, EV_REL, REL_X, 2); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithCoords(2.0f / TRACKBALL_MOVEMENT_THRESHOLD, + 1.0f / TRACKBALL_MOVEMENT_THRESHOLD), + WithPressure(1.0f))))); + args.clear(); + + // Release Button. + args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 0); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithCoords(0.0f, 0.0f), WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(0.0f, 0.0f), WithPressure(0.0f))))); + args.clear(); +} + +TEST_F(CursorInputMapperUnitTest, ProcessShouldNotRotateMotionsWhenOrientationAware) { + // InputReader works in the un-rotated coordinate space, so orientation-aware devices do not + // need to be rotated. + mPropertyMap.addProperty("cursor.mode", "navigation"); + mPropertyMap.addProperty("cursor.orientationAware", "1"); + createDevice(); + ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, ui::Rotation::Rotation90); + mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration); + + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, 1, 0, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, 1, 1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, 0, 1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1, 1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1, 0, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1, -1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, 0, -1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, 1, -1, 1)); +} + +TEST_F(CursorInputMapperUnitTest, ProcessShouldRotateMotionsWhenNotOrientationAware) { + // Since InputReader works in the un-rotated coordinate space, only devices that are not + // orientation-aware are affected by display rotation. + mPropertyMap.addProperty("cursor.mode", "navigation"); + createDevice(); + ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, ui::Rotation::Rotation0); + mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration); + + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, 1, 0, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, 1, 1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, 0, 1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1, 1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1, 0, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1, -1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, 0, -1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, 1, -1, 1)); + + deviceContext.setViewport(createPrimaryViewport(ui::Rotation::Rotation90)); + std::list<NotifyArgs> args = + mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration, + InputReaderConfiguration::Change::DISPLAY_INFO); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, 1, -1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, 1, -1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, 0, 0, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1, 1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1, 1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1, 1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, 0, 0, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, 1, -1, -1)); + + deviceContext.setViewport(createPrimaryViewport(ui::Rotation::Rotation180)); + args = mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration, + InputReaderConfiguration::Change::DISPLAY_INFO); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, 1, 0, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, 1, -1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, 0, -1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1, -1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1, 0, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1, 1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, 0, 1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, 1, 1, -1)); + + deviceContext.setViewport(createPrimaryViewport(ui::Rotation::Rotation270)); + args = mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration, + InputReaderConfiguration::Change::DISPLAY_INFO); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, 1, 1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, 1, 1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, 0, 0, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1, -1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1, -1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1, -1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, 0, 0, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, 1, 1, 1)); +} + +TEST_F(CursorInputMapperUnitTest, PointerCaptureDisablesOrientationChanges) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + DisplayViewport viewport = createPrimaryViewport(ui::Rotation::Rotation90); + mFakePointerController->setDisplayViewport(viewport); + mReaderConfiguration.setDisplayViewports({viewport}); + createMapper(); + + // Verify that the coordinates are rotated. + std::list<NotifyArgs> args; + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE), + WithRelativeMotion(-20.0f, 10.0f))))); + + // Enable Pointer Capture. + setPointerCapture(true); + + // Move and verify rotation is not applied. + args = process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(ACTION_MOVE), + WithSource(AINPUT_SOURCE_MOUSE_RELATIVE), + WithCoords(10.0f, 20.0f))))); +} + +TEST_F(CursorInputMapperUnitTest, ConfigureDisplayIdNoAssociatedViewport) { + DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation90); + DisplayViewport secondaryViewport = createSecondaryViewport(); + mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport}); + // Set up the secondary display as the display on which the pointer should be shown. The + // InputDevice is not associated with any display. + mFakePointerController->setDisplayViewport(secondaryViewport); + mFakePointerController->setPosition(100, 200); + createMapper(); + + // Ensure input events are generated for the secondary display. + std::list<NotifyArgs> args; + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE), + WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(110.0f, 220.0f))))); + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f)); +} + +TEST_F(CursorInputMapperUnitTest, ConfigureDisplayIdWithAssociatedViewport) { + DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation90); + DisplayViewport secondaryViewport = createSecondaryViewport(); + mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport}); + // Set up the secondary display as the display on which the pointer should be shown. + mFakePointerController->setDisplayViewport(secondaryViewport); + mFakePointerController->setPosition(100, 200); + createDevice(); + // Associate the InputDevice with the secondary display. + ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport); + mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration); + + // Ensure input events are generated for the secondary display. + std::list<NotifyArgs> args; + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE), + WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(110.0f, 220.0f))))); + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f)); +} + +TEST_F(CursorInputMapperUnitTest, ConfigureDisplayIdIgnoresEventsForMismatchedPointerDisplay) { + DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation90); + DisplayViewport secondaryViewport = createSecondaryViewport(); + mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport}); + // Set up the primary display as the display on which the pointer should be shown. + mFakePointerController->setDisplayViewport(primaryViewport); + createDevice(); + // Associate the InputDevice with the secondary display. + ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport); + mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration); + + // The mapper should not generate any events because it is associated with a display that is + // different from the pointer display. + std::list<NotifyArgs> args; + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, testing::IsEmpty()); +} + +TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleAllButtons) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + createMapper(); + + mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); + mFakePointerController->setPosition(100, 200); + + std::list<NotifyArgs> args; + + // press BTN_LEFT, release BTN_LEFT + args += process(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithCoords(100.0f, 200.0f), WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithCoords(100.0f, 200.0f), WithPressure(1.0f))))); + args.clear(); + + args += process(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 0); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithButtonState(0), WithCoords(100.0f, 200.0f), + WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithButtonState(0), WithCoords(100.0f, 200.0f), + WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithButtonState(0), WithCoords(100.0f, 200.0f), + WithPressure(0.0f))))); + args.clear(); + + // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE + args += process(ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 1); + args += process(ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY | + AMOTION_EVENT_BUTTON_TERTIARY), + WithCoords(100.0f, 200.0f), WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY), + WithCoords(100.0f, 200.0f), WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY | + AMOTION_EVENT_BUTTON_TERTIARY), + WithCoords(100.0f, 200.0f), WithPressure(1.0f))))); + args.clear(); + + args += process(ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 0); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY), + WithCoords(100.0f, 200.0f), WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY), + WithCoords(100.0f, 200.0f), WithPressure(1.0f))))); + args.clear(); + + args += process(ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 0); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithButtonState(0), WithCoords(100.0f, 200.0f), + WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithButtonState(0), + WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(100.0f, 200.0f), WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithButtonState(0), + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithCoords(100.0f, 200.0f), WithPressure(0.0f))))); +} + +class CursorInputMapperButtonKeyTest + : public CursorInputMapperUnitTest, + public testing::WithParamInterface< + std::tuple<int32_t /*evdevCode*/, int32_t /*expectedButtonState*/, + int32_t /*expectedKeyCode*/>> {}; + +TEST_P(CursorInputMapperButtonKeyTest, ProcessShouldHandleButtonKey) { + auto [evdevCode, expectedButtonState, expectedKeyCode] = GetParam(); + mPropertyMap.addProperty("cursor.mode", "pointer"); + createMapper(); + + mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); + mFakePointerController->setPosition(100, 200); + + std::list<NotifyArgs> args; + + args += process(ARBITRARY_TIME, EV_KEY, evdevCode, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyKeyArgs>(AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), + WithKeyCode(expectedKeyCode))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithButtonState(expectedButtonState), + WithCoords(100.0f, 200.0f), WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithButtonState(expectedButtonState), + WithCoords(100.0f, 200.0f), WithPressure(0.0f))))); + args.clear(); + + args += process(ARBITRARY_TIME, EV_KEY, evdevCode, 0); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithButtonState(0), WithCoords(100.0f, 200.0f), + WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithButtonState(0), WithCoords(100.0f, 200.0f), + WithPressure(0.0f))), + VariantWith<NotifyKeyArgs>(AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), + WithKeyCode(expectedKeyCode))))); +} + +INSTANTIATE_TEST_SUITE_P( + SideExtraBackAndForward, CursorInputMapperButtonKeyTest, + testing::Values(std::make_tuple(BTN_SIDE, AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK), + std::make_tuple(BTN_EXTRA, AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD), + std::make_tuple(BTN_BACK, AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK), + std::make_tuple(BTN_FORWARD, AMOTION_EVENT_BUTTON_FORWARD, + AKEYCODE_FORWARD))); + +TEST_F(CursorInputMapperUnitTest, ProcessShouldMoveThePointerAroundInPointerMode) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + createMapper(); + + mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); + mFakePointerController->setPosition(100, 200); + + std::list<NotifyArgs> args; + + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithSource(AINPUT_SOURCE_MOUSE), + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithCoords(110.0f, 220.0f), WithPressure(0.0f), WithSize(0.0f), + WithTouchDimensions(0.0f, 0.0f), WithToolDimensions(0.0f, 0.0f), + WithOrientation(0.0f), WithDistance(0.0f))))); + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f)); +} + +/** + * When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any + * pointer acceleration or speed processing should not be applied. + */ +TEST_F(CursorInputMapperUnitTest, PointerCaptureDisablesVelocityProcessing) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + const VelocityControlParameters testParams(/*scale=*/5.f, /*lowThreshold=*/0.f, + /*highThreshold=*/100.f, /*acceleration=*/10.f); + mReaderConfiguration.pointerVelocityControlParameters = testParams; + mFakePolicy->setVelocityControlParams(testParams); + createMapper(); + + std::list<NotifyArgs> args; + + // Move and verify scale is applied. + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithSource(AINPUT_SOURCE_MOUSE), + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))))); + NotifyMotionArgs motionArgs = std::get<NotifyMotionArgs>(args.front()); + const float relX = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); + const float relY = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); + ASSERT_GT(relX, 10); + ASSERT_GT(relY, 20); + args.clear(); + + // Enable Pointer Capture + setPointerCapture(true); + + // Move and verify scale is not applied. + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithSource(AINPUT_SOURCE_MOUSE_RELATIVE), + WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(10, 20))))); +} + +// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging +// logic can be removed. +class CursorInputMapperUnitTestWithChoreographer : public CursorInputMapperUnitTestBase { +protected: + void SetUp() override { + input_flags::enable_pointer_choreographer(true); + input_flags::enable_new_mouse_pointer_ballistics(false); + CursorInputMapperUnitTestBase::SetUp(); + } +}; + +TEST_F(CursorInputMapperUnitTestWithChoreographer, PopulateDeviceInfoReturnsRangeFromPolicy) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + mFakePolicy->clearViewports(); + mFakePointerController->clearBounds(); + createMapper(); + + InputDeviceInfo info; + mMapper->populateDeviceInfo(info); + + // Initially there should not be a valid motion range because there's no viewport or pointer + // bounds. + ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE)); + ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE, + AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f)); + + // When the viewport and the default pointer display ID is set, then there should be a valid + // motion range. + mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); + mFakePolicy->addDisplayViewport(createPrimaryViewport(ui::Rotation::Rotation0)); + std::list<NotifyArgs> args = + mMapper->reconfigure(systemTime(), mReaderConfiguration, + InputReaderConfiguration::Change::DISPLAY_INFO); + ASSERT_THAT(args, testing::IsEmpty()); + + InputDeviceInfo info2; + mMapper->populateDeviceInfo(info2); + + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, 0, + DISPLAY_WIDTH - 1, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, 0, + DISPLAY_HEIGHT - 1, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_PRESSURE, + AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f)); +} + +TEST_F(CursorInputMapperUnitTestWithChoreographer, ConfigureDisplayIdWithAssociatedViewport) { + DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation90); + DisplayViewport secondaryViewport = createSecondaryViewport(); + mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport}); + // Set up the secondary display as the display on which the pointer should be shown. + // The InputDevice is not associated with any display. + mFakePointerController->setDisplayViewport(secondaryViewport); + mFakePointerController->setPosition(100, 200); + createDevice(); + ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport); + mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration); + + std::list<NotifyArgs> args; + // Ensure input events are generated for the secondary display. + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE), + WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(0.0f, 0.0f))))); +} + +TEST_F(CursorInputMapperUnitTestWithChoreographer, + ConfigureDisplayIdShouldGenerateEventForMismatchedPointerDisplay) { + DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation90); + DisplayViewport secondaryViewport = createSecondaryViewport(); + mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport}); + // Set up the primary display as the display on which the pointer should be shown. + mFakePointerController->setDisplayViewport(primaryViewport); + createDevice(); + // Associate the InputDevice with the secondary display. + ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport); + mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration); + + // With PointerChoreographer enabled, there could be a PointerController for the associated + // display even if it is different from the pointer display. So the mapper should generate an + // event. + std::list<NotifyArgs> args; + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE), + WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(0.0f, 0.0f))))); +} + +TEST_F(CursorInputMapperUnitTestWithChoreographer, ProcessShouldHandleAllButtonsWithZeroCoords) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + createMapper(); + + mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); + mFakePointerController->setPosition(100, 200); + + std::list<NotifyArgs> args; + + // press BTN_LEFT, release BTN_LEFT + args += process(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithCoords(0.0f, 0.0f), WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithCoords(0.0f, 0.0f), WithPressure(1.0f))))); + args.clear(); + args += process(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 0); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithButtonState(0), WithCoords(0.0f, 0.0f), + WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithButtonState(0), WithCoords(0.0f, 0.0f), + WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithButtonState(0), WithCoords(0.0f, 0.0f), + WithPressure(0.0f))))); + args.clear(); + + // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE + args += process(ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 1); + args += process(ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY | + AMOTION_EVENT_BUTTON_TERTIARY), + WithCoords(0.0f, 0.0f), WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY), + WithCoords(0.0f, 0.0f), WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY | + AMOTION_EVENT_BUTTON_TERTIARY), + WithCoords(0.0f, 0.0f), WithPressure(1.0f))))); + args.clear(); + + args += process(ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 0); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY), + WithCoords(0.0f, 0.0f), WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY), + WithCoords(0.0f, 0.0f), WithPressure(1.0f))))); + args.clear(); + + args += process(ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 0); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithButtonState(0), WithCoords(0.0f, 0.0f), + WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithButtonState(0), + WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(0.0f, 0.0f), WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithButtonState(0), + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithCoords(0.0f, 0.0f), WithPressure(0.0f))))); +} + +class CursorInputMapperButtonKeyTestWithChoreographer + : public CursorInputMapperUnitTestWithChoreographer, + public testing::WithParamInterface< + std::tuple<int32_t /*evdevCode*/, int32_t /*expectedButtonState*/, + int32_t /*expectedKeyCode*/>> {}; + +TEST_P(CursorInputMapperButtonKeyTestWithChoreographer, + ProcessShouldHandleButtonKeyWithZeroCoords) { + auto [evdevCode, expectedButtonState, expectedKeyCode] = GetParam(); + mPropertyMap.addProperty("cursor.mode", "pointer"); + createMapper(); + + mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); + mFakePointerController->setPosition(100, 200); + + std::list<NotifyArgs> args; + + args += process(ARBITRARY_TIME, EV_KEY, evdevCode, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyKeyArgs>(AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), + WithKeyCode(expectedKeyCode))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithButtonState(expectedButtonState), + WithCoords(0.0f, 0.0f), WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithButtonState(expectedButtonState), + WithCoords(0.0f, 0.0f), WithPressure(0.0f))))); + args.clear(); + + args += process(ARBITRARY_TIME, EV_KEY, evdevCode, 0); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithButtonState(0), WithCoords(0.0f, 0.0f), + WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithButtonState(0), WithCoords(0.0f, 0.0f), + WithPressure(0.0f))), + VariantWith<NotifyKeyArgs>(AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), + WithKeyCode(expectedKeyCode))))); +} + +INSTANTIATE_TEST_SUITE_P( + SideExtraBackAndForward, CursorInputMapperButtonKeyTestWithChoreographer, + testing::Values(std::make_tuple(BTN_SIDE, AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK), + std::make_tuple(BTN_EXTRA, AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD), + std::make_tuple(BTN_BACK, AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK), + std::make_tuple(BTN_FORWARD, AMOTION_EVENT_BUTTON_FORWARD, + AKEYCODE_FORWARD))); + +TEST_F(CursorInputMapperUnitTestWithChoreographer, ProcessWhenModeIsPointerShouldKeepZeroCoords) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + createMapper(); + + mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); + mFakePointerController->setPosition(100, 200); + + std::list<NotifyArgs> args; + + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithSource(AINPUT_SOURCE_MOUSE), + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithCoords(0.0f, 0.0f), WithPressure(0.0f), WithSize(0.0f), + WithTouchDimensions(0.0f, 0.0f), WithToolDimensions(0.0f, 0.0f), + WithOrientation(0.0f), WithDistance(0.0f))))); +} + +TEST_F(CursorInputMapperUnitTestWithChoreographer, PointerCaptureDisablesVelocityProcessing) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + const VelocityControlParameters testParams(/*scale=*/5.f, /*lowThreshold=*/0.f, + /*highThreshold=*/100.f, /*acceleration=*/10.f); + mReaderConfiguration.pointerVelocityControlParameters = testParams; + mFakePolicy->setVelocityControlParams(testParams); + createMapper(); + + NotifyMotionArgs motionArgs; + std::list<NotifyArgs> args; + + // Move and verify scale is applied. + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithSource(AINPUT_SOURCE_MOUSE), + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))))); + motionArgs = std::get<NotifyMotionArgs>(args.front()); + const float relX = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); + const float relY = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); + ASSERT_GT(relX, 10); + ASSERT_GT(relY, 20); + args.clear(); + + // Enable Pointer Capture + setPointerCapture(true); + + // Move and verify scale is not applied. + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithSource(AINPUT_SOURCE_MOUSE_RELATIVE), + WithMotionAction(AMOTION_EVENT_ACTION_MOVE))))); + motionArgs = std::get<NotifyMotionArgs>(args.front()); + const float relX2 = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); + const float relY2 = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); + ASSERT_EQ(10, relX2); + ASSERT_EQ(20, relY2); +} + +TEST_F(CursorInputMapperUnitTestWithChoreographer, ConfigureDisplayIdNoAssociatedViewport) { + // Set up the default display. + mFakePolicy->clearViewports(); + mFakePolicy->addDisplayViewport(createPrimaryViewport(ui::Rotation::Rotation0)); + + // Set up the secondary display as the display on which the pointer should be shown. + // The InputDevice is not associated with any display. + mFakePolicy->addDisplayViewport(createSecondaryViewport()); + mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); + + createMapper(); + + mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); + mFakePointerController->setPosition(100, 200); + + // Ensure input events are generated without display ID or coords, because they will be decided + // later by PointerChoreographer. + std::list<NotifyArgs> args; + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(ADISPLAY_ID_NONE), + WithCoords(0.0f, 0.0f))))); +} + +// TODO(b/320433834): De-duplicate the test cases once the flag is removed. +class CursorInputMapperUnitTestWithNewBallistics : public CursorInputMapperUnitTestBase { +protected: + void SetUp() override { + input_flags::enable_pointer_choreographer(true); + input_flags::enable_new_mouse_pointer_ballistics(true); + CursorInputMapperUnitTestBase::SetUp(); + } +}; + +TEST_F(CursorInputMapperUnitTestWithNewBallistics, PointerCaptureDisablesVelocityProcessing) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + createMapper(); + + NotifyMotionArgs motionArgs; + std::list<NotifyArgs> args; + + // Move and verify scale is applied. + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + motionArgs = std::get<NotifyMotionArgs>(args.front()); + const float relX = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); + const float relY = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); + ASSERT_GT(relX, 10); + ASSERT_GT(relY, 20); + args.clear(); + + // Enable Pointer Capture + setPointerCapture(true); + + // Move and verify scale is not applied. + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + motionArgs = std::get<NotifyMotionArgs>(args.front()); + const float relX2 = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); + const float relY2 = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); + ASSERT_EQ(10, relX2); + ASSERT_EQ(20, relY2); +} + +TEST_F(CursorInputMapperUnitTestWithNewBallistics, ConfigureAccelerationWithAssociatedViewport) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation0); + mReaderConfiguration.setDisplayViewports({primaryViewport}); + createDevice(); + ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, primaryViewport); + mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration); + + std::list<NotifyArgs> args; + + // Verify that acceleration is being applied by default by checking that the movement is scaled. + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(HOVER_MOVE), WithDisplayId(DISPLAY_ID))))); + const auto& coords = get<NotifyMotionArgs>(args.back()).pointerCoords[0]; + ASSERT_GT(coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X), 10.f); + ASSERT_GT(coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y), 20.f); + + // Disable acceleration for the display, and verify that acceleration is no longer applied. + mReaderConfiguration.displaysWithMousePointerAccelerationDisabled.emplace(DISPLAY_ID); + args += mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration, + InputReaderConfiguration::Change::POINTER_SPEED); + args.clear(); + + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>(AllOf(WithMotionAction(HOVER_MOVE), + WithDisplayId(DISPLAY_ID), + WithRelativeMotion(10, 20))))); +} + +TEST_F(CursorInputMapperUnitTestWithNewBallistics, ConfigureAccelerationOnDisplayChange) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation0); + mReaderConfiguration.setDisplayViewports({primaryViewport}); + // Disable acceleration for the display. + mReaderConfiguration.displaysWithMousePointerAccelerationDisabled.emplace(DISPLAY_ID); + createDevice(); + + // Don't associate the device with the display yet. + ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, + /*viewport=*/std::nullopt); + mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration); + + std::list<NotifyArgs> args; + + // Verify that acceleration is being applied by default by checking that the movement is scaled. + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)))); + const auto& coords = get<NotifyMotionArgs>(args.back()).pointerCoords[0]; + ASSERT_GT(coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X), 10.f); + ASSERT_GT(coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y), 20.f); + + // Now associate the device with the display, and verify that acceleration is disabled. + deviceContext.setViewport(primaryViewport); + args += mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration, + InputReaderConfiguration::Change::DISPLAY_INFO); + args.clear(); + + args += process(ARBITRARY_TIME, EV_REL, REL_X, 10); + args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(HOVER_MOVE), WithDisplayId(DISPLAY_ID), + WithRelativeMotion(10, 20))))); +} + +namespace { + +// Minimum timestamp separation between subsequent input events from a Bluetooth device. +constexpr nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4); +// Maximum smoothing time delta so that we don't generate events too far into the future. +constexpr nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32); + +} // namespace + +class BluetoothCursorInputMapperUnitTest : public CursorInputMapperUnitTestBase { +protected: + void SetUp() override { + input_flags::enable_pointer_choreographer(false); + SetUpWithBus(BUS_BLUETOOTH); + + mFakePointerController = std::make_shared<FakePointerController>(); + mFakePolicy->setPointerController(mFakePointerController); + } +}; + +TEST_F(BluetoothCursorInputMapperUnitTest, TimestampSmoothening) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + createMapper(); + std::list<NotifyArgs> argsList; + + nsecs_t kernelEventTime = ARBITRARY_TIME; + nsecs_t expectedEventTime = ARBITRARY_TIME; + argsList += process(kernelEventTime, EV_REL, REL_X, 1); + argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(argsList, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime))))); + argsList.clear(); + + // Process several events that come in quick succession, according to their timestamps. + for (int i = 0; i < 3; i++) { + constexpr static nsecs_t delta = ms2ns(1); + static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA); + kernelEventTime += delta; + expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; + + argsList += process(kernelEventTime, EV_REL, REL_X, 1); + argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(argsList, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime))))); + argsList.clear(); + } +} + +TEST_F(BluetoothCursorInputMapperUnitTest, TimestampSmootheningIsCapped) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + createMapper(); + std::list<NotifyArgs> argsList; + + nsecs_t expectedEventTime = ARBITRARY_TIME; + argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1); + argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(argsList, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime))))); + argsList.clear(); + + // Process several events with the same timestamp from the kernel. + // Ensure that we do not generate events too far into the future. + constexpr static int32_t numEvents = + MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA; + for (int i = 0; i < numEvents; i++) { + expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; + + argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1); + argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(argsList, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime))))); + argsList.clear(); + } + + // By processing more events with the same timestamp, we should not generate events with a + // timestamp that is more than the specified max time delta from the timestamp at its injection. + const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA; + for (int i = 0; i < 3; i++) { + argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1); + argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(argsList, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(cappedEventTime))))); + argsList.clear(); + } +} + +TEST_F(BluetoothCursorInputMapperUnitTest, TimestampSmootheningNotUsed) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + createMapper(); + std::list<NotifyArgs> argsList; + + nsecs_t kernelEventTime = ARBITRARY_TIME; + nsecs_t expectedEventTime = ARBITRARY_TIME; + argsList += process(kernelEventTime, EV_REL, REL_X, 1); + argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(argsList, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime))))); + argsList.clear(); + + // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp + // smoothening is not needed, its timestamp is not affected. + kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1); + expectedEventTime = kernelEventTime; + + argsList += process(kernelEventTime, EV_REL, REL_X, 1); + argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(argsList, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime))))); + argsList.clear(); +} + +// --- BluetoothCursorInputMapperUnitTestWithChoreographer --- + +class BluetoothCursorInputMapperUnitTestWithChoreographer : public CursorInputMapperUnitTestBase { +protected: + void SetUp() override { + input_flags::enable_pointer_choreographer(true); + SetUpWithBus(BUS_BLUETOOTH); + + mFakePointerController = std::make_shared<FakePointerController>(); + mFakePolicy->setPointerController(mFakePointerController); + } +}; + +TEST_F(BluetoothCursorInputMapperUnitTestWithChoreographer, TimestampSmoothening) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + createMapper(); + std::list<NotifyArgs> argsList; + + nsecs_t kernelEventTime = ARBITRARY_TIME; + nsecs_t expectedEventTime = ARBITRARY_TIME; + argsList += process(kernelEventTime, EV_REL, REL_X, 1); + argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(argsList, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime))))); + argsList.clear(); + + // Process several events that come in quick succession, according to their timestamps. + for (int i = 0; i < 3; i++) { + constexpr static nsecs_t delta = ms2ns(1); + static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA); + kernelEventTime += delta; + expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; + + argsList += process(kernelEventTime, EV_REL, REL_X, 1); + argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(argsList, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime))))); + argsList.clear(); + } +} + +TEST_F(BluetoothCursorInputMapperUnitTestWithChoreographer, TimestampSmootheningIsCapped) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + createMapper(); + std::list<NotifyArgs> argsList; + + nsecs_t expectedEventTime = ARBITRARY_TIME; + argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1); + argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(argsList, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime))))); + argsList.clear(); + + // Process several events with the same timestamp from the kernel. + // Ensure that we do not generate events too far into the future. + constexpr static int32_t numEvents = + MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA; + for (int i = 0; i < numEvents; i++) { + expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; + + argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1); + argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(argsList, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime))))); + argsList.clear(); + } + + // By processing more events with the same timestamp, we should not generate events with a + // timestamp that is more than the specified max time delta from the timestamp at its injection. + const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA; + for (int i = 0; i < 3; i++) { + argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1); + argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(argsList, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(cappedEventTime))))); + argsList.clear(); + } +} + +TEST_F(BluetoothCursorInputMapperUnitTestWithChoreographer, TimestampSmootheningNotUsed) { + mPropertyMap.addProperty("cursor.mode", "pointer"); + createMapper(); + std::list<NotifyArgs> argsList; + + nsecs_t kernelEventTime = ARBITRARY_TIME; + nsecs_t expectedEventTime = ARBITRARY_TIME; + argsList += process(kernelEventTime, EV_REL, REL_X, 1); + argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(argsList, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime))))); + argsList.clear(); + + // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp + // smoothening is not needed, its timestamp is not affected. + kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1); + expectedEventTime = kernelEventTime; + + argsList += process(kernelEventTime, EV_REL, REL_X, 1); + argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0); + EXPECT_THAT(argsList, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime))))); + argsList.clear(); +} + } // namespace android diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp index 212fcebecd..daa000f2ce 100644 --- a/services/inputflinger/tests/FakeEventHub.cpp +++ b/services/inputflinger/tests/FakeEventHub.cpp @@ -431,6 +431,38 @@ status_t FakeEventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, return -1; } +void FakeEventHub::setMtSlotValues(int32_t deviceId, int32_t axis, + const std::vector<int32_t>& values) { + Device* device = getDevice(deviceId); + if (!device) { + FAIL() << "Missing device"; + } + device->mtSlotValues[axis] = values; +} + +base::Result<std::vector<int32_t>> FakeEventHub::getMtSlotValues(int32_t deviceId, int32_t axis, + size_t slotCount) const { + Device* device = getDevice(deviceId); + if (!device) { + ADD_FAILURE() << "Missing device"; + return base::ResultError("Missing device", UNKNOWN_ERROR); + } + const auto& mtSlotValuesIterator = device->mtSlotValues.find(axis); + if (mtSlotValuesIterator == device->mtSlotValues.end()) { + return base::ResultError("axis not supported", NAME_NOT_FOUND); + } + const auto& mtSlotValues = mtSlotValuesIterator->second; + if (mtSlotValues.size() != slotCount) { + ADD_FAILURE() << "MtSlot values specified for " << mtSlotValues.size() + << " slots but expected for " << slotCount << " Slots"; + return base::ResultError("Slot count mismatch", NAME_NOT_FOUND); + } + std::vector<int32_t> outValues(slotCount + 1); + outValues[0] = axis; + std::copy(mtSlotValues.begin(), mtSlotValues.end(), outValues.begin() + 1); + return std::move(outValues); +} + int32_t FakeEventHub::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const { Device* device = getDevice(deviceId); if (!device) { diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h index 8e06940aec..f07b3441c2 100644 --- a/services/inputflinger/tests/FakeEventHub.h +++ b/services/inputflinger/tests/FakeEventHub.h @@ -65,6 +65,7 @@ class FakeEventHub : public EventHubInterface { bool enabled; std::optional<RawLayoutInfo> layoutInfo; std::string sysfsRootPath; + std::unordered_map<int32_t, std::vector<int32_t>> mtSlotValues; status_t enable() { enabled = true; @@ -154,6 +155,11 @@ public: int32_t value); void assertQueueIsEmpty(); void setSysfsRootPath(int32_t deviceId, std::string sysfsRootPath) const; + // Populate fake slot values to be returned by the getter, size of the values should be equal to + // the slot count + void setMtSlotValues(int32_t deviceId, int32_t axis, const std::vector<int32_t>& values); + base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis, + size_t slotCount) const override; private: Device* getDevice(int32_t deviceId) const; diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h index e9d93af67b..fb2db06aff 100644 --- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h +++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h @@ -63,7 +63,7 @@ private: void interceptKeyBeforeQueueing(const KeyEvent&, uint32_t&) override {} - void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {} + void interceptMotionBeforeQueueing(int32_t, uint32_t, int32_t, nsecs_t, uint32_t&) override {} nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override { return 0; diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp index 88f514f1d4..9e9371248e 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp +++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp @@ -41,15 +41,21 @@ void FakeInputReaderPolicy::assertInputDevicesNotChanged() { } void FakeInputReaderPolicy::assertStylusGestureNotified(int32_t deviceId) { - std::scoped_lock lock(mLock); - ASSERT_TRUE(mStylusGestureNotified); - ASSERT_EQ(deviceId, *mStylusGestureNotified); - mStylusGestureNotified.reset(); + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + + const bool success = + mStylusGestureNotifiedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) { + return mDeviceIdOfNotifiedStylusGesture.has_value(); + }); + ASSERT_TRUE(success) << "Timed out waiting for stylus gesture to be notified"; + ASSERT_EQ(deviceId, *mDeviceIdOfNotifiedStylusGesture); + mDeviceIdOfNotifiedStylusGesture.reset(); } void FakeInputReaderPolicy::assertStylusGestureNotNotified() { std::scoped_lock lock(mLock); - ASSERT_FALSE(mStylusGestureNotified); + ASSERT_FALSE(mDeviceIdOfNotifiedStylusGesture); } void FakeInputReaderPolicy::clearViewports() { @@ -258,7 +264,8 @@ void FakeInputReaderPolicy::waitForInputDevices(std::function<void(bool)> proces void FakeInputReaderPolicy::notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) { std::scoped_lock lock(mLock); - mStylusGestureNotified = deviceId; + mDeviceIdOfNotifiedStylusGesture = deviceId; + mStylusGestureNotifiedCondition.notify_all(); } std::optional<DisplayViewport> FakeInputReaderPolicy::getPointerViewportForAssociatedDisplay( diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h index 4ef9c3ee4a..da5085db7c 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.h +++ b/services/inputflinger/tests/FakeInputReaderPolicy.h @@ -102,9 +102,11 @@ private: bool mInputDevicesChanged GUARDED_BY(mLock){false}; std::vector<DisplayViewport> mViewports; TouchAffineTransformation transform; - std::optional<int32_t /*deviceId*/> mStylusGestureNotified GUARDED_BY(mLock){}; bool mIsInputMethodConnectionActive{false}; + std::condition_variable mStylusGestureNotifiedCondition; + std::optional<DeviceId> mDeviceIdOfNotifiedStylusGesture GUARDED_BY(mLock){}; + uint32_t mNextPointerCaptureSequenceNumber{0}; }; diff --git a/services/inputflinger/tests/FakeInputTracingBackend.cpp b/services/inputflinger/tests/FakeInputTracingBackend.cpp new file mode 100644 index 0000000000..f4a06f7d28 --- /dev/null +++ b/services/inputflinger/tests/FakeInputTracingBackend.cpp @@ -0,0 +1,138 @@ +/* + * Copyright 2024 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 "FakeInputTracingBackend.h" + +#include <android-base/logging.h> +#include <utils/Errors.h> + +namespace android::inputdispatcher { + +namespace { + +// Use a larger timeout while waiting for events to be traced, compared to the timeout used while +// waiting to receive events through the input channel. Events are traced from a separate thread, +// which does not have the same high thread priority as the InputDispatcher's thread, so the tracer +// is expected to lag behind the Dispatcher at times. +constexpr auto TRACE_TIMEOUT = std::chrono::seconds(5); + +base::ResultError<> error(const std::ostringstream& ss) { + return base::ResultError(ss.str(), BAD_VALUE); +} + +inline auto getId(const trace::TracedEvent& v) { + return std::visit([](const auto& event) { return event.id; }, v); +} + +} // namespace + +// --- VerifyingTrace --- + +void VerifyingTrace::expectKeyDispatchTraced(const KeyEvent& event, int32_t windowId) { + std::scoped_lock lock(mLock); + mExpectedEvents.emplace_back(event, windowId); +} + +void VerifyingTrace::expectMotionDispatchTraced(const MotionEvent& event, int32_t windowId) { + std::scoped_lock lock(mLock); + mExpectedEvents.emplace_back(event, windowId); +} + +void VerifyingTrace::verifyExpectedEventsTraced() { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + + base::Result<void> result; + mEventTracedCondition.wait_for(lock, TRACE_TIMEOUT, [&]() REQUIRES(mLock) { + for (const auto& [expectedEvent, windowId] : mExpectedEvents) { + std::visit([&](const auto& event) + REQUIRES(mLock) { result = verifyEventTraced(event, windowId); }, + expectedEvent); + if (!result.ok()) { + return false; + } + } + return true; + }); + + EXPECT_TRUE(result.ok()) + << "Timed out waiting for all expected events to be traced successfully: " + << result.error().message(); +} + +void VerifyingTrace::reset() { + std::scoped_lock lock(mLock); + mTracedEvents.clear(); + mTracedWindowDispatches.clear(); + mExpectedEvents.clear(); +} + +template <typename Event> +base::Result<void> VerifyingTrace::verifyEventTraced(const Event& expectedEvent, + int32_t expectedWindowId) const { + std::ostringstream msg; + + auto tracedEventsIt = mTracedEvents.find(expectedEvent.getId()); + if (tracedEventsIt == mTracedEvents.end()) { + msg << "Expected event with ID 0x" << std::hex << expectedEvent.getId() + << " to be traced, but it was not.\n" + << "Expected event: " << expectedEvent; + return error(msg); + } + + auto tracedDispatchesIt = + std::find_if(mTracedWindowDispatches.begin(), mTracedWindowDispatches.end(), + [&](const WindowDispatchArgs& args) { + return args.windowId == expectedWindowId && + getId(args.eventEntry) == expectedEvent.getId(); + }); + if (tracedDispatchesIt == mTracedWindowDispatches.end()) { + msg << "Expected dispatch of event with ID 0x" << std::hex << expectedEvent.getId() + << " to window with ID 0x" << expectedWindowId << " to be traced, but it was not." + << "\nExpected event: " << expectedEvent; + return error(msg); + } + + return {}; +} + +// --- FakeInputTracingBackend --- + +void FakeInputTracingBackend::traceKeyEvent(const trace::TracedKeyEvent& event) const { + { + std::scoped_lock lock(mTrace->mLock); + mTrace->mTracedEvents.emplace(event.id); + } + mTrace->mEventTracedCondition.notify_all(); +} + +void FakeInputTracingBackend::traceMotionEvent(const trace::TracedMotionEvent& event) const { + { + std::scoped_lock lock(mTrace->mLock); + mTrace->mTracedEvents.emplace(event.id); + } + mTrace->mEventTracedCondition.notify_all(); +} + +void FakeInputTracingBackend::traceWindowDispatch(const WindowDispatchArgs& args) const { + { + std::scoped_lock lock(mTrace->mLock); + mTrace->mTracedWindowDispatches.push_back(args); + } + mTrace->mEventTracedCondition.notify_all(); +} + +} // namespace android::inputdispatcher diff --git a/services/inputflinger/tests/FakeInputTracingBackend.h b/services/inputflinger/tests/FakeInputTracingBackend.h new file mode 100644 index 0000000000..40ca3a29f8 --- /dev/null +++ b/services/inputflinger/tests/FakeInputTracingBackend.h @@ -0,0 +1,91 @@ +/* + * Copyright 2024 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 "../dispatcher/trace/InputTracingBackendInterface.h" + +#include <android-base/result.h> +#include <android-base/thread_annotations.h> +#include <gtest/gtest.h> +#include <input/Input.h> +#include <condition_variable> +#include <memory> +#include <mutex> +#include <unordered_set> +#include <vector> + +namespace android::inputdispatcher { + +/** + * A class representing an input trace, used to make assertions on what was traced by + * InputDispatcher in tests. This class is thread-safe. + */ +class VerifyingTrace { +public: + VerifyingTrace() = default; + + /** Add an expectation for a key event to be traced. */ + void expectKeyDispatchTraced(const KeyEvent& event, int32_t windowId); + + /** Add an expectation for a motion event to be traced. */ + void expectMotionDispatchTraced(const MotionEvent& event, int32_t windowId); + + /** + * Wait and verify that all expected events are traced. + * This is a lenient verifier that does not expect the events to be traced in the order + * that the events were expected, and does not fail if there are events that are traced that + * were not expected. Verifying does not clear the expectations. + */ + void verifyExpectedEventsTraced(); + + /** Reset the trace and clear all expectations. */ + void reset(); + +private: + std::mutex mLock; + std::condition_variable mEventTracedCondition; + std::unordered_set<uint32_t /*eventId*/> mTracedEvents GUARDED_BY(mLock); + using WindowDispatchArgs = trace::InputTracingBackendInterface::WindowDispatchArgs; + std::vector<WindowDispatchArgs> mTracedWindowDispatches GUARDED_BY(mLock); + std::vector<std::pair<std::variant<KeyEvent, MotionEvent>, int32_t /*windowId*/>> + mExpectedEvents GUARDED_BY(mLock); + + friend class FakeInputTracingBackend; + + // Helper to verify that the given event appears as expected in the trace. If the verification + // fails, the error message describes why. + template <typename Event> + base::Result<void> verifyEventTraced(const Event&, int32_t windowId) const REQUIRES(mLock); +}; + +/** + * A backend implementation for input tracing that records events to the provided + * VerifyingTrace used for testing. + */ +class FakeInputTracingBackend : public trace::InputTracingBackendInterface { +public: + FakeInputTracingBackend(std::shared_ptr<VerifyingTrace> trace) : mTrace(trace) {} + +private: + std::shared_ptr<VerifyingTrace> mTrace; + + void traceKeyEvent(const trace::TracedKeyEvent& entry) const override; + void traceMotionEvent(const trace::TracedMotionEvent& entry) const override; + void traceWindowDispatch(const WindowDispatchArgs& entry) const override; +}; + +} // namespace android::inputdispatcher diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp index 80319f2487..31e117352b 100644 --- a/services/inputflinger/tests/FakePointerController.cpp +++ b/services/inputflinger/tests/FakePointerController.cpp @@ -28,6 +28,10 @@ void FakePointerController::setBounds(float minX, float minY, float maxX, float mMaxY = maxY; } +void FakePointerController::clearBounds() { + mHaveBounds = false; +} + const std::map<int32_t, std::vector<int32_t>>& FakePointerController::getSpots() { return mSpotsByDisplay; } diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h index 800f8649fd..061ae62b57 100644 --- a/services/inputflinger/tests/FakePointerController.h +++ b/services/inputflinger/tests/FakePointerController.h @@ -33,6 +33,7 @@ public: virtual ~FakePointerController() {} void setBounds(float minX, float minY, float maxX, float maxY); + void clearBounds(); const std::map<int32_t, std::vector<int32_t>>& getSpots(); void setPosition(float x, float y) override; diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp index d2b68dd93e..dd88165561 100644 --- a/services/inputflinger/tests/GestureConverter_test.cpp +++ b/services/inputflinger/tests/GestureConverter_test.cpp @@ -41,10 +41,15 @@ namespace { const auto TOUCHPAD_PALM_REJECTION = ACONFIG_FLAG(input_flags, enable_touchpad_typing_palm_rejection); +const auto TOUCHPAD_PALM_REJECTION_V2 = + ACONFIG_FLAG(input_flags, enable_v2_touchpad_typing_palm_rejection); } // namespace using testing::AllOf; +using testing::Each; +using testing::ElementsAre; +using testing::VariantWith; class GestureConverterTestBase : public testing::Test { protected: @@ -107,16 +112,36 @@ TEST_F(GestureConverterTest, Move) { converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); - ASSERT_EQ(1u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10), - WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(POINTER_X, POINTER_Y), + WithRelativeMotion(0, 0))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithCoords(POINTER_X - 5, POINTER_Y + 10), + WithRelativeMotion(-5, 10), WithButtonState(0), + WithPressure(0.0f))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10)); + + // The same gesture again should only repeat the HOVER_MOVE and cursor position change, not the + // HOVER_ENTER. + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithCoords(POINTER_X - 10, POINTER_Y + 20), + WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER), + WithButtonState(0), WithPressure(0.0f), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); + + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 10, POINTER_Y + 20)); } TEST_F(GestureConverterTest, Move_Rotated) { @@ -126,14 +151,21 @@ TEST_F(GestureConverterTest, Move_Rotated) { converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); - ASSERT_EQ(1u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithCoords(POINTER_X + 10, POINTER_Y + 5), WithRelativeMotion(10, 5), - WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(POINTER_X, POINTER_Y), + WithRelativeMotion(0, 0))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithCoords(POINTER_X + 10, POINTER_Y + 5), + WithRelativeMotion(10, 5), WithButtonState(0), + WithPressure(0.0f))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X + 10, POINTER_Y + 5)); } @@ -147,67 +179,78 @@ TEST_F(GestureConverterTest, ButtonsChange) { Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT, /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture); - ASSERT_EQ(3u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | - AMOTION_EVENT_BUTTON_SECONDARY), - WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), - WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | - AMOTION_EVENT_BUTTON_SECONDARY), - WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | + AMOTION_EVENT_BUTTON_SECONDARY))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | + AMOTION_EVENT_BUTTON_SECONDARY))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(POINTER_X, POINTER_Y), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); // Then release the left button Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, leftUpGesture); - ASSERT_EQ(1u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), - WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, leftUpGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), + WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); // Finally release the right button Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_RIGHT, /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, rightUpGesture); - ASSERT_EQ(3u, args.size()); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, rightUpGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY))), + VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_UP)), + VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithButtonState(0), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); +} - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0), - WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0), - WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0), - WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); +TEST_F(GestureConverterTest, ButtonDownAfterMoveExitsHover) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + converter.setDisplayId(ADISPLAY_ID_DEFAULT); + + Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); + + Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, + /*down=*/GESTURES_BUTTON_LEFT, /*up=*/GESTURES_BUTTON_NONE, + /*is_tap=*/false); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture); + ASSERT_THAT(args.front(), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), WithButtonState(0), + WithCoords(POINTER_X - 5, POINTER_Y + 10), + WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)))); } TEST_F(GestureConverterTest, DragWithButton) { @@ -219,32 +262,31 @@ TEST_F(GestureConverterTest, DragWithButton) { Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture); - ASSERT_EQ(2u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), - WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), - WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(POINTER_X, POINTER_Y), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); // Move Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); - ASSERT_EQ(1u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10), - WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), - WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10), + WithToolType(ToolType::FINGER), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10)); @@ -252,24 +294,20 @@ TEST_F(GestureConverterTest, DragWithButton) { Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, upGesture); - ASSERT_EQ(3u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0), - WithCoords(POINTER_X - 5, POINTER_Y + 10), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0), - WithCoords(POINTER_X - 5, POINTER_Y + 10), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0), - WithCoords(POINTER_X - 5, POINTER_Y + 10), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, upGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY))), + VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_UP)), + VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithButtonState(0), + WithCoords(POINTER_X - 5, POINTER_Y + 10), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, Scroll) { @@ -279,50 +317,55 @@ TEST_F(GestureConverterTest, Scroll) { converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); - std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture); - ASSERT_EQ(2u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y), - WithGestureScrollDistance(0, 0, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), WithDownTime(downTime), - WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithCoords(POINTER_X, POINTER_Y - 10), - WithGestureScrollDistance(0, 10, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), - WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(downTime, READ_TIME, ARBITRARY_TIME, startGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(POINTER_X, POINTER_Y), + WithGestureScrollDistance(0, 0, EPSILON), + WithDownTime(downTime))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithCoords(POINTER_X, POINTER_Y - 10), + WithGestureScrollDistance(0, 10, EPSILON))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), + WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithCoords(POINTER_X, POINTER_Y - 15), - WithGestureScrollDistance(0, 5, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), - WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithCoords(POINTER_X, POINTER_Y - 15), + WithGestureScrollDistance(0, 5, EPSILON), + WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), + WithToolType(ToolType::FINGER), + WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, GESTURES_FLING_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithCoords(POINTER_X, POINTER_Y - 15), - WithGestureScrollDistance(0, 0, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), - WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(POINTER_X, POINTER_Y - 15), + WithGestureScrollDistance(0, 0, EPSILON), + WithMotionClassification( + MotionClassification::TWO_FINGER_SWIPE), + WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(POINTER_X, POINTER_Y), + WithMotionClassification(MotionClassification::NONE))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, Scroll_Rotated) { @@ -333,43 +376,51 @@ TEST_F(GestureConverterTest, Scroll_Rotated) { converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); - std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture); - ASSERT_EQ(2u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y), - WithGestureScrollDistance(0, 0, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), WithDownTime(downTime), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithCoords(POINTER_X - 10, POINTER_Y), - WithGestureScrollDistance(0, 10, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(downTime, READ_TIME, ARBITRARY_TIME, startGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(POINTER_X, POINTER_Y), + WithGestureScrollDistance(0, 0, EPSILON), + WithDownTime(downTime))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithCoords(POINTER_X - 10, POINTER_Y), + WithGestureScrollDistance(0, 10, EPSILON))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithCoords(POINTER_X - 15, POINTER_Y), - WithGestureScrollDistance(0, 5, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); - + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithCoords(POINTER_X - 15, POINTER_Y), + WithGestureScrollDistance(0, 5, EPSILON), + WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, GESTURES_FLING_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithCoords(POINTER_X - 15, POINTER_Y), - WithGestureScrollDistance(0, 0, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(POINTER_X - 15, POINTER_Y), + WithGestureScrollDistance(0, 0, EPSILON), + WithMotionClassification( + MotionClassification::TWO_FINGER_SWIPE))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(POINTER_X, POINTER_Y), + WithMotionClassification(MotionClassification::NONE))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, Scroll_ClearsClassificationAfterGesture) { @@ -378,21 +429,22 @@ TEST_F(GestureConverterTest, Scroll_ClearsClassificationAfterGesture) { converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, GESTURES_FLING_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); - ASSERT_EQ(1u, args.size()); - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionClassification(MotionClassification::NONE), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionClassification(MotionClassification::NONE), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, Scroll_ClearsScrollDistanceAfterGesture) { @@ -401,20 +453,21 @@ TEST_F(GestureConverterTest, Scroll_ClearsScrollDistanceAfterGesture) { converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, GESTURES_FLING_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we // need to use another gesture type, like pinch. Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, pinchGesture); ASSERT_FALSE(args.empty()); EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGestureScrollDistance(0, 0, EPSILON)); } @@ -426,17 +479,18 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsClassificationAfterGesture) Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0, /*dy=*/0); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/-5, /*dy=*/10); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); - ASSERT_EQ(1u, args.size()); - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - WithMotionClassification(MotionClassification::NONE)); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionClassification(MotionClassification::NONE)))); } TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) { @@ -446,16 +500,17 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) { Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/5, /*dy=*/5); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture); // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we // need to use another gesture type, like pinch. Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, pinchGesture); ASSERT_FALSE(args.empty()); EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), AllOf(WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(0))); @@ -472,48 +527,42 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, /* dy= */ 10); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); ASSERT_EQ(4u, args.size()); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithGestureSwipeFingerCount(3), WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); // Three fake fingers should be created. We don't actually care where they are, so long as they // move appropriately. NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON), - WithGestureSwipeFingerCount(3), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(1u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithPointerCount(1u))); PointerCoords finger0Start = arg.pointerCoords[0]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(2u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u))); PointerCoords finger1Start = arg.pointerCoords[1]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u))); PointerCoords finger2Start = arg.pointerCoords[2]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithGestureOffset(0, -0.01, EPSILON), WithGestureSwipeFingerCount(3), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u))); EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX()); EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX()); EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX()); @@ -523,7 +572,7 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, /* dy= */ 5); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); ASSERT_EQ(1u, args.size()); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, @@ -540,30 +589,40 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 15); Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture); - ASSERT_EQ(3u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(2u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON), - WithGestureSwipeFingerCount(3), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(1u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithGestureSwipeFingerCount(3), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(3u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithGestureSwipeFingerCount(3), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(2u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithGestureOffset(0, 0, EPSILON), + WithGestureSwipeFingerCount(3), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(1u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(POINTER_X, POINTER_Y), + WithMotionClassification(MotionClassification::NONE))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) { @@ -574,39 +633,38 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) { Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, /* dy= */ 10); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); ASSERT_EQ(4u, args.size()); + ASSERT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithDisplayId(ADISPLAY_ID_DEFAULT)))); // Three fake fingers should be created. We don't actually care where they are, so long as they // move appropriately. NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON), - WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithPointerCount(1u))); PointerCoords finger0Start = arg.pointerCoords[0]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u))); PointerCoords finger1Start = arg.pointerCoords[1]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u))); PointerCoords finger2Start = arg.pointerCoords[2]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u))); EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 10); EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 10); EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 10); @@ -616,7 +674,7 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) { Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, /* dy= */ 5); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); ASSERT_EQ(1u, args.size()); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, @@ -631,23 +689,24 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) { EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY()); Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture); - ASSERT_EQ(3u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON), - WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithGestureOffset(0, 0, EPSILON), WithPointerCount(1u))), + VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)))); + ASSERT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithDisplayId(ADISPLAY_ID_DEFAULT)))); } TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { @@ -657,58 +716,49 @@ TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 10, /* dy= */ 0); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); ASSERT_EQ(5u, args.size()); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithGestureSwipeFingerCount(4), WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); // Four fake fingers should be created. We don't actually care where they are, so long as they // move appropriately. NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON), - WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(1u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithPointerCount(1u))); PointerCoords finger0Start = arg.pointerCoords[0]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(2u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u))); PointerCoords finger1Start = arg.pointerCoords[1]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u))); PointerCoords finger2Start = arg.pointerCoords[2]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(4u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0, 0, EPSILON), WithPointerCount(4u))); PointerCoords finger3Start = arg.pointerCoords[3]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithGestureOffset(0.01, 0, EPSILON), WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(4u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0.01, 0, EPSILON), WithPointerCount(4u))); EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 10); EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 10); EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 10); @@ -720,7 +770,7 @@ TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { Gesture continueGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 5, /* dy= */ 0); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); ASSERT_EQ(1u, args.size()); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, @@ -739,38 +789,49 @@ TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY()); Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture); - ASSERT_EQ(4u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(4u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(2u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON), - WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(1u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithGestureSwipeFingerCount(4), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(4u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithGestureSwipeFingerCount(4), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(3u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithGestureSwipeFingerCount(4), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(2u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithGestureOffset(0, 0, EPSILON), + WithGestureSwipeFingerCount(4), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(1u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(POINTER_X, POINTER_Y), + WithMotionClassification(MotionClassification::NONE))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, Pinch_Inwards) { @@ -780,51 +841,62 @@ TEST_F(GestureConverterTest, Pinch_Inwards) { Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, GESTURES_ZOOM_START); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); - ASSERT_EQ(2u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), - WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), - WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(POINTER_X - 100, POINTER_Y), + WithPointerCount(1u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithPointerCoords(1, POINTER_X + 100, POINTER_Y), + WithPointerCount(2u))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 0.8, GESTURES_ZOOM_UPDATE); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture); - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(0.8f, EPSILON), - WithPointerCoords(0, POINTER_X - 80, POINTER_Y), - WithPointerCoords(1, POINTER_X + 80, POINTER_Y), WithPointerCount(2u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(0.8f, EPSILON), + WithPointerCoords(0, POINTER_X - 80, POINTER_Y), + WithPointerCoords(1, POINTER_X + 80, POINTER_Y), WithPointerCount(2u), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, GESTURES_ZOOM_END); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture); - ASSERT_EQ(2u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithPointerCount(2u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithPointerCount(1u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(POINTER_X, POINTER_Y), + WithMotionClassification(MotionClassification::NONE))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, Pinch_Outwards) { @@ -834,51 +906,62 @@ TEST_F(GestureConverterTest, Pinch_Outwards) { Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, GESTURES_ZOOM_START); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); - ASSERT_EQ(2u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), - WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), - WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(POINTER_X - 100, POINTER_Y), + WithPointerCount(1u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithPointerCoords(1, POINTER_X + 100, POINTER_Y), + WithPointerCount(2u))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1.2, GESTURES_ZOOM_UPDATE); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture); - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.2f, EPSILON), - WithPointerCoords(0, POINTER_X - 120, POINTER_Y), - WithPointerCoords(1, POINTER_X + 120, POINTER_Y), WithPointerCount(2u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.2f, EPSILON), + WithPointerCoords(0, POINTER_X - 120, POINTER_Y), + WithPointerCoords(1, POINTER_X + 120, POINTER_Y), + WithPointerCount(2u), WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, GESTURES_ZOOM_END); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture); - ASSERT_EQ(2u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithPointerCount(2u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithPointerCount(1u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(POINTER_X, POINTER_Y), + WithMotionClassification(MotionClassification::NONE))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, Pinch_ClearsClassificationAfterGesture) { @@ -888,21 +971,22 @@ TEST_F(GestureConverterTest, Pinch_ClearsClassificationAfterGesture) { Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_START); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1.2, GESTURES_ZOOM_UPDATE); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture); Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_END); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); - ASSERT_EQ(1u, args.size()); - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - WithMotionClassification(MotionClassification::NONE)); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionClassification(MotionClassification::NONE)))); } TEST_F(GestureConverterTest, Pinch_ClearsScaleFactorAfterGesture) { @@ -912,21 +996,22 @@ TEST_F(GestureConverterTest, Pinch_ClearsScaleFactorAfterGesture) { Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_START); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1.2, GESTURES_ZOOM_UPDATE); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture); Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_END); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture); // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we // need to use another gesture type, like scroll. Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/1, /*dy=*/0); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, scrollGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, scrollGesture); ASSERT_FALSE(args.empty()); EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGesturePinchScaleFactor(0, EPSILON)); } @@ -939,28 +1024,28 @@ TEST_F(GestureConverterTest, ResetWithButtonPressed) { Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*down=*/GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT, /*up=*/GESTURES_BUTTON_NONE, /*is_tap=*/false); - (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture); + (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture); std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME); - ASSERT_EQ(3u, args.size()); - - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), - WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0), - WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0), - WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), + WithButtonState(0))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithButtonState(0))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithButtonState(0))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(POINTER_X, POINTER_Y), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, ResetDuringScroll) { @@ -969,18 +1054,24 @@ TEST_F(GestureConverterTest, ResetDuringScroll) { converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); - (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME); - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithCoords(POINTER_X, POINTER_Y - 10), - WithGestureScrollDistance(0, 0, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), - WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(POINTER_X, POINTER_Y - 10), + WithGestureScrollDistance(0, 0, EPSILON), + WithMotionClassification( + MotionClassification::TWO_FINGER_SWIPE), + WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(POINTER_X, POINTER_Y), + WithMotionClassification(MotionClassification::NONE))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) { @@ -990,31 +1081,38 @@ TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) { Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0, /*dy=*/10); - (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME); - ASSERT_EQ(3u, args.size()); - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(2u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(1u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(3u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(2u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(1u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithMotionClassification(MotionClassification::NONE))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, ResetDuringPinch) { @@ -1024,22 +1122,29 @@ TEST_F(GestureConverterTest, ResetDuringPinch) { Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_START); - (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME); - ASSERT_EQ(2u, args.size()); - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithPointerCount(2u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithPointerCount(1u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(POINTER_X, POINTER_Y), + WithMotionClassification(MotionClassification::NONE))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, FlingTapDown) { @@ -1049,10 +1154,11 @@ TEST_F(GestureConverterTest, FlingTapDown) { Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapDownGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapDownGesture); ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); @@ -1069,52 +1175,43 @@ TEST_F(GestureConverterTest, Tap) { Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0, /* vy= */ 0, GESTURES_FLING_TAP_DOWN); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); - - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0), - WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); + // We don't need to check args here, since it's covered by the FlingTapDown test. Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture); - - ASSERT_EQ(5u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y), - WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), - WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f), - WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), - WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0), - WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f), - WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(POINTER_X, POINTER_Y), - WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0), - WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapGesture); + + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithButtonState(0), WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(0), WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithButtonState(0), WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithButtonState(0), WithPressure(0.0f))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(POINTER_X, POINTER_Y), + WithRelativeMotion(0.f, 0.f), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, Click) { @@ -1125,62 +1222,61 @@ TEST_F(GestureConverterTest, Click) { Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0, /* vy= */ 0, GESTURES_FLING_TAP_DOWN); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); - - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0), - WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); + // We don't need to check args here, since it's covered by the FlingTapDown test. Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture); - - ASSERT_EQ(2u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y), - WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), - WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f), - WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), - WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture); + + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithButtonState(0), WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithPressure(1.0f))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(POINTER_X, POINTER_Y), + WithRelativeMotion(0.f, 0.f), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture); - - ASSERT_EQ(3u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0), - WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f), - WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(POINTER_X, POINTER_Y), - WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0), - WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithPressure(0.0f))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>( + AllOf(WithButtonState(0), WithCoords(POINTER_X, POINTER_Y), + WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabled, - REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) { + REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION), + REQUIRES_FLAGS_DISABLED(TOUCHPAD_PALM_REJECTION_V2)) { + nsecs_t currentTime = ARBITRARY_GESTURE_TIME; + // Tap should be ignored when disabled mReader->getContext()->setPreventingTouchpadTaps(true); @@ -1188,28 +1284,106 @@ TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabled, GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); - Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0, + Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0, /* vy= */ 0, GESTURES_FLING_TAP_DOWN); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); + std::list<NotifyArgs> args = + converter.handleGesture(currentTime, currentTime, currentTime, flingGesture); + // We don't need to check args here, since it's covered by the FlingTapDown test. - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0), - WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); + Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime, + /* down= */ GESTURES_BUTTON_LEFT, + /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true); + args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture); - Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, + // no events should be generated + ASSERT_EQ(0u, args.size()); + + // Future taps should be re-enabled + ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps()); +} + +TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabledWithDelay, + REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) { + nsecs_t currentTime = ARBITRARY_GESTURE_TIME; + + // Tap should be ignored when disabled + mReader->getContext()->setPreventingTouchpadTaps(true); + + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + converter.setDisplayId(ADISPLAY_ID_DEFAULT); + + Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0, + /* vy= */ 0, GESTURES_FLING_TAP_DOWN); + std::list<NotifyArgs> args = + converter.handleGesture(currentTime, currentTime, currentTime, flingGesture); + // We don't need to check args here, since it's covered by the FlingTapDown test. + + Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime, /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture); + args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture); // no events should be generated ASSERT_EQ(0u, args.size()); // Future taps should be re-enabled ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps()); + + // taps before the threshold should still be ignored + currentTime += TAP_ENABLE_DELAY_NANOS.count(); + flingGesture = Gesture(kGestureFling, currentTime, currentTime, /* vx= */ 0, + /* vy= */ 0, GESTURES_FLING_TAP_DOWN); + args = converter.handleGesture(currentTime, currentTime, currentTime, flingGesture); + + ASSERT_EQ(1u, args.size()); + ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0))); + + tapGesture = Gesture(kGestureButtonsChange, currentTime, currentTime, + /* down= */ GESTURES_BUTTON_LEFT, + /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true); + args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture); + + // no events should be generated + ASSERT_EQ(0u, args.size()); + + // taps after the threshold should be recognised + currentTime += 1; + flingGesture = Gesture(kGestureFling, currentTime, currentTime, /* vx= */ 0, + /* vy= */ 0, GESTURES_FLING_TAP_DOWN); + args = converter.handleGesture(currentTime, currentTime, currentTime, flingGesture); + + ASSERT_EQ(1u, args.size()); + ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0))); + + tapGesture = Gesture(kGestureButtonsChange, currentTime, currentTime, + /* down= */ GESTURES_BUTTON_LEFT, + /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true); + args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithButtonState(0))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(0))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithButtonState(0))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithButtonState(0))))); + ASSERT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithRelativeMotion(0.f, 0.f)))); } TEST_F_WITH_FLAGS(GestureConverterTest, ClickWithTapToClickDisabled, @@ -1223,58 +1397,54 @@ TEST_F_WITH_FLAGS(GestureConverterTest, ClickWithTapToClickDisabled, Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0, /* vy= */ 0, GESTURES_FLING_TAP_DOWN); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); - - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0), - WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); + // We don't need to check args here, since it's covered by the FlingTapDown test. Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture); - ASSERT_EQ(2u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y), - WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), - WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f), - WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), - WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithButtonState(0), WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithPressure(1.0f))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(POINTER_X, POINTER_Y), + WithRelativeMotion(0.f, 0.f), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture); - - ASSERT_EQ(3u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0), - WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f), - WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(POINTER_X, POINTER_Y), - WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0), - WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture); + + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(0), WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithButtonState(0), WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithButtonState(0), WithPressure(0.0f))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(POINTER_X, POINTER_Y), + WithRelativeMotion(0.f, 0.f), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); // Future taps should be re-enabled ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps()); @@ -1290,21 +1460,57 @@ TEST_F_WITH_FLAGS(GestureConverterTest, MoveEnablesTapToClick, converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); - ASSERT_EQ(1u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10), - WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - - ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10)); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); + // We don't need to check args here, since it's covered by the Move test. // Future taps should be re-enabled ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps()); } +TEST_F_WITH_FLAGS(GestureConverterTest, KeypressCancelsHoverMove, + REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) { + const nsecs_t gestureStartTime = 1000; + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + converter.setDisplayId(ADISPLAY_ID_DEFAULT); + + // Start a move gesture at gestureStartTime + Gesture moveGesture(kGestureMove, gestureStartTime, gestureStartTime, -5, 10); + std::list<NotifyArgs> args = + converter.handleGesture(gestureStartTime, READ_TIME, gestureStartTime, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)), + VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)))); + + // Key presses with IME connection should cancel ongoing move gesture + nsecs_t currentTime = gestureStartTime + 100; + mFakePolicy->setIsInputMethodConnectionActive(true); + mReader->getContext()->setLastKeyDownTimestamp(currentTime); + moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10); + args = converter.handleGesture(currentTime, READ_TIME, gestureStartTime, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)))); + + // any updates in existing move gesture should be ignored + moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10); + args = converter.handleGesture(currentTime, READ_TIME, gestureStartTime, moveGesture); + ASSERT_EQ(0u, args.size()); + + // New gesture should not be affected + currentTime += 100; + moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10); + args = converter.handleGesture(currentTime, READ_TIME, currentTime, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)), + VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)))); +} + // TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging // logic can be removed. class GestureConverterTestWithChoreographer : public GestureConverterTestBase { @@ -1321,13 +1527,29 @@ TEST_F(GestureConverterTestWithChoreographer, Move) { converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); - ASSERT_EQ(1u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0), - WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER), - WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithRelativeMotion(0, 0))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithRelativeMotion(-5, 10), WithButtonState(0), + WithPressure(0.0f))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); + + // The same gesture again should only repeat the HOVER_MOVE, not the HOVER_ENTER. + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0), + WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER), + WithButtonState(0), WithPressure(0.0f), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTestWithChoreographer, Move_Rotated) { @@ -1337,13 +1559,20 @@ TEST_F(GestureConverterTestWithChoreographer, Move_Rotated) { converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); - ASSERT_EQ(1u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0), - WithRelativeMotion(10, 5), WithToolType(ToolType::FINGER), WithButtonState(0), - WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithRelativeMotion(0, 0))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithRelativeMotion(10, 5), WithButtonState(0), + WithPressure(0.0f))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTestWithChoreographer, ButtonsChange) { @@ -1355,65 +1584,77 @@ TEST_F(GestureConverterTestWithChoreographer, ButtonsChange) { Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT, /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture); - ASSERT_EQ(3u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | - AMOTION_EVENT_BUTTON_SECONDARY), - WithCoords(0, 0), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | - AMOTION_EVENT_BUTTON_SECONDARY), - WithCoords(0, 0), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | + AMOTION_EVENT_BUTTON_SECONDARY))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | + AMOTION_EVENT_BUTTON_SECONDARY))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); // Then release the left button Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, leftUpGesture); - ASSERT_EQ(1u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(0, 0), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, leftUpGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(0, 0), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); // Finally release the right button Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_RIGHT, /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, rightUpGesture); - ASSERT_EQ(3u, args.size()); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, rightUpGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY))), + VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_UP)), + VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithButtonState(0), WithCoords(0, 0), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); +} - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0), - WithCoords(0, 0), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0), - WithCoords(0, 0), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0), - WithCoords(0, 0), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); +TEST_F(GestureConverterTestWithChoreographer, ButtonDownAfterMoveExitsHover) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + converter.setDisplayId(ADISPLAY_ID_DEFAULT); + + Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); + + Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, + /*down=*/GESTURES_BUTTON_LEFT, /*up=*/GESTURES_BUTTON_NONE, + /*is_tap=*/false); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture); + ASSERT_THAT(args.front(), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), WithButtonState(0), + WithCoords(0, 0), WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT)))); } TEST_F(GestureConverterTestWithChoreographer, DragWithButton) { @@ -1425,53 +1666,48 @@ TEST_F(GestureConverterTestWithChoreographer, DragWithButton) { Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture); - ASSERT_EQ(2u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); // Move Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); - ASSERT_EQ(1u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, 0), - WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, 0), + WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); // Release the button Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, upGesture); - ASSERT_EQ(3u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0), - WithCoords(0, 0), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0), - WithCoords(0, 0), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0), - WithCoords(0, 0), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, upGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY))), + VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_UP)), + VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithButtonState(0), WithCoords(0, 0), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTestWithChoreographer, Scroll) { @@ -1481,47 +1717,54 @@ TEST_F(GestureConverterTestWithChoreographer, Scroll) { converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); - std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture); - ASSERT_EQ(2u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0), - WithGestureScrollDistance(0, 0, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), WithDownTime(downTime), - WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, -10), - WithGestureScrollDistance(0, 10, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), - WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(downTime, READ_TIME, ARBITRARY_TIME, startGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(0, 0), + WithGestureScrollDistance(0, 0, EPSILON), + WithDownTime(downTime))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithCoords(0, -10), + WithGestureScrollDistance(0, 10, EPSILON))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), + WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, -15), - WithGestureScrollDistance(0, 5, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), - WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, -15), + WithGestureScrollDistance(0, 5, EPSILON), + WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), + WithToolType(ToolType::FINGER), + WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, GESTURES_FLING_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0 - 15), - WithGestureScrollDistance(0, 0, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), - WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(0, -15), + WithGestureScrollDistance(0, 0, EPSILON), + WithMotionClassification( + MotionClassification::TWO_FINGER_SWIPE), + WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(0, 0), + WithMotionClassification(MotionClassification::NONE))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTestWithChoreographer, Scroll_Rotated) { @@ -1532,40 +1775,51 @@ TEST_F(GestureConverterTestWithChoreographer, Scroll_Rotated) { converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); - std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture); - ASSERT_EQ(2u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0), - WithGestureScrollDistance(0, 0, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), WithDownTime(downTime), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(-10, 0), - WithGestureScrollDistance(0, 10, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(downTime, READ_TIME, ARBITRARY_TIME, startGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(0, 0), + WithGestureScrollDistance(0, 0, EPSILON), + WithDownTime(downTime))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithCoords(-10, 0), + WithGestureScrollDistance(0, 10, EPSILON))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(-15, 0), - WithGestureScrollDistance(0, 5, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(-15, 0), + WithGestureScrollDistance(0, 5, EPSILON), + WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, GESTURES_FLING_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(-15, 0), - WithGestureScrollDistance(0, 0, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(-15, 0), + WithGestureScrollDistance(0, 0, EPSILON), + WithMotionClassification( + MotionClassification::TWO_FINGER_SWIPE))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(0, 0), + WithMotionClassification(MotionClassification::NONE))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsClassificationAfterGesture) { @@ -1574,21 +1828,22 @@ TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsClassificationAfterGe converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, GESTURES_FLING_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); - ASSERT_EQ(1u, args.size()); - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionClassification(MotionClassification::NONE), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionClassification(MotionClassification::NONE), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsScrollDistanceAfterGesture) { @@ -1597,20 +1852,21 @@ TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsScrollDistanceAfterGe converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, GESTURES_FLING_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we // need to use another gesture type, like pinch. Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, pinchGesture); ASSERT_FALSE(args.empty()); EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGestureScrollDistance(0, 0, EPSILON)); } @@ -1622,17 +1878,18 @@ TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_ClearsClassificat Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0, /*dy=*/0); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/-5, /*dy=*/10); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); - ASSERT_EQ(1u, args.size()); - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - WithMotionClassification(MotionClassification::NONE)); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionClassification(MotionClassification::NONE)))); } TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) { @@ -1642,16 +1899,17 @@ TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_ClearsGestureAxes Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/5, /*dy=*/5); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture); // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we // need to use another gesture type, like pinch. Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, pinchGesture); ASSERT_FALSE(args.empty()); EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), AllOf(WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(0))); @@ -1668,48 +1926,42 @@ TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Vertical) { Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, /* dy= */ 10); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); ASSERT_EQ(4u, args.size()); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithGestureSwipeFingerCount(3), WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); // Three fake fingers should be created. We don't actually care where they are, so long as they // move appropriately. NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON), - WithGestureSwipeFingerCount(3), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(1u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithPointerCount(1u))); PointerCoords finger0Start = arg.pointerCoords[0]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(2u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u))); PointerCoords finger1Start = arg.pointerCoords[1]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u))); PointerCoords finger2Start = arg.pointerCoords[2]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithGestureOffset(0, -0.01, EPSILON), WithGestureSwipeFingerCount(3), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u))); EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX()); EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX()); EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX()); @@ -1719,7 +1971,7 @@ TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Vertical) { Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, /* dy= */ 5); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); ASSERT_EQ(1u, args.size()); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, @@ -1736,30 +1988,40 @@ TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Vertical) { EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 15); Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture); - ASSERT_EQ(3u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(2u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON), - WithGestureSwipeFingerCount(3), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(1u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithGestureSwipeFingerCount(3), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(3u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithGestureSwipeFingerCount(3), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(2u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithGestureOffset(0, 0, EPSILON), + WithGestureSwipeFingerCount(3), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(1u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(0, 0), + WithMotionClassification(MotionClassification::NONE))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Rotated) { @@ -1770,39 +2032,38 @@ TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Rotated) { Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, /* dy= */ 10); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); ASSERT_EQ(4u, args.size()); + ASSERT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithDisplayId(ADISPLAY_ID_DEFAULT)))); // Three fake fingers should be created. We don't actually care where they are, so long as they // move appropriately. NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON), - WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithPointerCount(1u))); PointerCoords finger0Start = arg.pointerCoords[0]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u))); PointerCoords finger1Start = arg.pointerCoords[1]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u))); PointerCoords finger2Start = arg.pointerCoords[2]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u))); EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 10); EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 10); EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 10); @@ -1812,7 +2073,7 @@ TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Rotated) { Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, /* dy= */ 5); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); ASSERT_EQ(1u, args.size()); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, @@ -1827,23 +2088,24 @@ TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Rotated) { EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY()); Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture); - ASSERT_EQ(3u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON), - WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithGestureOffset(0, 0, EPSILON), WithPointerCount(1u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER))))); + ASSERT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithDisplayId(ADISPLAY_ID_DEFAULT)))); } TEST_F(GestureConverterTestWithChoreographer, FourFingerSwipe_Horizontal) { @@ -1853,58 +2115,49 @@ TEST_F(GestureConverterTestWithChoreographer, FourFingerSwipe_Horizontal) { Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 10, /* dy= */ 0); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); ASSERT_EQ(5u, args.size()); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithGestureSwipeFingerCount(4), WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); // Four fake fingers should be created. We don't actually care where they are, so long as they // move appropriately. NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON), - WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(1u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithPointerCount(1u))); PointerCoords finger0Start = arg.pointerCoords[0]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(2u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u))); PointerCoords finger1Start = arg.pointerCoords[1]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u))); PointerCoords finger2Start = arg.pointerCoords[2]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(4u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0, 0, EPSILON), WithPointerCount(4u))); PointerCoords finger3Start = arg.pointerCoords[3]; args.pop_front(); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithGestureOffset(0.01, 0, EPSILON), WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(4u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithGestureOffset(0.01, 0, EPSILON), WithPointerCount(4u))); EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 10); EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 10); EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 10); @@ -1916,7 +2169,7 @@ TEST_F(GestureConverterTestWithChoreographer, FourFingerSwipe_Horizontal) { Gesture continueGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 5, /* dy= */ 0); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); ASSERT_EQ(1u, args.size()); arg = std::get<NotifyMotionArgs>(args.front()); ASSERT_THAT(arg, @@ -1935,38 +2188,49 @@ TEST_F(GestureConverterTestWithChoreographer, FourFingerSwipe_Horizontal) { EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY()); Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture); - ASSERT_EQ(4u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(4u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(2u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON), - WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(1u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithGestureSwipeFingerCount(4), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(4u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithGestureSwipeFingerCount(4), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(3u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithGestureSwipeFingerCount(4), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(2u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithGestureOffset(0, 0, EPSILON), + WithGestureSwipeFingerCount(4), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(1u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(0, 0), + WithMotionClassification(MotionClassification::NONE))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTestWithChoreographer, Pinch_Inwards) { @@ -1976,50 +2240,59 @@ TEST_F(GestureConverterTestWithChoreographer, Pinch_Inwards) { Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, GESTURES_ZOOM_START); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); - ASSERT_EQ(2u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), WithCoords(-100, 0), - WithPointerCount(1u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCoords(1, 100, 0), - WithPointerCount(2u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(-100, 0), WithPointerCount(1u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithPointerCoords(1, 100, 0), WithPointerCount(2u))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 0.8, GESTURES_ZOOM_UPDATE); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture); - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(0.8f, EPSILON), WithPointerCoords(0, -80, 0), - WithPointerCoords(1, 80, 0), WithPointerCount(2u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(0.8f, EPSILON), + WithPointerCoords(0, -80, 0), WithPointerCoords(1, 80, 0), + WithPointerCount(2u), WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, GESTURES_ZOOM_END); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture); - ASSERT_EQ(2u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithPointerCount(2u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithPointerCount(1u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(0, 0), + WithMotionClassification(MotionClassification::NONE))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTestWithChoreographer, Pinch_Outwards) { @@ -2029,50 +2302,59 @@ TEST_F(GestureConverterTestWithChoreographer, Pinch_Outwards) { Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, GESTURES_ZOOM_START); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); - ASSERT_EQ(2u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), WithCoords(-100, 0), - WithPointerCount(1u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCoords(1, 100, 0), - WithPointerCount(2u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(-100, 0), WithPointerCount(1u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithPointerCoords(1, 100, 0), WithPointerCount(2u))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1.1, GESTURES_ZOOM_UPDATE); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture); - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.1f, EPSILON), WithPointerCoords(0, -110, 0), - WithPointerCoords(1, 110, 0), WithPointerCount(2u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.1f, EPSILON), + WithPointerCoords(0, -110, 0), WithPointerCoords(1, 110, 0), + WithPointerCount(2u), WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, GESTURES_ZOOM_END); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture); - ASSERT_EQ(2u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithPointerCount(2u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithPointerCount(1u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(0, 0), + WithMotionClassification(MotionClassification::NONE))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsClassificationAfterGesture) { @@ -2082,21 +2364,22 @@ TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsClassificationAfterGes Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_START); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1.2, GESTURES_ZOOM_UPDATE); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture); Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_END); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); - ASSERT_EQ(1u, args.size()); - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - WithMotionClassification(MotionClassification::NONE)); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionClassification(MotionClassification::NONE)))); } TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsScaleFactorAfterGesture) { @@ -2106,21 +2389,22 @@ TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsScaleFactorAfterGestur Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_START); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1.2, GESTURES_ZOOM_UPDATE); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture); Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_END); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture); // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we // need to use another gesture type, like scroll. Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/1, /*dy=*/0); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, scrollGesture); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, scrollGesture); ASSERT_FALSE(args.empty()); EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGesturePinchScaleFactor(0, EPSILON)); } @@ -2133,27 +2417,28 @@ TEST_F(GestureConverterTestWithChoreographer, ResetWithButtonPressed) { Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*down=*/GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT, /*up=*/GESTURES_BUTTON_NONE, /*is_tap=*/false); - (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture); + (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture); std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME); - ASSERT_EQ(3u, args.size()); - - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(0, 0), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0), - WithCoords(0, 0), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0), - WithCoords(0, 0), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), + WithButtonState(0))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithButtonState(0))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithButtonState(0))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTestWithChoreographer, ResetDuringScroll) { @@ -2162,17 +2447,24 @@ TEST_F(GestureConverterTestWithChoreographer, ResetDuringScroll) { converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); - (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME); - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, -10), - WithGestureScrollDistance(0, 0, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), - WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(0, -10), + WithGestureScrollDistance(0, 0, EPSILON), + WithMotionClassification( + MotionClassification::TWO_FINGER_SWIPE), + WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(0, 0), + WithMotionClassification(MotionClassification::NONE))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTestWithChoreographer, ResetDuringThreeFingerSwipe) { @@ -2182,31 +2474,38 @@ TEST_F(GestureConverterTestWithChoreographer, ResetDuringThreeFingerSwipe) { Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0, /*dy=*/10); - (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME); - ASSERT_EQ(3u, args.size()); - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(2u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(1u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(3u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(2u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification( + MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(1u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithMotionClassification(MotionClassification::NONE))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTestWithChoreographer, ResetDuringPinch) { @@ -2216,22 +2515,29 @@ TEST_F(GestureConverterTestWithChoreographer, ResetDuringPinch) { Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_START); - (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME); - ASSERT_EQ(2u, args.size()); - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithPointerCount(2u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithPointerCount(1u))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(0, 0), + WithMotionClassification(MotionClassification::NONE))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTestWithChoreographer, FlingTapDown) { @@ -2241,10 +2547,11 @@ TEST_F(GestureConverterTestWithChoreographer, FlingTapDown) { Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapDownGesture); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapDownGesture); ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithCoords(0, 0), WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); } @@ -2257,50 +2564,43 @@ TEST_F(GestureConverterTestWithChoreographer, Tap) { Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0, /* vy= */ 0, GESTURES_FLING_TAP_DOWN); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); - - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0), - WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0), - WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); + // We don't need to check args here, since it's covered by the FlingTapDown test. Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture); - - ASSERT_EQ(5u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0), - WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0), - WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0), - WithCoords(0, 0), WithRelativeMotion(0.f, 0.f), - WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0), - WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0), - WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0), - WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapGesture); + + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithButtonState(0), WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(0), WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithButtonState(0), WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithButtonState(0), WithPressure(0.0f))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0), + WithRelativeMotion(0.f, 0.f), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTestWithChoreographer, Click) { @@ -2311,60 +2611,62 @@ TEST_F(GestureConverterTestWithChoreographer, Click) { Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0, /* vy= */ 0, GESTURES_FLING_TAP_DOWN); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); - - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0), - WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0), - WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); + // We don't need to check args here, since it's covered by the FlingTapDown test. Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture); - - ASSERT_EQ(2u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0), - WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0), - WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture); + + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithButtonState(0), WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithPressure(1.0f))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0), + WithRelativeMotion(0.f, 0.f), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture); - - ASSERT_EQ(3u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0), - WithCoords(0, 0), WithRelativeMotion(0.f, 0.f), - WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0), - WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0), - WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0), - WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture); + + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithPressure(0.0f))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithButtonState(0), WithCoords(0, 0), + WithRelativeMotion(0.f, 0.f), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); } TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, TapWithTapToClickDisabled, - REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) { + REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION), + REQUIRES_FLAGS_DISABLED(TOUCHPAD_PALM_REJECTION_V2)) { + nsecs_t currentTime = ARBITRARY_GESTURE_TIME; + // Tap should be ignored when disabled mReader->getContext()->setPreventingTouchpadTaps(true); @@ -2372,27 +2674,106 @@ TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, TapWithTapToClickDisabl GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); - Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0, + Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0, /* vy= */ 0, GESTURES_FLING_TAP_DOWN); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); + std::list<NotifyArgs> args = + converter.handleGesture(currentTime, currentTime, currentTime, flingGesture); + // We don't need to check args here, since it's covered by the FlingTapDown test. - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0), - WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0), - WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); + Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime, + /* down= */ GESTURES_BUTTON_LEFT, + /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true); + args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture); - Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, + // no events should be generated + ASSERT_EQ(0u, args.size()); + + // Future taps should be re-enabled + ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps()); +} + +TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, TapWithTapToClickDisabledWithDelay, + REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) { + nsecs_t currentTime = ARBITRARY_GESTURE_TIME; + + // Tap should be ignored when disabled + mReader->getContext()->setPreventingTouchpadTaps(true); + + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + converter.setDisplayId(ADISPLAY_ID_DEFAULT); + + Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0, + /* vy= */ 0, GESTURES_FLING_TAP_DOWN); + std::list<NotifyArgs> args = + converter.handleGesture(currentTime, currentTime, currentTime, flingGesture); + // We don't need to check args here, since it's covered by the FlingTapDown test. + + Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime, /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture); + args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture); // no events should be generated ASSERT_EQ(0u, args.size()); // Future taps should be re-enabled ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps()); + + // taps before the threshold should still be ignored + currentTime += TAP_ENABLE_DELAY_NANOS.count(); + flingGesture = Gesture(kGestureFling, currentTime, currentTime, /* vx= */ 0, + /* vy= */ 0, GESTURES_FLING_TAP_DOWN); + args = converter.handleGesture(currentTime, currentTime, currentTime, flingGesture); + + ASSERT_EQ(1u, args.size()); + ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0))); + + tapGesture = Gesture(kGestureButtonsChange, currentTime, currentTime, + /* down= */ GESTURES_BUTTON_LEFT, + /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true); + args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture); + + // no events should be generated + ASSERT_EQ(0u, args.size()); + + // taps after the threshold should be recognised + currentTime += 1; + flingGesture = Gesture(kGestureFling, currentTime, currentTime, /* vx= */ 0, + /* vy= */ 0, GESTURES_FLING_TAP_DOWN); + args = converter.handleGesture(currentTime, currentTime, currentTime, flingGesture); + + ASSERT_EQ(1u, args.size()); + ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0))); + + tapGesture = Gesture(kGestureButtonsChange, currentTime, currentTime, + /* down= */ GESTURES_BUTTON_LEFT, + /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true); + args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithButtonState(0))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(0))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithButtonState(0))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithButtonState(0))))); + ASSERT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithRelativeMotion(0.f, 0.f)))); } TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, ClickWithTapToClickDisabled, @@ -2406,56 +2787,58 @@ TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, ClickWithTapToClickDisa Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0, /* vy= */ 0, GESTURES_FLING_TAP_DOWN); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); - - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0), - WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0), - WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); + // We don't need to check args here, since it's covered by the FlingTapDown test. Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture); - ASSERT_EQ(2u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0), - WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0), - WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture); + + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithButtonState(0), WithPressure(0.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithPressure(1.0f))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithPressure(1.0f))))); + ASSERT_THAT(args, + Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0), + WithRelativeMotion(0.f, 0.f), + WithToolType(ToolType::FINGER), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture); - - ASSERT_EQ(3u, args.size()); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0), - WithCoords(0, 0), WithRelativeMotion(0.f, 0.f), - WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0), - WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); - args.pop_front(); - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0), - WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0), - WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture); + + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(0), WithCoords(0, 0), + WithRelativeMotion(0.f, 0.f), + WithToolType(ToolType::FINGER), WithButtonState(0), + WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(0, 0), WithRelativeMotion(0.f, 0.f), + WithToolType(ToolType::FINGER), WithButtonState(0), + WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))), + VariantWith<NotifyMotionArgs>( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithCoords(0, 0), WithRelativeMotion(0, 0), + WithToolType(ToolType::FINGER), WithButtonState(0), + WithPressure(0.0f), + WithDisplayId(ADISPLAY_ID_DEFAULT))))); // Future taps should be re-enabled ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps()); @@ -2471,16 +2854,55 @@ TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, MoveEnablesTapToClick, converter.setDisplayId(ADISPLAY_ID_DEFAULT); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); - ASSERT_EQ(1u, args.size()); - - ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0), - WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER), - WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); + std::list<NotifyArgs> args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); + // We don't need to check args here, since it's covered by the Move test. // Future taps should be re-enabled ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps()); } +TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, KeypressCancelsHoverMove, + REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) { + const nsecs_t gestureStartTime = 1000; + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + converter.setDisplayId(ADISPLAY_ID_DEFAULT); + + // Start a move gesture at gestureStartTime + Gesture moveGesture(kGestureMove, gestureStartTime, gestureStartTime, -5, 10); + std::list<NotifyArgs> args = + converter.handleGesture(gestureStartTime, READ_TIME, gestureStartTime, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)), + VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)))); + + // Key presses with IME connection should cancel ongoing move gesture + nsecs_t currentTime = gestureStartTime + 100; + mFakePolicy->setIsInputMethodConnectionActive(true); + mReader->getContext()->setLastKeyDownTimestamp(currentTime); + moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10); + args = converter.handleGesture(currentTime, READ_TIME, gestureStartTime, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)))); + + // any updates in existing move gesture should be ignored + moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10); + args = converter.handleGesture(currentTime, READ_TIME, gestureStartTime, moveGesture); + ASSERT_EQ(0u, args.size()); + + // New gesture should not be affected + currentTime += 100; + moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10); + args = converter.handleGesture(currentTime, READ_TIME, currentTime, moveGesture); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)), + VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)))); +} + } // namespace android diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 5002391f61..cb7791ae2c 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -17,6 +17,7 @@ #include "../dispatcher/InputDispatcher.h" #include "../BlockingQueue.h" #include "FakeApplicationHandle.h" +#include "FakeInputTracingBackend.h" #include "TestEventMatchers.h" #include <NotifyArgsBuilders.h> @@ -154,7 +155,21 @@ static KeyEvent getTestKeyEvent() { class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface { struct AnrResult { sp<IBinder> token{}; - gui::Pid pid{gui::Pid::INVALID}; + std::optional<gui::Pid> pid{}; + }; + /* Stores data about a user-activity-poke event from the dispatcher. */ + struct UserActivityPokeEvent { + nsecs_t eventTime; + int32_t eventType; + int32_t displayId; + + bool operator==(const UserActivityPokeEvent& rhs) const = default; + + friend std::ostream& operator<<(std::ostream& os, const UserActivityPokeEvent& ev) { + os << "UserActivityPokeEvent[time=" << ev.eventTime << ", eventType=" << ev.eventType + << ", displayId=" << ev.displayId << "]"; + return os; + } }; public: @@ -245,7 +260,7 @@ public: void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout, const sp<IBinder>& expectedToken, - gui::Pid expectedPid) { + std::optional<gui::Pid> expectedPid) { std::unique_lock lock(mLock); android::base::ScopedLockAssertion assumeLocked(mLock); AnrResult result; @@ -265,7 +280,7 @@ public: } void assertNotifyWindowResponsiveWasCalled(const sp<IBinder>& expectedToken, - gui::Pid expectedPid) { + std::optional<gui::Pid> expectedPid) { std::unique_lock lock(mLock); android::base::ScopedLockAssertion assumeLocked(mLock); AnrResult result; @@ -349,16 +364,40 @@ public: mInterceptKeyTimeout = timeout; } + std::chrono::nanoseconds getKeyWaitingForEventsTimeout() override { return 500ms; } + void setStaleEventTimeout(std::chrono::nanoseconds timeout) { mStaleEventTimeout = timeout; } - void assertUserActivityPoked() { - std::scoped_lock lock(mLock); - ASSERT_TRUE(mPokedUserActivity) << "Expected user activity to have been poked"; + void assertUserActivityNotPoked() { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + + std::optional<UserActivityPokeEvent> pokeEvent = + getItemFromStorageLockedInterruptible(500ms, mUserActivityPokeEvents, lock, + mNotifyUserActivity); + + ASSERT_FALSE(pokeEvent) << "Expected user activity not to have been poked"; } - void assertUserActivityNotPoked() { - std::scoped_lock lock(mLock); - ASSERT_FALSE(mPokedUserActivity) << "Expected user activity not to have been poked"; + /** + * Asserts that a user activity poke has happened. The earliest recorded poke event will be + * cleared after this call. + * + * If an expected UserActivityPokeEvent is provided, asserts that the given event is the + * earliest recorded poke event. + */ + void assertUserActivityPoked(std::optional<UserActivityPokeEvent> expectedPokeEvent = {}) { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + + std::optional<UserActivityPokeEvent> pokeEvent = + getItemFromStorageLockedInterruptible(500ms, mUserActivityPokeEvents, lock, + mNotifyUserActivity); + ASSERT_TRUE(pokeEvent) << "Expected a user poke event"; + + if (expectedPokeEvent) { + ASSERT_EQ(expectedPokeEvent, *pokeEvent); + } } void assertNotifyDeviceInteractionWasCalled(int32_t deviceId, std::set<gui::Uid> uids) { @@ -414,7 +453,9 @@ private: sp<IBinder> mDropTargetWindowToken GUARDED_BY(mLock); bool mNotifyDropWindowWasCalled GUARDED_BY(mLock) = false; - bool mPokedUserActivity GUARDED_BY(mLock) = false; + + std::condition_variable mNotifyUserActivity; + std::queue<UserActivityPokeEvent> mUserActivityPokeEvents; std::chrono::milliseconds mInterceptKeyTimeout = 0ms; @@ -483,16 +524,14 @@ private: void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<gui::Pid> pid, const std::string&) override { std::scoped_lock lock(mLock); - ASSERT_TRUE(pid.has_value()); - mAnrWindows.push({connectionToken, *pid}); + mAnrWindows.push({connectionToken, pid}); mNotifyAnr.notify_all(); } void notifyWindowResponsive(const sp<IBinder>& connectionToken, std::optional<gui::Pid> pid) override { std::scoped_lock lock(mLock); - ASSERT_TRUE(pid.has_value()); - mResponsiveWindows.push({connectionToken, *pid}); + mResponsiveWindows.push({connectionToken, pid}); mNotifyAnr.notify_all(); } @@ -549,7 +588,7 @@ private: } } - void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {} + void interceptMotionBeforeQueueing(int32_t, uint32_t, int32_t, nsecs_t, uint32_t&) override {} nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override { nsecs_t delay = std::chrono::nanoseconds(mInterceptKeyTimeout).count(); @@ -572,12 +611,14 @@ private: /** We simply reconstruct NotifySwitchArgs in policy because InputDispatcher is * essentially a passthrough for notifySwitch. */ - mLastNotifySwitch = NotifySwitchArgs(/*id=*/1, when, policyFlags, switchValues, switchMask); + mLastNotifySwitch = + NotifySwitchArgs(InputEvent::nextId(), when, policyFlags, switchValues, switchMask); } - void pokeUserActivity(nsecs_t, int32_t, int32_t) override { + void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override { std::scoped_lock lock(mLock); - mPokedUserActivity = true; + mNotifyUserActivity.notify_all(); + mUserActivityPokeEvents.push({eventTime, eventType, displayId}); } bool isStaleEvent(nsecs_t currentTime, nsecs_t eventTime) override { @@ -618,14 +659,22 @@ private: // --- InputDispatcherTest --- +// The trace is a global variable for now, to avoid having to pass it into all of the +// FakeWindowHandles created throughout the tests. +// TODO(b/210460522): Update the tests to avoid the need to have the trace be a global variable. +static std::shared_ptr<VerifyingTrace> gVerifyingTrace = std::make_shared<VerifyingTrace>(); + class InputDispatcherTest : public testing::Test { protected: std::unique_ptr<FakeInputDispatcherPolicy> mFakePolicy; std::unique_ptr<InputDispatcher> mDispatcher; void SetUp() override { + gVerifyingTrace->reset(); mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>(); - mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy); + mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy, + std::make_unique<FakeInputTracingBackend>( + gVerifyingTrace)); mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false); // Start InputDispatcher thread @@ -633,6 +682,7 @@ protected: } void TearDown() override { + ASSERT_NO_FATAL_FAILURE(gVerifyingTrace->verifyExpectedEventsTraced()); ASSERT_EQ(OK, mDispatcher->stop()); mFakePolicy.reset(); mDispatcher.reset(); @@ -836,7 +886,8 @@ TEST_F(InputDispatcherTest, NotifyConfigurationChanged_CallsPolicy) { } TEST_F(InputDispatcherTest, NotifySwitch_CallsPolicy) { - NotifySwitchArgs args(/*id=*/10, /*eventTime=*/20, /*policyFlags=*/0, /*switchValues=*/1, + NotifySwitchArgs args(InputEvent::nextId(), /*eventTime=*/20, /*policyFlags=*/0, + /*switchValues=*/1, /*switchMask=*/2); mDispatcher->notifySwitch(args); @@ -859,30 +910,31 @@ public: explicit FakeInputReceiver(std::unique_ptr<InputChannel> clientChannel, const std::string name) : mConsumer(std::move(clientChannel)), mName(name) {} - InputEvent* consume(std::chrono::milliseconds timeout, bool handled = false) { - InputEvent* event; - std::optional<uint32_t> consumeSeq = receiveEvent(timeout, &event); + std::unique_ptr<InputEvent> consume(std::chrono::milliseconds timeout, bool handled = false) { + auto [consumeSeq, event] = receiveEvent(timeout); if (!consumeSeq) { return nullptr; } finishEvent(*consumeSeq, handled); - return event; + return std::move(event); } /** * Receive an event without acknowledging it. * Return the sequence number that could later be used to send finished signal. */ - std::optional<uint32_t> receiveEvent(std::chrono::milliseconds timeout, - InputEvent** outEvent = nullptr) { + std::pair<std::optional<uint32_t>, std::unique_ptr<InputEvent>> receiveEvent( + std::chrono::milliseconds timeout) { uint32_t consumeSeq; - InputEvent* event; + std::unique_ptr<InputEvent> event; std::chrono::time_point start = std::chrono::steady_clock::now(); status_t status = WOULD_BLOCK; while (status == WOULD_BLOCK) { + InputEvent* rawEventPtr = nullptr; status = mConsumer.consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, - &event); + &rawEventPtr); + event = std::unique_ptr<InputEvent>(rawEventPtr); std::chrono::duration elapsed = std::chrono::steady_clock::now() - start; if (elapsed > timeout) { break; @@ -891,21 +943,17 @@ public: if (status == WOULD_BLOCK) { // Just means there's no event available. - return std::nullopt; + return std::make_pair(std::nullopt, nullptr); } if (status != OK) { ADD_FAILURE() << mName.c_str() << ": consumer consume should return OK."; - return std::nullopt; + return std::make_pair(std::nullopt, nullptr); } if (event == nullptr) { ADD_FAILURE() << "Consumed correctly, but received NULL event from consumer"; - return std::nullopt; } - if (outEvent != nullptr) { - *outEvent = event; - } - return consumeSeq; + return std::make_pair(consumeSeq, std::move(event)); } /** @@ -924,7 +972,7 @@ public: void consumeEvent(InputEventType expectedEventType, int32_t expectedAction, std::optional<int32_t> expectedDisplayId, std::optional<int32_t> expectedFlags) { - InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED); + std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED); ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; @@ -968,8 +1016,8 @@ public: } } - MotionEvent* consumeMotion() { - InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED); + std::unique_ptr<MotionEvent> consumeMotion() { + std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED); if (event == nullptr) { ADD_FAILURE() << mName << ": expected a MotionEvent, but didn't get one."; @@ -980,17 +1028,17 @@ public: ADD_FAILURE() << mName << " expected a MotionEvent, got " << *event; return nullptr; } - return static_cast<MotionEvent*>(event); + return std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event.release())); } void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) { - MotionEvent* motionEvent = consumeMotion(); + std::unique_ptr<MotionEvent> motionEvent = consumeMotion(); ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event, but expected " << matcher; ASSERT_THAT(*motionEvent, matcher); } void consumeFocusEvent(bool hasFocus, bool inTouchMode) { - InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED); + std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED); ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; ASSERT_EQ(InputEventType::FOCUS, event->getType()) @@ -999,12 +1047,12 @@ public: ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) << mName.c_str() << ": event displayId should always be NONE."; - FocusEvent* focusEvent = static_cast<FocusEvent*>(event); - EXPECT_EQ(hasFocus, focusEvent->getHasFocus()); + FocusEvent& focusEvent = static_cast<FocusEvent&>(*event); + EXPECT_EQ(hasFocus, focusEvent.getHasFocus()); } void consumeCaptureEvent(bool hasCapture) { - const InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED); + std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED); ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; ASSERT_EQ(InputEventType::CAPTURE, event->getType()) @@ -1018,7 +1066,7 @@ public: } void consumeDragEvent(bool isExiting, float x, float y) { - const InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED); + std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED); ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; ASSERT_EQ(InputEventType::DRAG, event->getType()) << "Instead of DragEvent, got " << *event; @@ -1033,7 +1081,7 @@ public: } void consumeTouchModeEvent(bool inTouchMode) { - const InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED); + std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED); ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; ASSERT_EQ(InputEventType::TOUCH_MODE, event->getType()) @@ -1045,8 +1093,8 @@ public: EXPECT_EQ(inTouchMode, touchModeEvent.isInTouchMode()); } - void assertNoEvents() { - InputEvent* event = consume(CONSUME_TIMEOUT_NO_EVENT_EXPECTED); + void assertNoEvents(std::chrono::milliseconds timeout) { + std::unique_ptr<InputEvent> event = consume(timeout); if (event == nullptr) { return; } @@ -1075,11 +1123,11 @@ public: sp<IBinder> getToken() { return mConsumer.getChannel()->getConnectionToken(); } - int getChannelFd() { return mConsumer.getChannel()->getFd().get(); } + int getChannelFd() { return mConsumer.getChannel()->getFd(); } private: InputConsumer mConsumer; - PreallocatedInputEventFactory mEventFactory; + DynamicInputEventFactory mEventFactory; std::string mName; }; @@ -1091,9 +1139,10 @@ public: FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle, const std::unique_ptr<InputDispatcher>& dispatcher, const std::string name, - int32_t displayId, std::optional<sp<IBinder>> token = std::nullopt) + int32_t displayId, bool createInputChannel = true) : mName(name) { - if (token == std::nullopt) { + sp<IBinder> token; + if (createInputChannel) { base::Result<std::unique_ptr<InputChannel>> channel = dispatcher->createInputChannel(name); token = (*channel)->getConnectionToken(); @@ -1103,7 +1152,7 @@ public: inputApplicationHandle->updateInfo(); mInfo.applicationInfo = *inputApplicationHandle->getInfo(); - mInfo.token = *token; + mInfo.token = token; mInfo.id = sId++; mInfo.name = name; mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT; @@ -1182,6 +1231,11 @@ public: mInfo.setInputConfig(WindowInfo::InputConfig::DISABLE_USER_ACTIVITY, disableUserActivity); } + void setGlobalStylusBlocksTouch(bool shouldGlobalStylusBlockTouch) { + mInfo.setInputConfig(WindowInfo::InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH, + shouldGlobalStylusBlockTouch); + } + void setAlpha(float alpha) { mInfo.alpha = alpha; } void setTouchOcclusionMode(TouchOcclusionMode mode) { mInfo.touchOcclusionMode = mode; } @@ -1221,31 +1275,33 @@ public: void setWindowOffset(float offsetX, float offsetY) { mInfo.transform.set(offsetX, offsetY); } - KeyEvent* consumeKey(bool handled = true) { - InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED, handled); + std::unique_ptr<KeyEvent> consumeKey(bool handled = true) { + std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED, handled); if (event == nullptr) { - ADD_FAILURE() << "Consume failed : no event"; + ADD_FAILURE() << "No event"; return nullptr; } if (event->getType() != InputEventType::KEY) { - ADD_FAILURE() << "Instead of key event, got " << *event; + ADD_FAILURE() << "Instead of key event, got " << event; return nullptr; } - return static_cast<KeyEvent*>(event); + return std::unique_ptr<KeyEvent>(static_cast<KeyEvent*>(event.release())); } void consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) { - KeyEvent* keyEvent = consumeKey(); - ASSERT_NE(nullptr, keyEvent) << "Did not get a key event, but expected " << matcher; + std::unique_ptr<KeyEvent> keyEvent = consumeKey(); + ASSERT_NE(nullptr, keyEvent); ASSERT_THAT(*keyEvent, matcher); } void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) { - consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId, expectedFlags); + consumeKeyEvent(AllOf(WithKeyAction(ACTION_DOWN), WithDisplayId(expectedDisplayId), + WithFlags(expectedFlags))); } void consumeKeyUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) { - consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, expectedDisplayId, expectedFlags); + consumeKeyEvent(AllOf(WithKeyAction(ACTION_UP), WithDisplayId(expectedDisplayId), + WithFlags(expectedFlags))); } void consumeMotionCancel(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, @@ -1267,44 +1323,46 @@ public: void consumeAnyMotionDown(std::optional<int32_t> expectedDisplayId = std::nullopt, std::optional<int32_t> expectedFlags = std::nullopt) { - consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId, - expectedFlags); + consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), + testing::Conditional(expectedDisplayId.has_value(), + WithDisplayId(*expectedDisplayId), testing::_), + testing::Conditional(expectedFlags.has_value(), WithFlags(*expectedFlags), + testing::_))); } void consumeMotionPointerDown(int32_t pointerIdx, int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { - int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN | + const int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); - consumeEvent(InputEventType::MOTION, action, expectedDisplayId, expectedFlags); + consumeMotionEvent(AllOf(WithMotionAction(action), WithDisplayId(expectedDisplayId), + WithFlags(expectedFlags))); } void consumeMotionPointerUp(int32_t pointerIdx, int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { - int32_t action = AMOTION_EVENT_ACTION_POINTER_UP | + const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); - consumeEvent(InputEventType::MOTION, action, expectedDisplayId, expectedFlags); + consumeMotionEvent(AllOf(WithMotionAction(action), WithDisplayId(expectedDisplayId), + WithFlags(expectedFlags))); } void consumeMotionUp(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { - consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId, - expectedFlags); + consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDisplayId(expectedDisplayId), + WithFlags(expectedFlags))); } void consumeMotionOutside(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { - consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE, expectedDisplayId, - expectedFlags); + consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_OUTSIDE), + WithDisplayId(expectedDisplayId), WithFlags(expectedFlags))); } - void consumeMotionOutsideWithZeroedCoords(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, - int32_t expectedFlags = 0) { - MotionEvent* motionEvent = consumeMotion(); - ASSERT_NE(nullptr, motionEvent); - EXPECT_EQ(AMOTION_EVENT_ACTION_OUTSIDE, motionEvent->getActionMasked()); - EXPECT_EQ(0.f, motionEvent->getRawPointerCoords(0)->getX()); - EXPECT_EQ(0.f, motionEvent->getRawPointerCoords(0)->getY()); + void consumeMotionOutsideWithZeroedCoords() { + consumeMotionEvent( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_OUTSIDE), WithRawCoords(0, 0))); } void consumeFocusEvent(bool hasFocus, bool inTouchMode = true) { @@ -1319,21 +1377,21 @@ public: mInputReceiver->consumeCaptureEvent(hasCapture); } - const MotionEvent& consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) { - MotionEvent* motionEvent = consumeMotion(); - if (nullptr == motionEvent) { - LOG(FATAL) << "Did not get a motion event, but expected " << matcher; + std::unique_ptr<MotionEvent> consumeMotionEvent( + const ::testing::Matcher<MotionEvent>& matcher = testing::_) { + std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED); + if (event == nullptr) { + ADD_FAILURE() << "No event"; + return nullptr; } + if (event->getType() != InputEventType::MOTION) { + ADD_FAILURE() << "Instead of motion event, got " << *event; + return nullptr; + } + std::unique_ptr<MotionEvent> motionEvent = + std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event.release())); EXPECT_THAT(*motionEvent, matcher); - return *motionEvent; - } - - void consumeEvent(InputEventType expectedEventType, int32_t expectedAction, - std::optional<int32_t> expectedDisplayId, - std::optional<int32_t> expectedFlags) { - ASSERT_NE(mInputReceiver, nullptr) << "Invalid consume event on window with no receiver"; - mInputReceiver->consumeEvent(expectedEventType, expectedAction, expectedDisplayId, - expectedFlags); + return motionEvent; } void consumeDragEvent(bool isExiting, float x, float y) { @@ -1346,12 +1404,8 @@ public: mInputReceiver->consumeTouchModeEvent(inTouchMode); } - std::optional<uint32_t> receiveEvent(InputEvent** outEvent = nullptr) { - if (mInputReceiver == nullptr) { - ADD_FAILURE() << "Invalid receive event on window with no receiver"; - return std::nullopt; - } - return mInputReceiver->receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED, outEvent); + std::pair<std::optional<uint32_t>, std::unique_ptr<InputEvent>> receiveEvent() { + return receive(); } void finishEvent(uint32_t sequenceNum) { @@ -1364,34 +1418,14 @@ public: mInputReceiver->sendTimeline(inputEventId, timeline); } - InputEvent* consume(std::chrono::milliseconds timeout, bool handled = true) { - if (mInputReceiver == nullptr) { - return nullptr; - } - return mInputReceiver->consume(timeout, handled); - } - - MotionEvent* consumeMotion() { - InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED); - if (event == nullptr) { - ADD_FAILURE() << "Consume failed : no event"; - return nullptr; - } - if (event->getType() != InputEventType::MOTION) { - ADD_FAILURE() << "Instead of motion event, got " << *event; - return nullptr; - } - return static_cast<MotionEvent*>(event); - } - - void assertNoEvents() { + void assertNoEvents(std::chrono::milliseconds timeout = CONSUME_TIMEOUT_NO_EVENT_EXPECTED) { if (mInputReceiver == nullptr && mInfo.inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL)) { return; // Can't receive events if the window does not have input channel } ASSERT_NE(nullptr, mInputReceiver) << "Window without InputReceiver must specify feature NO_INPUT_CHANNEL"; - mInputReceiver->assertNoEvents(); + mInputReceiver->assertNoEvents(timeout); } sp<IBinder> getToken() { return mInfo.token; } @@ -1409,12 +1443,57 @@ public: int getChannelFd() { return mInputReceiver->getChannelFd(); } + // FakeWindowHandle uses this consume method to ensure received events are added to the trace. + std::unique_ptr<InputEvent> consume(std::chrono::milliseconds timeout, bool handled = true) { + if (mInputReceiver == nullptr) { + LOG(FATAL) << "Cannot consume event from a window with no input event receiver"; + } + std::unique_ptr<InputEvent> event = mInputReceiver->consume(timeout, handled); + if (event == nullptr) { + ADD_FAILURE() << "Consume failed: no event"; + } + expectReceivedEventTraced(event); + return event; + } + private: FakeWindowHandle(std::string name) : mName(name){}; const std::string mName; std::shared_ptr<FakeInputReceiver> mInputReceiver; static std::atomic<int32_t> sId; // each window gets a unique id, like in surfaceflinger friend class sp<FakeWindowHandle>; + + // FakeWindowHandle uses this receive method to ensure received events are added to the trace. + std::pair<std::optional<uint32_t /*seq*/>, std::unique_ptr<InputEvent>> receive() { + if (mInputReceiver == nullptr) { + ADD_FAILURE() << "Invalid receive event on window with no receiver"; + return std::make_pair(std::nullopt, nullptr); + } + auto out = mInputReceiver->receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED); + const auto& [_, event] = out; + expectReceivedEventTraced(event); + return std::move(out); + } + + void expectReceivedEventTraced(const std::unique_ptr<InputEvent>& event) { + if (!event) { + return; + } + + switch (event->getType()) { + case InputEventType::KEY: { + gVerifyingTrace->expectKeyDispatchTraced(static_cast<KeyEvent&>(*event), mInfo.id); + break; + } + case InputEventType::MOTION: { + gVerifyingTrace->expectMotionDispatchTraced(static_cast<MotionEvent&>(*event), + mInfo.id); + break; + } + default: + break; + } + } }; std::atomic<int32_t> FakeWindowHandle::sId{1}; @@ -1432,7 +1511,8 @@ public: } std::optional<int32_t> receiveEvent() { - return mInputReceiver.receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED); + const auto [sequenceNum, _] = mInputReceiver.receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED); + return sequenceNum; } void finishEvent(uint32_t consumeSeq) { return mInputReceiver.finishEvent(consumeSeq); } @@ -1470,9 +1550,9 @@ public: mInputReceiver.consumeMotionEvent(matcher); } - MotionEvent* consumeMotion() { return mInputReceiver.consumeMotion(); } + std::unique_ptr<MotionEvent> consumeMotion() { return mInputReceiver.consumeMotion(); } - void assertNoEvents() { mInputReceiver.assertNoEvents(); } + void assertNoEvents() { mInputReceiver.assertNoEvents(CONSUME_TIMEOUT_NO_EVENT_EXPECTED); } private: FakeInputReceiver mInputReceiver; @@ -1579,9 +1659,9 @@ static InputEventInjectionResult injectMotionUp(InputDispatcher& dispatcher, int static NotifyKeyArgs generateKeyArgs(int32_t action, int32_t displayId = ADISPLAY_ID_NONE) { nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); // Define a valid key event. - NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD, - displayId, POLICY_FLAG_PASS_TO_USER, action, /*flags=*/0, AKEYCODE_A, KEY_A, - AMETA_NONE, currentTime); + NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID, + AINPUT_SOURCE_KEYBOARD, displayId, POLICY_FLAG_PASS_TO_USER, action, + /*flags=*/0, AKEYCODE_A, KEY_A, AMETA_NONE, currentTime); return args; } @@ -1590,9 +1670,9 @@ static NotifyKeyArgs generateSystemShortcutArgs(int32_t action, int32_t displayId = ADISPLAY_ID_NONE) { nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); // Define a valid key event. - NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD, - displayId, 0, action, /*flags=*/0, AKEYCODE_C, KEY_C, AMETA_META_ON, - currentTime); + NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID, + AINPUT_SOURCE_KEYBOARD, displayId, 0, action, /*flags=*/0, AKEYCODE_C, KEY_C, + AMETA_META_ON, currentTime); return args; } @@ -1601,9 +1681,9 @@ static NotifyKeyArgs generateAssistantKeyArgs(int32_t action, int32_t displayId = ADISPLAY_ID_NONE) { nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); // Define a valid key event. - NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD, - displayId, 0, action, /*flags=*/0, AKEYCODE_ASSIST, KEY_ASSISTANT, - AMETA_NONE, currentTime); + NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID, + AINPUT_SOURCE_KEYBOARD, displayId, 0, action, /*flags=*/0, AKEYCODE_ASSIST, + KEY_ASSISTANT, AMETA_NONE, currentTime); return args; } @@ -1631,9 +1711,9 @@ static NotifyKeyArgs generateAssistantKeyArgs(int32_t action, nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); // Define a valid motion event. - NotifyMotionArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, source, displayId, - POLICY_FLAG_PASS_TO_USER, action, /*actionButton=*/0, /*flags=*/0, - AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, + NotifyMotionArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID, source, + displayId, POLICY_FLAG_PASS_TO_USER, action, /*actionButton=*/0, + /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties, pointerCoords, /*xPrecision=*/0, /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION, @@ -1652,7 +1732,8 @@ static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source, int32 static NotifyPointerCaptureChangedArgs generatePointerCaptureChangedArgs( const PointerCaptureRequest& request) { - return NotifyPointerCaptureChangedArgs(/*id=*/0, systemTime(SYSTEM_TIME_MONOTONIC), request); + return NotifyPointerCaptureChangedArgs(InputEvent::nextId(), systemTime(SYSTEM_TIME_MONOTONIC), + request); } } // namespace @@ -1688,6 +1769,24 @@ TEST_F(InputDispatcherTest, SetInputWindow_SingleWindowTouch) { window->consumeMotionDown(ADISPLAY_ID_DEFAULT); } +using InputDispatcherDeathTest = InputDispatcherTest; + +/** + * When 'onWindowInfosChanged' arguments contain a duplicate entry for the same window, dispatcher + * should crash. + */ +TEST_F(InputDispatcherDeathTest, DuplicateWindowInfosAbortDispatcher) { + testing::GTEST_FLAG(death_test_style) = "threadsafe"; + ScopedSilentDeath _silentDeath; + + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); + ASSERT_DEATH(mDispatcher->onWindowInfosChanged( + {{*window->getInfo(), *window->getInfo()}, {}, 0, 0}), + "Incorrect WindowInfosUpdate provided"); +} + TEST_F(InputDispatcherTest, WhenDisplayNotSpecified_InjectMotionToDefaultDisplay) { std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, @@ -2379,7 +2478,7 @@ TEST_F(InputDispatcherTest, HoverWhileWindowAppears) { sp<FakeWindowHandle> obscuringWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Obscuring window", ADISPLAY_ID_DEFAULT, - /*token=*/std::make_optional<sp<IBinder>>(nullptr)); + /*createInputChannel=*/false); obscuringWindow->setFrame(Rect(0, 0, 200, 200)); obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED); obscuringWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID); @@ -2428,7 +2527,7 @@ TEST_F(InputDispatcherTest, HoverMoveWhileWindowAppears) { sp<FakeWindowHandle> obscuringWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Obscuring window", ADISPLAY_ID_DEFAULT, - /*token=*/std::make_optional<sp<IBinder>>(nullptr)); + /*createInputChannel=*/false); obscuringWindow->setFrame(Rect(0, 0, 200, 200)); obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED); obscuringWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID); @@ -3355,6 +3454,150 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverIgnoresTouchTap) { } /** + * If stylus is down anywhere on the screen, then touches should not be delivered to windows that + * have InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH. + * + * Two windows: one on the left and one on the right. + * The window on the right has GLOBAL_STYLUS_BLOCKS_TOUCH config. + * Stylus down on the left window, and then touch down on the right window. + * Check that the right window doesn't get touches while the stylus is down on the left window. + */ +TEST_F(InputDispatcherMultiDeviceTest, GlobalStylusDownBlocksTouch) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> leftWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Left window", + ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 100, 100)); + + sp<FakeWindowHandle> sbtRightWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, + "Stylus blocks touch (right) window", ADISPLAY_ID_DEFAULT); + sbtRightWindow->setFrame(Rect(100, 100, 200, 200)); + sbtRightWindow->setGlobalStylusBlocksTouch(true); + + mDispatcher->onWindowInfosChanged( + {{*leftWindow->getInfo(), *sbtRightWindow->getInfo()}, {}, 0, 0}); + + const int32_t stylusDeviceId = 5; + const int32_t touchDeviceId = 4; + + // Stylus down in the left window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(52)) + .deviceId(stylusDeviceId) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId))); + + // Finger tap on the right window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(151)) + .deviceId(touchDeviceId) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(151)) + .deviceId(touchDeviceId) + .build()); + + // The touch should be blocked, because stylus is down somewhere else on screen! + sbtRightWindow->assertNoEvents(); + + // Continue stylus motion, and ensure it's not impacted. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(53)) + .deviceId(stylusDeviceId) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(53)) + .deviceId(stylusDeviceId) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId))); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_UP), WithDeviceId(stylusDeviceId))); + + // Now that the stylus gesture is done, touches should be getting delivered correctly. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(153)) + .deviceId(touchDeviceId) + .build()); + sbtRightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); +} + +/** + * If stylus is hovering anywhere on the screen, then touches should not be delivered to windows + * that have InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH. + * + * Two windows: one on the left and one on the right. + * The window on the right has GLOBAL_STYLUS_BLOCKS_TOUCH config. + * Stylus hover on the left window, and then touch down on the right window. + * Check that the right window doesn't get touches while the stylus is hovering on the left window. + */ +TEST_F(InputDispatcherMultiDeviceTest, GlobalStylusHoverBlocksTouch) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> leftWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Left window", + ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 100, 100)); + + sp<FakeWindowHandle> sbtRightWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, + "Stylus blocks touch (right) window", ADISPLAY_ID_DEFAULT); + sbtRightWindow->setFrame(Rect(100, 100, 200, 200)); + sbtRightWindow->setGlobalStylusBlocksTouch(true); + + mDispatcher->onWindowInfosChanged( + {{*leftWindow->getInfo(), *sbtRightWindow->getInfo()}, {}, 0, 0}); + + const int32_t stylusDeviceId = 5; + const int32_t touchDeviceId = 4; + + // Stylus hover in the left window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(52)) + .deviceId(stylusDeviceId) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId))); + + // Finger tap on the right window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(151)) + .deviceId(touchDeviceId) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(151)) + .deviceId(touchDeviceId) + .build()); + + // The touch should be blocked, because stylus is hovering somewhere else on screen! + sbtRightWindow->assertNoEvents(); + + // Continue stylus motion, and ensure it's not impacted. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(53)) + .deviceId(stylusDeviceId) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(53)) + .deviceId(stylusDeviceId) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId))); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId))); + + // Now that the stylus gesture is done, touches should be getting delivered correctly. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(153)) + .deviceId(touchDeviceId) + .build()); + sbtRightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); +} + +/** * 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. @@ -3625,8 +3868,8 @@ TEST_F(InputDispatcherTest, SplitTouchesSendCorrectActionDownTime) { mDispatcher->waitForIdle(); - MotionEvent* motionEvent1 = window1->consumeMotion(); - ASSERT_NE(motionEvent1, nullptr); + std::unique_ptr<MotionEvent> motionEvent1 = window1->consumeMotionEvent(); + ASSERT_NE(nullptr, motionEvent1); window2->assertNoEvents(); nsecs_t downTimeForWindow1 = motionEvent1->getDownTime(); ASSERT_EQ(motionEvent1->getDownTime(), motionEvent1->getEventTime()); @@ -3634,8 +3877,8 @@ TEST_F(InputDispatcherTest, SplitTouchesSendCorrectActionDownTime) { // Now touch down on the window with another pointer mDispatcher->notifyMotion(generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}})); mDispatcher->waitForIdle(); - MotionEvent* motionEvent2 = window2->consumeMotion(); - ASSERT_NE(motionEvent2, nullptr); + std::unique_ptr<MotionEvent> motionEvent2 = window2->consumeMotionEvent(); + ASSERT_NE(nullptr, motionEvent2); nsecs_t downTimeForWindow2 = motionEvent2->getDownTime(); ASSERT_NE(downTimeForWindow1, downTimeForWindow2); ASSERT_EQ(motionEvent2->getDownTime(), motionEvent2->getEventTime()); @@ -4233,8 +4476,7 @@ TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsKeyStream) { // When device reset happens, that key stream should be terminated with FLAG_CANCELED // on the app side. mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID}); - window->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT, - AKEY_EVENT_FLAG_CANCELED); + window->consumeKeyUp(ADISPLAY_ID_DEFAULT, AKEY_EVENT_FLAG_CANCELED); } TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsMotionStream) { @@ -4473,7 +4715,8 @@ TEST_F(InputDispatcherTest, NonSplitTouchableWindowReceivesMultiTouch) { InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - const MotionEvent* event = window->consumeMotion(); + std::unique_ptr<MotionEvent> event = window->consumeMotionEvent(); + ASSERT_NE(nullptr, event); EXPECT_EQ(POINTER_1_DOWN, event->getAction()); EXPECT_EQ(70, event->getX(0)); // 50 + 20 EXPECT_EQ(90, event->getY(0)); // 50 + 40 @@ -4744,7 +4987,8 @@ TEST_F(InputDispatcherTest, WhenMultiDisplayWindowSameToken_DispatchCancelToTarg EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindowDefaultDisplay->getToken())); // windowDefaultDisplay gets cancel - MotionEvent* event = windowDefaultDisplay->consumeMotion(); + std::unique_ptr<MotionEvent> event = windowDefaultDisplay->consumeMotionEvent(); + ASSERT_NE(nullptr, event); EXPECT_EQ(AMOTION_EVENT_ACTION_CANCEL, event->getAction()); // The cancel event is sent to windowDefaultDisplay of the ADISPLAY_ID_DEFAULT display, so the @@ -4882,7 +5126,7 @@ TEST_F(InputDispatcherDisplayProjectionTest, WindowGetsEventsInCorrectCoordinate {PointF{150, 220}})); firstWindow->assertNoEvents(); - const MotionEvent* event = secondWindow->consumeMotion(); + std::unique_ptr<MotionEvent> event = secondWindow->consumeMotionEvent(); ASSERT_NE(nullptr, event); EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, event->getAction()); @@ -5137,9 +5381,11 @@ TEST_P(TransferTouchFixture, TransferTouch_OnePointer) { sp<FakeWindowHandle> wallpaper = sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); wallpaper->setIsWallpaper(true); - // Add the windows to the dispatcher + // Add the windows to the dispatcher, and ensure the first window is focused mDispatcher->onWindowInfosChanged( {{*firstWindow->getInfo(), *secondWindow->getInfo(), *wallpaper->getInfo()}, {}, 0, 0}); + setFocusedWindow(firstWindow); + firstWindow->consumeFocusEvent(true); // Send down to the first window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, @@ -5149,6 +5395,8 @@ TEST_P(TransferTouchFixture, TransferTouch_OnePointer) { firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); wallpaper->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + // Dispatcher reports pointer down outside focus for the wallpaper + mFakePolicy->assertOnPointerDownEquals(wallpaper->getToken()); // Transfer touch to the second window TransferFunction f = GetParam(); @@ -5156,15 +5404,17 @@ TEST_P(TransferTouchFixture, TransferTouch_OnePointer) { ASSERT_TRUE(success); // The first window gets cancel and the second gets down firstWindow->consumeMotionCancel(); - secondWindow->consumeMotionDown(); + secondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); wallpaper->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + // There should not be any changes to the focused window when transferring touch + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertOnPointerDownWasNotCalled()); // Send up event to the second window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); - // The first window gets no events and the second gets up + // The first window gets no events and the second gets up firstWindow->assertNoEvents(); - secondWindow->consumeMotionUp(); + secondWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); wallpaper->assertNoEvents(); } @@ -5211,7 +5461,7 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWindowsWithSpy) { ASSERT_TRUE(success); // The first window gets cancel and the second gets down firstWindow->consumeMotionCancel(); - secondWindow->consumeMotionDown(); + secondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); // Send up event to the second window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, @@ -5219,7 +5469,7 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWindowsWithSpy) { // The first window gets no events and the second+spy get up firstWindow->assertNoEvents(); spyWindow->consumeMotionUp(); - secondWindow->consumeMotionUp(); + secondWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); } TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) { @@ -5262,22 +5512,24 @@ TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) { ASSERT_TRUE(success); // The first window gets cancel and the second gets down and pointer down firstWindow->consumeMotionCancel(); - secondWindow->consumeMotionDown(); - secondWindow->consumeMotionPointerDown(1); + secondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + secondWindow->consumeMotionPointerDown(1, ADISPLAY_ID_DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); // Send pointer up to the second window mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint})); // The first window gets nothing and the second gets pointer up firstWindow->assertNoEvents(); - secondWindow->consumeMotionPointerUp(1); + secondWindow->consumeMotionPointerUp(1, ADISPLAY_ID_DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); // Send up event to the second window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); // The first window gets nothing and the second gets up firstWindow->assertNoEvents(); - secondWindow->consumeMotionUp(); + secondWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); } TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) { @@ -5324,24 +5576,26 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) { // The first window gets cancel and the second gets down firstWindow->consumeMotionCancel(); - secondWindow->consumeMotionDown(); + secondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); wallpaper1->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); - wallpaper2->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaper2->consumeMotionDown(ADISPLAY_ID_DEFAULT, + expectedWallpaperFlags | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); // Send up event to the second window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); // The first window gets no events and the second gets up firstWindow->assertNoEvents(); - secondWindow->consumeMotionUp(); + secondWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); wallpaper1->assertNoEvents(); - wallpaper2->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaper2->consumeMotionUp(ADISPLAY_ID_DEFAULT, + expectedWallpaperFlags | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); } // For the cases of single pointer touch and two pointers non-split touch, the api's // 'transferTouch' and 'transferTouchFocus' are equivalent in behaviour. They only differ // for the case where there are multiple pointers split across several windows. -INSTANTIATE_TEST_SUITE_P(TransferFunctionTests, TransferTouchFixture, +INSTANTIATE_TEST_SUITE_P(InputDispatcherTransferFunctionTests, TransferTouchFixture, ::testing::Values( [&](const std::unique_ptr<InputDispatcher>& dispatcher, sp<IBinder> /*ignored*/, sp<IBinder> destChannelToken) { @@ -5394,7 +5648,8 @@ TEST_F(InputDispatcherTest, TransferTouchFocus_TwoPointersSplitTouch) { mDispatcher->transferTouchFocus(firstWindow->getToken(), secondWindow->getToken()); // The first window gets cancel and the new gets pointer down (it already saw down) firstWindow->consumeMotionCancel(); - secondWindow->consumeMotionPointerDown(1); + secondWindow->consumeMotionPointerDown(1, ADISPLAY_ID_DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); // Send pointer up to the second window mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, @@ -5402,14 +5657,15 @@ TEST_F(InputDispatcherTest, TransferTouchFocus_TwoPointersSplitTouch) { {pointInFirst, pointInSecond})); // The first window gets nothing and the second gets pointer up firstWindow->assertNoEvents(); - secondWindow->consumeMotionPointerUp(1); + secondWindow->consumeMotionPointerUp(1, ADISPLAY_ID_DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); // Send up event to the second window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); // The first window gets nothing and the second gets up firstWindow->assertNoEvents(); - secondWindow->consumeMotionUp(); + secondWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); } // Same as TransferTouchFocus_TwoPointersSplitTouch, but using 'transferTouch' api. @@ -5520,21 +5776,23 @@ TEST_F(InputDispatcherTest, TransferTouchFocus_CloneSurface) { secondWindowInPrimary->getToken())); // The first window gets cancel. firstWindowInPrimary->consumeMotionCancel(); - secondWindowInPrimary->consumeMotionDown(); + secondWindowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; firstWindowInPrimary->assertNoEvents(); - secondWindowInPrimary->consumeMotionMove(); + secondWindowInPrimary->consumeMotionMove(ADISPLAY_ID_DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; firstWindowInPrimary->assertNoEvents(); - secondWindowInPrimary->consumeMotionUp(); + secondWindowInPrimary->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); } // Same as TransferTouchFocus_CloneSurface, but this touch on the secondary display and use @@ -5573,27 +5831,29 @@ TEST_F(InputDispatcherTest, TransferTouch_CloneSurface) { << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; // Window should receive motion event. - firstWindowInPrimary->consumeMotionDown(SECOND_DISPLAY_ID); + firstWindowInSecondary->consumeMotionDown(SECOND_DISPLAY_ID); // Transfer touch focus ASSERT_TRUE(mDispatcher->transferTouch(secondWindowInSecondary->getToken(), SECOND_DISPLAY_ID)); // The first window gets cancel. - firstWindowInPrimary->consumeMotionCancel(SECOND_DISPLAY_ID); - secondWindowInPrimary->consumeMotionDown(SECOND_DISPLAY_ID); + firstWindowInSecondary->consumeMotionCancel(SECOND_DISPLAY_ID); + secondWindowInSecondary->consumeMotionDown(SECOND_DISPLAY_ID, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - firstWindowInPrimary->assertNoEvents(); - secondWindowInPrimary->consumeMotionMove(SECOND_DISPLAY_ID); + firstWindowInSecondary->assertNoEvents(); + secondWindowInSecondary->consumeMotionMove(SECOND_DISPLAY_ID, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - firstWindowInPrimary->assertNoEvents(); - secondWindowInPrimary->consumeMotionUp(SECOND_DISPLAY_ID); + firstWindowInSecondary->assertNoEvents(); + secondWindowInSecondary->consumeMotionUp(SECOND_DISPLAY_ID, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); } TEST_F(InputDispatcherTest, FocusedWindow_ReceivesFocusEventAndKeyEvent) { @@ -5931,7 +6191,8 @@ TEST_F(InputDispatcherMonitorTest, NoWindowTransform) { injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; window->consumeMotionDown(ADISPLAY_ID_DEFAULT); - MotionEvent* event = monitor.consumeMotion(); + std::unique_ptr<MotionEvent> event = monitor.consumeMotion(); + ASSERT_NE(nullptr, event); // Even though window has transform, gesture monitor must not. ASSERT_EQ(ui::Transform(), event->getTransform()); } @@ -5946,6 +6207,219 @@ TEST_F(InputDispatcherMonitorTest, InjectionFailsWithNoWindow) { monitor.assertNoEvents(); } +/** + * Two displays + * The first monitor has a foreground window, a monitor + * The second window has only one monitor. + * We first inject a Down event into the first display, this injection should succeed and both + * the foreground window and monitor should receive a down event, then inject a Down event into + * the second display as well, this injection should fail, at this point, the first display + * window and monitor should not receive a cancel or any other event. + * Continue to inject Move and UP events to the first display, the events should be received + * normally by the foreground window and monitor. + */ +TEST_F(InputDispatcherMonitorTest, MonitorTouchIsNotCanceledWhenAnotherEmptyDisplayReceiveEvents) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); + + FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT); + FakeMonitorReceiver secondMonitor = FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {100, 200})) + << "The down event injected into the first display should succeed"; + + window->consumeMotionDown(); + monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT); + + ASSERT_EQ(InputEventInjectionResult::FAILED, + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID, + {100, 200})) + << "The down event injected into the second display should fail since there's no " + "touchable window"; + + // Continue to inject event to first display. + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {110, 220})) + << "The move event injected into the first display should succeed"; + + window->consumeMotionMove(); + monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT); + + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {110, 220})) + << "The up event injected into the first display should succeed"; + + window->consumeMotionUp(); + monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT); + + window->assertNoEvents(); + monitor.assertNoEvents(); + secondMonitor.assertNoEvents(); +} + +/** + * Two displays + * There is a monitor and foreground window on each display. + * First, we inject down events into each of the two displays, at this point, the foreground windows + * and monitors on both displays should receive down events. + * At this point, the foreground window of the second display goes away, the gone window should + * receive the cancel event, and the other windows and monitors should not receive any events. + * Inject a move event into the second display. At this point, the injection should fail because + * the second display no longer has a foreground window. At this point, the monitor on the second + * display should receive a cancel event, and any windows or monitors on the first display should + * not receive any events, and any subsequent injection of events into the second display should + * also fail. + * Continue to inject events into the first display, and the events should all be injected + * successfully and received normally. + */ +TEST_F(InputDispatcherMonitorTest, MonitorTouchIsNotCancelWhenAnotherDisplayMonitorTouchCanceled) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); + sp<FakeWindowHandle> secondWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "SecondForeground", + SECOND_DISPLAY_ID); + + FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT); + FakeMonitorReceiver secondMonitor = FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID); + + // There is a foreground window on both displays. + mDispatcher->onWindowInfosChanged({{*window->getInfo(), *secondWindow->getInfo()}, {}, 0, 0}); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {100, 200})) + << "The down event injected into the first display should succeed"; + + window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT); + + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID, + {100, 200})) + << "The down event injected into the second display should succeed"; + + secondWindow->consumeMotionDown(SECOND_DISPLAY_ID); + secondMonitor.consumeMotionDown(SECOND_DISPLAY_ID); + + // Now second window is gone away. + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + // The gone window should receive a cancel, and the monitor on the second display should not + // receive any events. + secondWindow->consumeMotionCancel(SECOND_DISPLAY_ID); + secondMonitor.assertNoEvents(); + + ASSERT_EQ(InputEventInjectionResult::FAILED, + injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, + SECOND_DISPLAY_ID, {110, 220})) + << "The move event injected into the second display should fail because there's no " + "touchable window"; + // Now the monitor on the second display should receive a cancel event. + secondMonitor.consumeMotionCancel(SECOND_DISPLAY_ID); + + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {110, 200})) + << "The move event injected into the first display should succeed"; + + window->consumeMotionMove(); + monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT); + + ASSERT_EQ(InputEventInjectionResult::FAILED, + injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID, + {110, 220})) + << "The up event injected into the second display should fail because there's no " + "touchable window"; + + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {110, 220})) + << "The up event injected into the first display should succeed"; + + window->consumeMotionUp(ADISPLAY_ID_DEFAULT); + monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT); + + window->assertNoEvents(); + monitor.assertNoEvents(); + secondWindow->assertNoEvents(); + secondMonitor.assertNoEvents(); +} + +/** + * One display with transform + * There is a foreground window and a monitor on the display + * Inject down event and move event sequentially, the foreground window and monitor can receive down + * event and move event, then let the foreground window go away, the foreground window receives + * cancel event, inject move event again, the monitor receives cancel event, all the events received + * by the monitor should be with the same transform as the display + */ +TEST_F(InputDispatcherMonitorTest, MonitorTouchCancelEventWithDisplayTransform) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); + FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT); + + ui::Transform transform; + transform.set({1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 0, 0, 1}); + + gui::DisplayInfo displayInfo; + displayInfo.displayId = ADISPLAY_ID_DEFAULT; + displayInfo.transform = transform; + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {displayInfo}, 0, 0}); + + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {100, 200})) + << "The down event injected should succeed"; + + window->consumeMotionDown(); + std::unique_ptr<MotionEvent> downMotionEvent = monitor.consumeMotion(); + EXPECT_EQ(transform, downMotionEvent->getTransform()); + EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, downMotionEvent->getAction()); + + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {110, 220})) + << "The move event injected should succeed"; + + window->consumeMotionMove(); + std::unique_ptr<MotionEvent> moveMotionEvent = monitor.consumeMotion(); + EXPECT_EQ(transform, moveMotionEvent->getTransform()); + EXPECT_EQ(AMOTION_EVENT_ACTION_MOVE, moveMotionEvent->getAction()); + + // Let foreground window gone + mDispatcher->onWindowInfosChanged({{}, {displayInfo}, 0, 0}); + + // Foreground window should receive a cancel event, but not the monitor. + window->consumeMotionCancel(); + + ASSERT_EQ(InputEventInjectionResult::FAILED, + injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {110, 220})) + << "The move event injected should failed"; + // Now foreground should not receive any events, but monitor should receive a cancel event + // with transform that same as display's display. + std::unique_ptr<MotionEvent> cancelMotionEvent = monitor.consumeMotion(); + EXPECT_EQ(transform, cancelMotionEvent->getTransform()); + EXPECT_EQ(ADISPLAY_ID_DEFAULT, cancelMotionEvent->getDisplayId()); + EXPECT_EQ(AMOTION_EVENT_ACTION_CANCEL, cancelMotionEvent->getAction()); + + // Other event inject to this display should fail. + ASSERT_EQ(InputEventInjectionResult::FAILED, + injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {110, 220})) + << "The up event injected should fail because the touched window was removed"; + window->assertNoEvents(); + monitor.assertNoEvents(); +} + TEST_F(InputDispatcherTest, TestMoveEvent) { std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, @@ -5968,8 +6442,7 @@ TEST_F(InputDispatcherTest, TestMoveEvent) { motionArgs.pointerCoords[0].getX() - 10); mDispatcher->notifyMotion(motionArgs); - window->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE, ADISPLAY_ID_DEFAULT, - /*expectedFlags=*/0); + window->consumeMotionMove(ADISPLAY_ID_DEFAULT, /*expectedFlags=*/0); } /** @@ -6039,9 +6512,8 @@ TEST_F(InputDispatcherTest, VerifyInputEvent_KeyEvent) { const NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN); mDispatcher->notifyKey(keyArgs); - KeyEvent* event = window->consumeKey(); + std::unique_ptr<KeyEvent> event = window->consumeKey(); ASSERT_NE(event, nullptr); - std::unique_ptr<VerifiedInputEvent> verified = mDispatcher->verifyInputEvent(*event); ASSERT_NE(verified, nullptr); ASSERT_EQ(verified->type, VerifiedInputEvent::Type::KEY); @@ -6083,9 +6555,8 @@ TEST_F(InputDispatcherTest, VerifyInputEvent_MotionEvent) { ADISPLAY_ID_DEFAULT); mDispatcher->notifyMotion(motionArgs); - MotionEvent* event = window->consumeMotion(); - ASSERT_NE(event, nullptr); - + std::unique_ptr<MotionEvent> event = window->consumeMotionEvent(); + ASSERT_NE(nullptr, event); std::unique_ptr<VerifiedInputEvent> verified = mDispatcher->verifyInputEvent(*event); ASSERT_NE(verified, nullptr); ASSERT_EQ(verified->type, VerifiedInputEvent::Type::MOTION); @@ -6613,13 +7084,13 @@ TEST_F(InputDispatcherTest, HoverEnterExitSynthesisUsesNewEventId) { ADISPLAY_ID_DEFAULT, {PointF{50, 50}}); mDispatcher->notifyMotion(notifyArgs); - const MotionEvent& leftEnter = left->consumeMotionEvent( + std::unique_ptr<MotionEvent> leftEnter = left->consumeMotionEvent( AllOf(WithMotionAction(ACTION_HOVER_ENTER), Not(WithEventId(notifyArgs.id)), WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER))); - + ASSERT_NE(nullptr, leftEnter); spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER), Not(WithEventId(notifyArgs.id)), - Not(WithEventId(leftEnter.getId())), + Not(WithEventId(leftEnter->getId())), WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER))); // Send move to the right window, and ensure hover exit and enter are synthesized with new ids. @@ -6627,13 +7098,13 @@ TEST_F(InputDispatcherTest, HoverEnterExitSynthesisUsesNewEventId) { {PointF{150, 50}}); mDispatcher->notifyMotion(notifyArgs); - const MotionEvent& leftExit = left->consumeMotionEvent( + std::unique_ptr<MotionEvent> leftExit = left->consumeMotionEvent( AllOf(WithMotionAction(ACTION_HOVER_EXIT), Not(WithEventId(notifyArgs.id)), WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER))); - + ASSERT_NE(nullptr, leftExit); right->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER), Not(WithEventId(notifyArgs.id)), - Not(WithEventId(leftExit.getId())), + Not(WithEventId(leftExit->getId())), WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER))); spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithEventId(notifyArgs.id))); @@ -6664,8 +7135,8 @@ protected: } void consumeKey(bool handled, const ::testing::Matcher<KeyEvent>& matcher) { - KeyEvent* event = mWindow->consumeKey(handled); - ASSERT_NE(event, nullptr) << "Did not receive key event"; + std::unique_ptr<KeyEvent> event = mWindow->consumeKey(handled); + ASSERT_NE(nullptr, event); ASSERT_THAT(*event, matcher); } }; @@ -6846,6 +7317,104 @@ TEST_F(InputDispatcherFallbackKeyTest, CanceledKeyCancelsFallback) { mWindow->assertNoEvents(); } +TEST_F(InputDispatcherFallbackKeyTest, InputChannelRemovedDuringPolicyCall) { + setFallback(AKEYCODE_B); + mDispatcher->notifyKey( + KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build()); + + // Do not handle this key event. + consumeKey(/*handled=*/false, + AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0))); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A)); + consumeKey(/*handled=*/true, + AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_B), + WithFlags(AKEY_EVENT_FLAG_FALLBACK))); + + mFakePolicy->setUnhandledKeyHandler([&](const KeyEvent& event) { + // When the unhandled key is reported to the policy next, remove the input channel. + mDispatcher->removeInputChannel(mWindow->getToken()); + return KeyEventBuilder(event).keyCode(AKEYCODE_B).build(); + }); + // Release the original key, and let the app now handle the previously unhandled key. + // This should result in the previously generated fallback key to be cancelled. + // Since the policy was notified of the unhandled DOWN event earlier, it will also be notified + // of the UP event for consistency. The Dispatcher calls into the policy from its own thread + // without holding the lock, because it need to synchronously fetch the fallback key. While in + // the policy call, we will now remove the input channel. Once the policy call returns, the + // Dispatcher will no longer have a channel to send cancellation events to. Ensure this does + // not cause any crashes. + mDispatcher->notifyKey( + KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build()); + consumeKey(/*handled=*/true, + AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_A), WithFlags(0))); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A)); +} + +TEST_F(InputDispatcherFallbackKeyTest, WindowRemovedDuringPolicyCall) { + setFallback(AKEYCODE_B); + mDispatcher->notifyKey( + KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build()); + + // Do not handle this key event. + consumeKey(/*handled=*/false, + AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0))); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A)); + consumeKey(/*handled=*/true, + AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_B), + WithFlags(AKEY_EVENT_FLAG_FALLBACK))); + + mFakePolicy->setUnhandledKeyHandler([&](const KeyEvent& event) { + // When the unhandled key is reported to the policy next, remove the window. + mDispatcher->onWindowInfosChanged({{}, {}, 0, 0}); + return KeyEventBuilder(event).keyCode(AKEYCODE_B).build(); + }); + // Release the original key, which the app will not handle. When this unhandled key is reported + // to the policy, the window will be removed. + mDispatcher->notifyKey( + KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build()); + consumeKey(/*handled=*/false, + AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_A), WithFlags(0))); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A)); + + // Since the window was removed, it loses focus, and the channel state will be reset. + consumeKey(/*handled=*/true, + AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_B), + WithFlags(AKEY_EVENT_FLAG_FALLBACK | AKEY_EVENT_FLAG_CANCELED))); + mWindow->consumeFocusEvent(false); + mWindow->assertNoEvents(); +} + +TEST_F(InputDispatcherFallbackKeyTest, WindowRemovedWhileAwaitingFinishedSignal) { + setFallback(AKEYCODE_B); + mDispatcher->notifyKey( + KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build()); + + // Do not handle this key event. + consumeKey(/*handled=*/false, + AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0))); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A)); + const auto [seq, event] = mWindow->receiveEvent(); + ASSERT_TRUE(seq.has_value() && event != nullptr) << "Failed to receive fallback event"; + ASSERT_EQ(event->getType(), InputEventType::KEY); + ASSERT_THAT(static_cast<const KeyEvent&>(*event), + AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_B), + WithFlags(AKEY_EVENT_FLAG_FALLBACK))); + + // Remove the window now, which should generate a cancellations and make the window lose focus. + mDispatcher->onWindowInfosChanged({{}, {}, 0, 0}); + consumeKey(/*handled=*/true, + AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_A), + WithFlags(AKEY_EVENT_FLAG_CANCELED))); + consumeKey(/*handled=*/true, + AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_B), + WithFlags(AKEY_EVENT_FLAG_FALLBACK | AKEY_EVENT_FLAG_CANCELED))); + mWindow->consumeFocusEvent(false); + + // Finish the event by reporting it as handled. + mWindow->finishEvent(*seq); + mWindow->assertNoEvents(); +} + class InputDispatcherKeyRepeatTest : public InputDispatcherTest { protected: static constexpr std::chrono::nanoseconds KEY_REPEAT_TIMEOUT = 40ms; @@ -6855,12 +7424,9 @@ protected: sp<FakeWindowHandle> mWindow; virtual void SetUp() override { - mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>(); - mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy); - mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false); - mDispatcher->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY); - ASSERT_EQ(OK, mDispatcher->start()); + InputDispatcherTest::SetUp(); + mDispatcher->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY); setUpWindow(); } @@ -6897,7 +7463,7 @@ protected: mDispatcher->notifyKey(keyArgs); // Window should receive key down event. - mWindow->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT, + mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT, /*expectedFlags=*/0); } }; @@ -6967,8 +7533,8 @@ TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseEventIdFrom GTEST_SKIP() << "Flaky test (b/270393106)"; sendAndConsumeKeyDown(/*deviceId=*/1); for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) { - KeyEvent* repeatEvent = mWindow->consumeKey(); - ASSERT_NE(nullptr, repeatEvent) << "Didn't receive event with repeat count " << repeatCount; + std::unique_ptr<KeyEvent> repeatEvent = mWindow->consumeKey(); + ASSERT_NE(nullptr, repeatEvent); EXPECT_EQ(IdGenerator::Source::INPUT_DISPATCHER, IdGenerator::getSource(repeatEvent->getId())); } @@ -6980,8 +7546,8 @@ TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseUniqueEvent std::unordered_set<int32_t> idSet; for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) { - KeyEvent* repeatEvent = mWindow->consumeKey(); - ASSERT_NE(nullptr, repeatEvent) << "Didn't receive event with repeat count " << repeatCount; + std::unique_ptr<KeyEvent> repeatEvent = mWindow->consumeKey(); + ASSERT_NE(nullptr, repeatEvent); int32_t id = repeatEvent->getId(); EXPECT_EQ(idSet.end(), idSet.find(id)); idSet.insert(id); @@ -7071,8 +7637,7 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, SetInputWindow_MultiDisplayFocus) mDispatcher->onWindowInfosChanged({{*windowInPrimary->getInfo()}, {}, 0, 0}); // Old focus should receive a cancel event. - windowInSecondary->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_NONE, - AKEY_EVENT_FLAG_CANCELED); + windowInSecondary->consumeKeyUp(ADISPLAY_ID_NONE, AKEY_EVENT_FLAG_CANCELED); // Test inject a key down, should timeout because of no target window. ASSERT_NO_FATAL_FAILURE(assertInjectedKeyTimesOut(*mDispatcher)); @@ -7208,6 +7773,94 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, CancelTouch_MultiDisplay) { monitorInSecondary.assertNoEvents(); } +/** + * Send a key to the primary display and to the secondary display. + * Then cause the key on the primary display to be canceled by sending in a stale key. + * Ensure that the key on the primary display is canceled, and that the key on the secondary display + * does not get canceled. + */ +TEST_F(InputDispatcherFocusOnTwoDisplaysTest, WhenDropKeyEvent_OnlyCancelCorrespondingKeyGesture) { + // Send a key down on primary display + mDispatcher->notifyKey( + KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD) + .displayId(ADISPLAY_ID_DEFAULT) + .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT) + .build()); + windowInPrimary->consumeKeyEvent( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithDisplayId(ADISPLAY_ID_DEFAULT))); + windowInSecondary->assertNoEvents(); + + // Send a key down on second display + mDispatcher->notifyKey( + KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD) + .displayId(SECOND_DISPLAY_ID) + .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT) + .build()); + windowInSecondary->consumeKeyEvent( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithDisplayId(SECOND_DISPLAY_ID))); + windowInPrimary->assertNoEvents(); + + // Send a valid key up event on primary display that will be dropped because it is stale + NotifyKeyArgs staleKeyUp = + KeyArgsBuilder(AKEY_EVENT_ACTION_UP, AINPUT_SOURCE_KEYBOARD) + .displayId(ADISPLAY_ID_DEFAULT) + .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT) + .build(); + static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 10ms; + mFakePolicy->setStaleEventTimeout(STALE_EVENT_TIMEOUT); + std::this_thread::sleep_for(STALE_EVENT_TIMEOUT); + mDispatcher->notifyKey(staleKeyUp); + + // Only the key gesture corresponding to the dropped event should receive the cancel event. + // Therefore, windowInPrimary should get the cancel event and windowInSecondary should not + // receive any events. + windowInPrimary->consumeKeyEvent(AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), + WithDisplayId(ADISPLAY_ID_DEFAULT), + WithFlags(AKEY_EVENT_FLAG_CANCELED))); + windowInSecondary->assertNoEvents(); +} + +/** + * Similar to 'WhenDropKeyEvent_OnlyCancelCorrespondingKeyGesture' but for motion events. + */ +TEST_F(InputDispatcherFocusOnTwoDisplaysTest, WhenDropMotionEvent_OnlyCancelCorrespondingGesture) { + // Send touch down on primary display. + mDispatcher->notifyMotion( + MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200)) + .displayId(ADISPLAY_ID_DEFAULT) + .build()); + windowInPrimary->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(ADISPLAY_ID_DEFAULT))); + windowInSecondary->assertNoEvents(); + + // Send touch down on second display. + mDispatcher->notifyMotion( + MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200)) + .displayId(SECOND_DISPLAY_ID) + .build()); + windowInPrimary->assertNoEvents(); + windowInSecondary->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(SECOND_DISPLAY_ID))); + + // inject a valid MotionEvent on primary display that will be stale when it arrives. + NotifyMotionArgs staleMotionUp = + MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .displayId(ADISPLAY_ID_DEFAULT) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200)) + .build(); + static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 10ms; + mFakePolicy->setStaleEventTimeout(STALE_EVENT_TIMEOUT); + std::this_thread::sleep_for(STALE_EVENT_TIMEOUT); + mDispatcher->notifyMotion(staleMotionUp); + + // For stale motion events, we let the gesture to complete. This behaviour is different from key + // events, where we would cancel the current keys instead. + windowInPrimary->consumeMotionEvent(WithMotionAction(ACTION_UP)); + windowInSecondary->assertNoEvents(); +} + class InputFilterTest : public InputDispatcherTest { protected: void testNotifyMotion(int32_t displayId, bool expectToBeFiltered, @@ -7407,6 +8060,130 @@ TEST_F(InputFilterInjectionPolicyTest, RegularInjectedEvents_ReceiveVirtualDevic /*resolvedDeviceId=*/VIRTUAL_KEYBOARD_ID, /*flags=*/0); } +class InputDispatcherUserActivityPokeTests : public InputDispatcherTest { +protected: + virtual void SetUp() override { + InputDispatcherTest::SetUp(); + + std::shared_ptr<FakeApplicationHandle> application = + std::make_shared<FakeApplicationHandle>(); + application->setDispatchingTimeout(100ms); + mWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow", + ADISPLAY_ID_DEFAULT); + mWindow->setFrame(Rect(0, 0, 100, 100)); + mWindow->setDispatchingTimeout(100ms); + mWindow->setFocusable(true); + + // Set focused application. + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + + mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0}); + setFocusedWindow(mWindow); + mWindow->consumeFocusEvent(true); + } + + void notifyAndConsumeMotion(int32_t action, uint32_t source, int32_t displayId, + nsecs_t eventTime) { + mDispatcher->notifyMotion(MotionArgsBuilder(action, source) + .displayId(displayId) + .eventTime(eventTime) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .build()); + mWindow->consumeMotionEvent(WithMotionAction(action)); + } + +private: + sp<FakeWindowHandle> mWindow; +}; + +TEST_F_WITH_FLAGS( + InputDispatcherUserActivityPokeTests, MinPokeTimeObserved, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + rate_limit_user_activity_poke_in_dispatcher))) { + mDispatcher->setMinTimeBetweenUserActivityPokes(50ms); + + // First event of type TOUCH. Should poke. + notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(50)); + mFakePolicy->assertUserActivityPoked( + {{milliseconds_to_nanoseconds(50), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}}); + + // 80ns > 50ns has passed since previous TOUCH event. Should poke. + notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(130)); + mFakePolicy->assertUserActivityPoked( + {{milliseconds_to_nanoseconds(130), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}}); + + // First event of type OTHER. Should poke (despite being within 50ns of previous TOUCH event). + notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(135)); + mFakePolicy->assertUserActivityPoked( + {{milliseconds_to_nanoseconds(135), USER_ACTIVITY_EVENT_OTHER, ADISPLAY_ID_DEFAULT}}); + + // Within 50ns of previous TOUCH event. Should NOT poke. + notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(140)); + mFakePolicy->assertUserActivityNotPoked(); + + // Within 50ns of previous OTHER event. Should NOT poke. + notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(150)); + mFakePolicy->assertUserActivityNotPoked(); + + // Within 50ns of previous TOUCH event (which was at time 130). Should NOT poke. + // Note that STYLUS is mapped to TOUCH user activity, since it's a pointer-type source. + notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(160)); + mFakePolicy->assertUserActivityNotPoked(); + + // 65ns > 50ns has passed since previous OTHER event. Should poke. + notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(200)); + mFakePolicy->assertUserActivityPoked( + {{milliseconds_to_nanoseconds(200), USER_ACTIVITY_EVENT_OTHER, ADISPLAY_ID_DEFAULT}}); + + // 170ns > 50ns has passed since previous TOUCH event. Should poke. + notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(300)); + mFakePolicy->assertUserActivityPoked( + {{milliseconds_to_nanoseconds(300), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}}); + + // Assert that there's no more user activity poke event. + mFakePolicy->assertUserActivityNotPoked(); +} + +TEST_F_WITH_FLAGS( + InputDispatcherUserActivityPokeTests, DefaultMinPokeTimeOf100MsUsed, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + rate_limit_user_activity_poke_in_dispatcher))) { + notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(200)); + mFakePolicy->assertUserActivityPoked( + {{milliseconds_to_nanoseconds(200), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}}); + + notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(280)); + mFakePolicy->assertUserActivityNotPoked(); + + notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(340)); + mFakePolicy->assertUserActivityPoked( + {{milliseconds_to_nanoseconds(340), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}}); +} + +TEST_F_WITH_FLAGS( + InputDispatcherUserActivityPokeTests, ZeroMinPokeTimeDisablesRateLimiting, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + rate_limit_user_activity_poke_in_dispatcher))) { + mDispatcher->setMinTimeBetweenUserActivityPokes(0ms); + + notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, 20); + mFakePolicy->assertUserActivityPoked(); + + notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, 30); + mFakePolicy->assertUserActivityPoked(); +} + class InputDispatcherOnPointerDownOutsideFocus : public InputDispatcherTest { virtual void SetUp() override { InputDispatcherTest::SetUp(); @@ -7530,8 +8307,7 @@ class InputDispatcherMultiWindowSameTokenTests : public InputDispatcherTest { ADISPLAY_ID_DEFAULT); mWindow1->setFrame(Rect(0, 0, 100, 100)); - mWindow2 = sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window 2", - ADISPLAY_ID_DEFAULT, mWindow1->getToken()); + mWindow2 = mWindow1->clone(ADISPLAY_ID_DEFAULT); mWindow2->setFrame(Rect(100, 100, 200, 200)); mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0}); @@ -7550,12 +8326,9 @@ protected: void consumeMotionEvent(const sp<FakeWindowHandle>& window, int32_t expectedAction, const std::vector<PointF>& points) { const std::string name = window->getName(); - MotionEvent* motionEvent = window->consumeMotion(); - - ASSERT_NE(nullptr, motionEvent) - << name.c_str() << ": consumer should have returned non-NULL event."; - - ASSERT_THAT(*motionEvent, WithMotionAction(expectedAction)); + std::unique_ptr<MotionEvent> motionEvent = + window->consumeMotionEvent(WithMotionAction(expectedAction)); + ASSERT_NE(nullptr, motionEvent); ASSERT_EQ(points.size(), motionEvent->getPointerCount()); for (size_t i = 0; i < points.size(); i++) { @@ -7571,13 +8344,13 @@ protected: } } - void touchAndAssertPositions(int32_t action, const std::vector<PointF>& touchedPoints, + void touchAndAssertPositions(sp<FakeWindowHandle> touchedWindow, int32_t action, + const std::vector<PointF>& touchedPoints, std::vector<PointF> expectedPoints) { mDispatcher->notifyMotion(generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, touchedPoints)); - // Always consume from window1 since it's the window that has the InputReceiver - consumeMotionEvent(mWindow1, action, expectedPoints); + consumeMotionEvent(touchedWindow, action, expectedPoints); } }; @@ -7585,15 +8358,15 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, SingleTouchSameScale) { // Touch Window 1 PointF touchedPoint = {10, 10}; PointF expectedPoint = getPointInWindow(mWindow1->getInfo(), touchedPoint); - touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint}); + touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint}); // Release touch on Window 1 - touchAndAssertPositions(AMOTION_EVENT_ACTION_UP, {touchedPoint}, {expectedPoint}); + touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_UP, {touchedPoint}, {expectedPoint}); // Touch Window 2 touchedPoint = {150, 150}; expectedPoint = getPointInWindow(mWindow2->getInfo(), touchedPoint); - touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint}); + touchAndAssertPositions(mWindow2, AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint}); } TEST_F(InputDispatcherMultiWindowSameTokenTests, SingleTouchDifferentTransform) { @@ -7604,21 +8377,21 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, SingleTouchDifferentTransform) // Touch Window 1 PointF touchedPoint = {10, 10}; PointF expectedPoint = getPointInWindow(mWindow1->getInfo(), touchedPoint); - touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint}); + touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint}); // Release touch on Window 1 - touchAndAssertPositions(AMOTION_EVENT_ACTION_UP, {touchedPoint}, {expectedPoint}); + touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_UP, {touchedPoint}, {expectedPoint}); // Touch Window 2 touchedPoint = {150, 150}; expectedPoint = getPointInWindow(mWindow2->getInfo(), touchedPoint); - touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint}); - touchAndAssertPositions(AMOTION_EVENT_ACTION_UP, {touchedPoint}, {expectedPoint}); + touchAndAssertPositions(mWindow2, AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint}); + touchAndAssertPositions(mWindow2, AMOTION_EVENT_ACTION_UP, {touchedPoint}, {expectedPoint}); // Update the transform so rotation is set mWindow2->setWindowTransform(0, -1, 1, 0); mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0}); expectedPoint = getPointInWindow(mWindow2->getInfo(), touchedPoint); - touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint}); + touchAndAssertPositions(mWindow2, AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint}); } TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchDifferentTransform) { @@ -7628,22 +8401,25 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchDifferentTransform // Touch Window 1 std::vector<PointF> touchedPoints = {PointF{10, 10}}; std::vector<PointF> expectedPoints = {getPointInWindow(mWindow1->getInfo(), touchedPoints[0])}; - touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints); + touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints); // Touch Window 2 + // Since this is part of the same touch gesture that has already been dispatched to Window 1, + // the touch stream from Window 2 will be merged with the stream in Window 1. The merged stream + // will continue to be dispatched through Window 1. touchedPoints.push_back(PointF{150, 150}); expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1])); - touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints); + touchAndAssertPositions(mWindow1, POINTER_1_DOWN, touchedPoints, expectedPoints); // Release Window 2 - touchAndAssertPositions(POINTER_1_UP, touchedPoints, expectedPoints); + touchAndAssertPositions(mWindow1, POINTER_1_UP, touchedPoints, expectedPoints); expectedPoints.pop_back(); // Update the transform so rotation is set for Window 2 mWindow2->setWindowTransform(0, -1, 1, 0); mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0}); expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1])); - touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints); + touchAndAssertPositions(mWindow1, POINTER_1_DOWN, touchedPoints, expectedPoints); } TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchMoveDifferentTransform) { @@ -7653,37 +8429,37 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchMoveDifferentTrans // Touch Window 1 std::vector<PointF> touchedPoints = {PointF{10, 10}}; std::vector<PointF> expectedPoints = {getPointInWindow(mWindow1->getInfo(), touchedPoints[0])}; - touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints); + touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints); // Touch Window 2 touchedPoints.push_back(PointF{150, 150}); expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1])); - touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints); + touchAndAssertPositions(mWindow1, POINTER_1_DOWN, touchedPoints, expectedPoints); // Move both windows touchedPoints = {{20, 20}, {175, 175}}; expectedPoints = {getPointInWindow(mWindow1->getInfo(), touchedPoints[0]), getPointInWindow(mWindow2->getInfo(), touchedPoints[1])}; - touchAndAssertPositions(AMOTION_EVENT_ACTION_MOVE, touchedPoints, expectedPoints); + touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_MOVE, touchedPoints, expectedPoints); // Release Window 2 - touchAndAssertPositions(POINTER_1_UP, touchedPoints, expectedPoints); + touchAndAssertPositions(mWindow1, POINTER_1_UP, touchedPoints, expectedPoints); expectedPoints.pop_back(); // Touch Window 2 mWindow2->setWindowTransform(0, -1, 1, 0); mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0}); expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1])); - touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints); + touchAndAssertPositions(mWindow1, POINTER_1_DOWN, touchedPoints, expectedPoints); // Move both windows touchedPoints = {{20, 20}, {175, 175}}; expectedPoints = {getPointInWindow(mWindow1->getInfo(), touchedPoints[0]), getPointInWindow(mWindow2->getInfo(), touchedPoints[1])}; - touchAndAssertPositions(AMOTION_EVENT_ACTION_MOVE, touchedPoints, expectedPoints); + touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_MOVE, touchedPoints, expectedPoints); } TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleWindowsFirstTouchWithScale) { @@ -7693,20 +8469,20 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleWindowsFirstTouchWithSc // Touch Window 1 std::vector<PointF> touchedPoints = {PointF{10, 10}}; std::vector<PointF> expectedPoints = {getPointInWindow(mWindow1->getInfo(), touchedPoints[0])}; - touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints); + touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints); // Touch Window 2 touchedPoints.push_back(PointF{150, 150}); expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1])); - touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints); + touchAndAssertPositions(mWindow1, POINTER_1_DOWN, touchedPoints, expectedPoints); // Move both windows touchedPoints = {{20, 20}, {175, 175}}; expectedPoints = {getPointInWindow(mWindow1->getInfo(), touchedPoints[0]), getPointInWindow(mWindow2->getInfo(), touchedPoints[1])}; - touchAndAssertPositions(AMOTION_EVENT_ACTION_MOVE, touchedPoints, expectedPoints); + touchAndAssertPositions(mWindow1, AMOTION_EVENT_ACTION_MOVE, touchedPoints, expectedPoints); } /** @@ -7750,7 +8526,7 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, HoverIntoClone) { .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(150)) .build()); consumeMotionEvent(mWindow1, ACTION_HOVER_EXIT, {{50, 50}}); - consumeMotionEvent(mWindow1, ACTION_HOVER_ENTER, + consumeMotionEvent(mWindow2, ACTION_HOVER_ENTER, {getPointInWindow(mWindow2->getInfo(), PointF{150, 150})}); } @@ -7855,7 +8631,7 @@ TEST_F(InputDispatcherSingleWindowAnr, OnPointerDown_BasicAnr) { injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, WINDOW_LOCATION)); - std::optional<uint32_t> sequenceNum = mWindow->receiveEvent(); // ACTION_DOWN + const auto [sequenceNum, _] = mWindow->receiveEvent(); // ACTION_DOWN ASSERT_TRUE(sequenceNum); const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow); @@ -7871,7 +8647,7 @@ TEST_F(InputDispatcherSingleWindowAnr, OnPointerDown_BasicAnr) { TEST_F(InputDispatcherSingleWindowAnr, OnKeyDown_BasicAnr) { // Inject a key, and don't respond - expect that ANR is called. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(*mDispatcher)); - std::optional<uint32_t> sequenceNum = mWindow->receiveEvent(); + const auto [sequenceNum, _] = mWindow->receiveEvent(); ASSERT_TRUE(sequenceNum); const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow); @@ -8031,7 +8807,7 @@ TEST_F(InputDispatcherSingleWindowAnr, SpyWindowAnr) { WINDOW_LOCATION)); mWindow->consumeMotionDown(); - std::optional<uint32_t> sequenceNum = spy->receiveEvent(); // ACTION_DOWN + const auto [sequenceNum, _] = spy->receiveEvent(); // ACTION_DOWN ASSERT_TRUE(sequenceNum); const std::chrono::duration timeout = spy->getDispatchingTimeout(DISPATCHING_TIMEOUT); mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, spy); @@ -8172,41 +8948,31 @@ TEST_F(InputDispatcherSingleWindowAnr, Policy_DoesNotGetDuplicateAnr) { /** * If a window is processing a motion event, and then a key event comes in, the key event should * not get delivered to the focused window until the motion is processed. - * - * Warning!!! - * This test depends on the value of android::inputdispatcher::KEY_WAITING_FOR_MOTION_TIMEOUT - * and the injection timeout that we specify when injecting the key. - * We must have the injection timeout (100ms) be smaller than - * KEY_WAITING_FOR_MOTION_TIMEOUT (currently 500ms). - * - * If that value changes, this test should also change. */ TEST_F(InputDispatcherSingleWindowAnr, Key_StaysPendingWhileMotionIsProcessed) { + // The timeouts in this test are established by relying on the fact that the "key waiting for + // events timeout" is equal to 500ms. + ASSERT_EQ(mFakePolicy->getKeyWaitingForEventsTimeout(), 500ms); mWindow->setDispatchingTimeout(2s); // Set a long ANR timeout to prevent it from triggering mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0}); tapOnWindow(); - std::optional<uint32_t> downSequenceNum = mWindow->receiveEvent(); + const auto& [downSequenceNum, downEvent] = mWindow->receiveEvent(); ASSERT_TRUE(downSequenceNum); - std::optional<uint32_t> upSequenceNum = mWindow->receiveEvent(); + const auto& [upSequenceNum, upEvent] = mWindow->receiveEvent(); ASSERT_TRUE(upSequenceNum); - // Don't finish the events yet, and send a key - // Injection will "succeed" because we will eventually give up and send the key to the focused - // window even if motions are still being processed. But because the injection timeout is short, - // we will receive INJECTION_TIMED_OUT as the result. - InputEventInjectionResult result = - injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::WAIT_FOR_RESULT, 100ms); - ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result); + // Don't finish the events yet, and send a key + mDispatcher->notifyKey( + KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD) + .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT) + .build()); // Key will not be sent to the window, yet, because the window is still processing events // and the key remains pending, waiting for the touch events to be processed // Make sure that `assertNoEvents` doesn't wait too long, because it could cause an ANR. - // Rely here on the fact that it uses CONSUME_TIMEOUT_NO_EVENT_EXPECTED under the hood. - static_assert(CONSUME_TIMEOUT_NO_EVENT_EXPECTED < 100ms); - mWindow->assertNoEvents(); + mWindow->assertNoEvents(100ms); - std::this_thread::sleep_for(500ms); + std::this_thread::sleep_for(400ms); // if we wait long enough though, dispatcher will give up, and still send the key // to the focused window, even though we have not yet finished the motion event mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT); @@ -8221,14 +8987,17 @@ TEST_F(InputDispatcherSingleWindowAnr, Key_StaysPendingWhileMotionIsProcessed) { * focused window right away. */ TEST_F(InputDispatcherSingleWindowAnr, - PendingKey_IsDroppedWhileMotionIsProcessedAndNewTouchComesIn) { + PendingKey_IsDeliveredWhileMotionIsProcessingAndNewTouchComesIn) { + // The timeouts in this test are established by relying on the fact that the "key waiting for + // events timeout" is equal to 500ms. + ASSERT_EQ(mFakePolicy->getKeyWaitingForEventsTimeout(), 500ms); mWindow->setDispatchingTimeout(2s); // Set a long ANR timeout to prevent it from triggering mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0}); tapOnWindow(); - std::optional<uint32_t> downSequenceNum = mWindow->receiveEvent(); + const auto& [downSequenceNum, _] = mWindow->receiveEvent(); ASSERT_TRUE(downSequenceNum); - std::optional<uint32_t> upSequenceNum = mWindow->receiveEvent(); + const auto& [upSequenceNum, upEvent] = mWindow->receiveEvent(); ASSERT_TRUE(upSequenceNum); // Don't finish the events yet, and send a key mDispatcher->notifyKey( @@ -8236,15 +9005,19 @@ TEST_F(InputDispatcherSingleWindowAnr, .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT) .build()); // At this point, key is still pending, and should not be sent to the application yet. - // Make sure the `assertNoEvents` check doesn't take too long. It uses - // CONSUME_TIMEOUT_NO_EVENT_EXPECTED under the hood. - static_assert(CONSUME_TIMEOUT_NO_EVENT_EXPECTED < 100ms); - mWindow->assertNoEvents(); + mWindow->assertNoEvents(100ms); // Now tap down again. It should cause the pending key to go to the focused window right away. tapOnWindow(); - mWindow->consumeKeyEvent(WithKeyAction(AKEY_EVENT_ACTION_DOWN)); // it doesn't matter that we - // haven't ack'd the other events yet. We can finish events in any order. + // Now that we tapped, we should receive the key immediately. + // Since there's still room for slowness, we use 200ms, which is much less than + // the "key waiting for events' timeout of 500ms minus the already waited 100ms duration. + std::unique_ptr<InputEvent> keyEvent = mWindow->consume(200ms); + ASSERT_NE(nullptr, keyEvent); + ASSERT_EQ(InputEventType::KEY, keyEvent->getType()); + ASSERT_THAT(static_cast<KeyEvent&>(*keyEvent), WithKeyAction(AKEY_EVENT_ACTION_DOWN)); + // it doesn't matter that we haven't ack'd the other events yet. We can finish events in any + // order. mWindow->finishEvent(*downSequenceNum); // first tap's ACTION_DOWN mWindow->finishEvent(*upSequenceNum); // first tap's ACTION_UP mWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); @@ -8264,7 +9037,7 @@ TEST_F(InputDispatcherSingleWindowAnr, TwoGesturesWithAnr) { .pointer(PointerBuilder(0, ToolType::FINGER).x(10).y(10)) .build()); - std::optional<uint32_t> sequenceNum = mWindow->receiveEvent(); // ACTION_DOWN + const auto [sequenceNum, _] = mWindow->receiveEvent(); // ACTION_DOWN ASSERT_TRUE(sequenceNum); const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow); @@ -8301,6 +9074,61 @@ TEST_F(InputDispatcherSingleWindowAnr, TwoGesturesWithAnr) { mWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); } +// Send an event to the app and have the app not respond right away. Then remove the app window. +// When the window is removed, the dispatcher will cancel the events for that window. +// So InputDispatcher will enqueue ACTION_CANCEL event as well. +TEST_F(InputDispatcherSingleWindowAnr, AnrAfterWindowRemoval) { + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {WINDOW_LOCATION})); + + const auto [sequenceNum, _] = mWindow->receiveEvent(); // ACTION_DOWN + ASSERT_TRUE(sequenceNum); + + // Remove the window, but the input channel should remain alive. + mDispatcher->onWindowInfosChanged({{}, {}, 0, 0}); + + const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); + // Since the window was removed, Dispatcher does not know the PID associated with the window + // anymore, so the policy is notified without the PID. + mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken(), + /*pid=*/std::nullopt); + + mWindow->finishEvent(*sequenceNum); + // The cancellation was generated when the window was removed, along with the focus event. + mWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); + mWindow->consumeFocusEvent(false); + ASSERT_TRUE(mDispatcher->waitForIdle()); + mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), /*pid=*/std::nullopt); +} + +// Send an event to the app and have the app not respond right away. Wait for the policy to be +// notified of the unresponsive window, then remove the app window. +TEST_F(InputDispatcherSingleWindowAnr, AnrFollowedByWindowRemoval) { + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {WINDOW_LOCATION})); + + const auto [sequenceNum, _] = mWindow->receiveEvent(); // ACTION_DOWN + ASSERT_TRUE(sequenceNum); + const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); + mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow); + + // Remove the window, but the input channel should remain alive. + mDispatcher->onWindowInfosChanged({{}, {}, 0, 0}); + + mWindow->finishEvent(*sequenceNum); + // The cancellation was generated during the ANR, and the window lost focus when it was removed. + mWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); + mWindow->consumeFocusEvent(false); + ASSERT_TRUE(mDispatcher->waitForIdle()); + // Since the window was removed, Dispatcher does not know the PID associated with the window + // becoming responsive, so the policy is notified without the PID. + mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), /*pid=*/std::nullopt); +} + class InputDispatcherMultiWindowAnr : public InputDispatcherTest { virtual void SetUp() override { InputDispatcherTest::SetUp(); @@ -8380,8 +9208,7 @@ TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsive) { .build())); mFocusedWindow->consumeMotionDown(); mFocusedWindow->consumeMotionUp(); - mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE, - ADISPLAY_ID_DEFAULT, /*flags=*/0); + mUnfocusedWindow->consumeMotionOutside(ADISPLAY_ID_DEFAULT, /*flags=*/0); // We consumed all events, so no ANR ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertNotifyAnrWasNotCalled(); @@ -8394,7 +9221,7 @@ TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsive) { .x(FOCUSED_WINDOW_LOCATION.x) .y(FOCUSED_WINDOW_LOCATION.y)) .build())); - std::optional<uint32_t> unfocusedSequenceNum = mUnfocusedWindow->receiveEvent(); + const auto [unfocusedSequenceNum, _] = mUnfocusedWindow->receiveEvent(); ASSERT_TRUE(unfocusedSequenceNum); const std::chrono::duration timeout = @@ -8457,12 +9284,11 @@ TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsiveWithSameTimeout // At the same time, FLAG_WATCH_OUTSIDE_TOUCH targets should not receive any events. TEST_F(InputDispatcherMultiWindowAnr, DuringAnr_SecondTapIsIgnored) { tapOnFocusedWindow(); - mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE, - ADISPLAY_ID_DEFAULT, /*flags=*/0); + mUnfocusedWindow->consumeMotionOutside(ADISPLAY_ID_DEFAULT, /*flags=*/0); // Receive the events, but don't respond - std::optional<uint32_t> downEventSequenceNum = mFocusedWindow->receiveEvent(); // ACTION_DOWN + const auto [downEventSequenceNum, downEvent] = mFocusedWindow->receiveEvent(); // ACTION_DOWN ASSERT_TRUE(downEventSequenceNum); - std::optional<uint32_t> upEventSequenceNum = mFocusedWindow->receiveEvent(); // ACTION_UP + const auto [upEventSequenceNum, upEvent] = mFocusedWindow->receiveEvent(); // ACTION_UP ASSERT_TRUE(upEventSequenceNum); const std::chrono::duration timeout = mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); @@ -8540,9 +9366,9 @@ TEST_F(InputDispatcherMultiWindowAnr, PendingKey_GoesToNewlyFocusedWindow) { {{*mFocusedWindow->getInfo(), *mUnfocusedWindow->getInfo()}, {}, 0, 0}); tapOnUnfocusedWindow(); - std::optional<uint32_t> downSequenceNum = mUnfocusedWindow->receiveEvent(); + const auto [downSequenceNum, downEvent] = mUnfocusedWindow->receiveEvent(); ASSERT_TRUE(downSequenceNum); - std::optional<uint32_t> upSequenceNum = mUnfocusedWindow->receiveEvent(); + const auto [upSequenceNum, upEvent] = mUnfocusedWindow->receiveEvent(); ASSERT_TRUE(upSequenceNum); // Don't finish the events yet, and send a key // Injection will succeed because we will eventually give up and send the key to the focused @@ -8590,8 +9416,7 @@ TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) { mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {FOCUSED_WINDOW_LOCATION})); - mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE, - ADISPLAY_ID_DEFAULT, /*flags=*/0); + mUnfocusedWindow->consumeMotionOutside(ADISPLAY_ID_DEFAULT, /*flags=*/0); // Touch Window 2 mDispatcher->notifyMotion( @@ -8606,8 +9431,7 @@ TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) { mFocusedWindow->consumeMotionDown(); // Focused window may or may not receive ACTION_MOVE // But it should definitely receive ACTION_CANCEL due to the ANR - InputEvent* event; - std::optional<int32_t> moveOrCancelSequenceNum = mFocusedWindow->receiveEvent(&event); + const auto [moveOrCancelSequenceNum, event] = mFocusedWindow->receiveEvent(); ASSERT_TRUE(moveOrCancelSequenceNum); mFocusedWindow->finishEvent(*moveOrCancelSequenceNum); ASSERT_NE(nullptr, event); @@ -8691,6 +9515,104 @@ TEST_F(InputDispatcherMultiWindowAnr, FocusedWindowWithoutSetFocusedApplication_ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyAnrWasNotCalled()); } +/** + * If we are pruning input queue, we should never drop pointer events. Otherwise, we risk having + * an inconsistent event stream inside the dispatcher. In this test, we make sure that the + * dispatcher doesn't prune pointer events incorrectly. + * + * This test reproduces a crash in InputDispatcher. + * To reproduce the crash, we need to simulate the conditions for "pruning input queue" to occur. + * + * Keep the currently focused application (mApplication), and have no focused window. + * We set up two additional windows: + * 1) The navigation bar window. This simulates the system "NavigationBar", which is used in the + * 3-button navigation mode. This window injects a BACK button when it's touched. 2) The application + * window. This window is not focusable, but is touchable. + * + * We first touch the navigation bar, which causes it to inject a key. Since there's no focused + * window, the dispatcher doesn't process this key, and all other events inside dispatcher are now + * blocked. The dispatcher is waiting for 'mApplication' to add a focused window. + * + * Now, we touch "Another window". This window is owned by a different application than + * 'mApplication'. This causes the dispatcher to stop waiting for 'mApplication' to add a focused + * window. Now, the "pruning input queue" behaviour should kick in, and the dispatcher should start + * dropping the events from its queue. Ensure that no crash occurs. + * + * In this test, we are setting long timeouts to prevent ANRs and events dropped due to being stale. + * This does not affect the test running time. + */ +TEST_F(InputDispatcherMultiWindowAnr, PruningInputQueueShouldNotDropPointerEvents) { + std::shared_ptr<FakeApplicationHandle> systemUiApplication = + std::make_shared<FakeApplicationHandle>(); + systemUiApplication->setDispatchingTimeout(3000ms); + mFakePolicy->setStaleEventTimeout(3000ms); + sp<FakeWindowHandle> navigationBar = + sp<FakeWindowHandle>::make(systemUiApplication, mDispatcher, "NavigationBar", + ADISPLAY_ID_DEFAULT); + navigationBar->setFocusable(false); + navigationBar->setWatchOutsideTouch(true); + navigationBar->setFrame(Rect(0, 0, 100, 100)); + + mApplication->setDispatchingTimeout(3000ms); + // 'mApplication' is already focused, but we call it again here to make it explicit. + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication); + + std::shared_ptr<FakeApplicationHandle> anotherApplication = + std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> appWindow = + sp<FakeWindowHandle>::make(anotherApplication, mDispatcher, "Another window", + ADISPLAY_ID_DEFAULT); + appWindow->setFocusable(false); + appWindow->setFrame(Rect(100, 100, 200, 200)); + + mDispatcher->onWindowInfosChanged( + {{*navigationBar->getInfo(), *appWindow->getInfo()}, {}, 0, 0}); + // 'mFocusedWindow' is no longer in the dispatcher window list, and therefore loses focus + mFocusedWindow->consumeFocusEvent(false); + + // Touch down the navigation bar. It consumes the touch and injects a key into the dispatcher + // in response. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .build()); + navigationBar->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + // Key will not be sent anywhere because we have no focused window. It will remain pending. + // Pretend we are injecting KEYCODE_BACK, but it doesn't actually matter what key it is. + InputEventInjectionResult result = + injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms, + /*allowKeyRepeat=*/false); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result); + + // Finish the gesture - lift up finger and inject ACTION_UP key event + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .build()); + result = injectKey(*mDispatcher, AKEY_EVENT_ACTION_UP, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms, + /*allowKeyRepeat=*/false); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result); + // The key that was injected is blocking the dispatcher, so the navigation bar shouldn't be + // getting any events yet. + navigationBar->assertNoEvents(); + + // Now touch "Another window". This touch is going to a different application than the one we + // are waiting for (which is 'mApplication'). + // This should cause the dispatcher to drop the pending focus-dispatched events (like the key + // trying to be injected) and to continue processing the rest of the events in the original + // order. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(150)) + .build()); + navigationBar->consumeMotionEvent(WithMotionAction(ACTION_UP)); + navigationBar->consumeMotionEvent(WithMotionAction(ACTION_OUTSIDE)); + appWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + appWindow->assertNoEvents(); + navigationBar->assertNoEvents(); +} + // These tests ensure we cannot send touch events to a window that's positioned behind a window // that has feature NO_INPUT_CHANNEL. // Layout: @@ -8706,7 +9628,7 @@ class InputDispatcherMultiWindowOcclusionTests : public InputDispatcherTest { mNoInputWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher, "Window without input channel", ADISPLAY_ID_DEFAULT, - /*token=*/std::make_optional<sp<IBinder>>(nullptr)); + /*createInputChannel=*/false); mNoInputWindow->setNoInputChannel(true); mNoInputWindow->setFrame(Rect(0, 0, 100, 100)); // It's perfectly valid for this window to not have an associated input channel @@ -8774,8 +9696,7 @@ protected: InputDispatcherTest::SetUp(); mApp = std::make_shared<FakeApplicationHandle>(); mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); - mMirror = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindowMirror", - ADISPLAY_ID_DEFAULT, mWindow->getToken()); + mMirror = mWindow->clone(ADISPLAY_ID_DEFAULT); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp); mWindow->setFocusable(true); mMirror->setFocusable(true); @@ -8799,7 +9720,7 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, CanGetFocus) { TEST_F(InputDispatcherMirrorWindowFocusTests, FocusedIfAllWindowsFocusable) { setFocusedWindow(mMirror); - // window gets focused + // window gets focused because it is above the mirror mWindow->consumeFocusEvent(true); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; @@ -8872,10 +9793,10 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, FocusedWhileWindowsAlive) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyDown(ADISPLAY_ID_NONE); + mMirror->consumeKeyDown(ADISPLAY_ID_NONE); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyUp(ADISPLAY_ID_NONE); + mMirror->consumeKeyUp(ADISPLAY_ID_NONE); // Both windows are removed mDispatcher->onWindowInfosChanged({{}, {}, 0, 0}); @@ -9603,7 +10524,7 @@ protected: /*isDragDrop=*/true); if (transferred) { mWindow->consumeMotionCancel(); - mDragWindow->consumeMotionDown(); + mDragWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); } return transferred; } @@ -9617,7 +10538,7 @@ TEST_F(InputDispatcherDragTests, DragEnterAndDragExit) { injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(false, 50, 50); mSecondWindow->assertNoEvents(); @@ -9626,7 +10547,7 @@ TEST_F(InputDispatcherDragTests, DragEnterAndDragExit) { injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(true, 150, 50); mSecondWindow->consumeDragEvent(false, 50, 50); @@ -9635,7 +10556,7 @@ TEST_F(InputDispatcherDragTests, DragEnterAndDragExit) { injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(false, 50, 50); mSecondWindow->consumeDragEvent(true, -50, 50); @@ -9643,7 +10564,7 @@ TEST_F(InputDispatcherDragTests, DragEnterAndDragExit) { injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); } @@ -9680,7 +10601,7 @@ TEST_F(InputDispatcherDragTests, DragAndDrop) { injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(false, 50, 50); mSecondWindow->assertNoEvents(); @@ -9689,7 +10610,7 @@ TEST_F(InputDispatcherDragTests, DragAndDrop) { injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(true, 150, 50); mSecondWindow->consumeDragEvent(false, 50, 50); @@ -9698,7 +10619,7 @@ TEST_F(InputDispatcherDragTests, DragAndDrop) { injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken()); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); @@ -9723,7 +10644,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropNotCancelledIfSomeOtherPointerIsPilf // Receives cancel for first pointer after next pointer down mSpyWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL)); - mSpyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + mSpyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithPointerIds({1}))); mDragWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); mSpyWindow->assertNoEvents(); @@ -9733,18 +10654,19 @@ TEST_F(InputDispatcherDragTests, DragAndDropNotCancelledIfSomeOtherPointerIsPilf mDragWindow->assertNoEvents(); const MotionEvent firstFingerMoveEvent = - MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + MotionEventBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(60).y(60)) .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(60).y(60)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, + injectMotionEvent(*mDispatcher, firstFingerMoveEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; // Drag window should still receive the new event - mDragWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + mDragWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE))); mDragWindow->assertNoEvents(); } @@ -9759,7 +10681,7 @@ TEST_F(InputDispatcherDragTests, StylusDragAndDrop) { .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) .build())) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(false, 50, 50); mSecondWindow->assertNoEvents(); @@ -9771,7 +10693,7 @@ TEST_F(InputDispatcherDragTests, StylusDragAndDrop) { .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50)) .build())) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken()); @@ -9784,7 +10706,7 @@ TEST_F(InputDispatcherDragTests, StylusDragAndDrop) { .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50)) .build())) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); } @@ -9802,7 +10724,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropOnInvalidWindow) { injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(false, 50, 50); mSecondWindow->assertNoEvents(); @@ -9811,7 +10733,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropOnInvalidWindow) { injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(true, 150, 50); mSecondWindow->assertNoEvents(); @@ -9820,7 +10742,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropOnInvalidWindow) { injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mFakePolicy->assertDropTargetEquals(*mDispatcher, nullptr); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); @@ -9889,7 +10811,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)); - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(false, 50, 50); mSecondWindow->consumeMotionMove(); @@ -9903,7 +10825,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)); - mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mFakePolicy->assertDropTargetEquals(*mDispatcher, mWindow->getToken()); mWindow->assertNoEvents(); mSecondWindow->consumeMotionMove(); @@ -9930,8 +10852,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenMultiDisplays) { .displayId(SECOND_DISPLAY_ID) .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) .build())); - windowInSecondary->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN, - SECOND_DISPLAY_ID, /*expectedFlag=*/0); + windowInSecondary->consumeMotionDown(SECOND_DISPLAY_ID, /*expectedFlag=*/0); // Update window again. mDispatcher->onWindowInfosChanged( {{*mDragWindow->getInfo(), *mSpyWindow->getInfo(), *mWindow->getInfo(), @@ -9945,7 +10866,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenMultiDisplays) { injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(false, 50, 50); mSecondWindow->assertNoEvents(); @@ -9954,7 +10875,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenMultiDisplays) { injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(true, 150, 50); mSecondWindow->consumeDragEvent(false, 50, 50); @@ -9963,7 +10884,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenMultiDisplays) { injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken()); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); @@ -9981,7 +10902,7 @@ TEST_F(InputDispatcherDragTests, MouseDragAndDrop) { .y(50)) .build())) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(false, 50, 50); mSecondWindow->assertNoEvents(); @@ -9995,7 +10916,7 @@ TEST_F(InputDispatcherDragTests, MouseDragAndDrop) { .y(50)) .build())) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(true, 150, 50); mSecondWindow->consumeDragEvent(false, 50, 50); @@ -10009,7 +10930,7 @@ TEST_F(InputDispatcherDragTests, MouseDragAndDrop) { .y(50)) .build())) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); + mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken()); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); @@ -10050,7 +10971,8 @@ TEST_F(InputDispatcherDragTests, DragAndDropFinishedWhenCancelCurrentTouch) { // Trigger cancel mDispatcher->cancelCurrentTouch(); ASSERT_NO_FATAL_FAILURE(mSecondWindow->consumeMotionCancel()); - ASSERT_NO_FATAL_FAILURE(mDragWindow->consumeMotionCancel()); + ASSERT_NO_FATAL_FAILURE(mDragWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)); ASSERT_NO_FATAL_FAILURE(mSpyWindow->consumeMotionCancel()); ASSERT_TRUE(mDispatcher->waitForIdle()); diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp index 36be684f96..2aecab98fd 100644 --- a/services/inputflinger/tests/InputMapperTest.cpp +++ b/services/inputflinger/tests/InputMapperTest.cpp @@ -19,12 +19,16 @@ #include <InputReaderBase.h> #include <gtest/gtest.h> #include <ui/Rotation.h> +#include <utils/Timers.h> + +#include "NotifyArgs.h" namespace android { +using testing::_; using testing::Return; -void InputMapperUnitTest::SetUp() { +void InputMapperUnitTest::SetUpWithBus(int bus) { mFakePointerController = std::make_shared<FakePointerController>(); mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); mFakePointerController->setPosition(INITIAL_CURSOR_X, INITIAL_CURSOR_Y); @@ -36,20 +40,30 @@ void InputMapperUnitTest::SetUp() { EXPECT_CALL(mMockInputReaderContext, getPolicy()).WillRepeatedly(Return(mFakePolicy.get())); EXPECT_CALL(mMockInputReaderContext, getEventHub()).WillRepeatedly(Return(&mMockEventHub)); - InputDeviceIdentifier identifier; - identifier.name = "device"; - identifier.location = "USB1"; - identifier.bus = 0; - EXPECT_CALL(mMockEventHub, getDeviceIdentifier(EVENTHUB_ID)).WillRepeatedly(Return(identifier)); + mIdentifier.name = "device"; + mIdentifier.location = "USB1"; + mIdentifier.bus = bus; + EXPECT_CALL(mMockEventHub, getDeviceIdentifier(EVENTHUB_ID)) + .WillRepeatedly(Return(mIdentifier)); + EXPECT_CALL(mMockEventHub, getConfiguration(EVENTHUB_ID)).WillRepeatedly([&](int32_t) { + return mPropertyMap; + }); +} + +void InputMapperUnitTest::createDevice() { mDevice = std::make_unique<InputDevice>(&mMockInputReaderContext, DEVICE_ID, - /*generation=*/2, identifier); + /*generation=*/2, mIdentifier); + mDevice->addEmptyEventHubDevice(EVENTHUB_ID); mDeviceContext = std::make_unique<InputDeviceContext>(*mDevice, EVENTHUB_ID); + std::list<NotifyArgs> args = + mDevice->configure(systemTime(), mReaderConfiguration, /*changes=*/{}); + ASSERT_THAT(args, testing::ElementsAre(testing::VariantWith<NotifyDeviceResetArgs>(_))); } void InputMapperUnitTest::setupAxis(int axis, bool valid, int32_t min, int32_t max, int32_t resolution) { - EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, testing::_)) + EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, _)) .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) { outAxisInfo->valid = valid; outAxisInfo->minValue = min; @@ -215,8 +229,8 @@ std::list<NotifyArgs> InputMapperTest::handleTimeout(InputMapper& mapper, nsecs_ return generatedArgs; } -void InputMapperTest::assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source, - float min, float max, float flat, float fuzz) { +void assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source, float min, + float max, float flat, float fuzz) { const InputDeviceInfo::MotionRange* range = info.getMotionRange(axis, source); ASSERT_TRUE(range != nullptr) << "Axis: " << axis << " Source: " << source; ASSERT_EQ(axis, range->axis) << "Axis: " << axis << " Source: " << source; @@ -227,11 +241,9 @@ void InputMapperTest::assertMotionRange(const InputDeviceInfo& info, int32_t axi ASSERT_NEAR(fuzz, range->fuzz, EPSILON) << "Axis: " << axis << " Source: " << source; } -void InputMapperTest::assertPointerCoords(const PointerCoords& coords, float x, float y, - float pressure, float size, float touchMajor, - float touchMinor, float toolMajor, float toolMinor, - float orientation, float distance, - float scaledAxisEpsilon) { +void assertPointerCoords(const PointerCoords& coords, float x, float y, float pressure, float size, + float touchMajor, float touchMinor, float toolMajor, float toolMinor, + float orientation, float distance, float scaledAxisEpsilon) { ASSERT_NEAR(x, coords.getAxisValue(AMOTION_EVENT_AXIS_X), scaledAxisEpsilon); ASSERT_NEAR(y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y), scaledAxisEpsilon); ASSERT_NEAR(pressure, coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), EPSILON); diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h index 05b0e97ff6..e176a65551 100644 --- a/services/inputflinger/tests/InputMapperTest.h +++ b/services/inputflinger/tests/InputMapperTest.h @@ -32,6 +32,7 @@ #include "InterfaceMocks.h" #include "TestConstants.h" #include "TestInputListener.h" +#include "input/PropertyMap.h" namespace android { @@ -41,7 +42,15 @@ protected: static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000; static constexpr float INITIAL_CURSOR_X = 400; static constexpr float INITIAL_CURSOR_Y = 240; - virtual void SetUp() override; + virtual void SetUp() override { SetUpWithBus(0); } + virtual void SetUpWithBus(int bus); + + /** + * Initializes mDevice and mDeviceContext. When this happens, mDevice takes a copy of + * mPropertyMap, so tests that need to set configuration properties should do so before calling + * this. Others will most likely want to call it in their SetUp method. + */ + void createDevice(); void setupAxis(int axis, bool valid, int32_t min, int32_t max, int32_t resolution); @@ -54,6 +63,7 @@ protected: std::list<NotifyArgs> process(int32_t type, int32_t code, int32_t value); std::list<NotifyArgs> process(nsecs_t when, int32_t type, int32_t code, int32_t value); + InputDeviceIdentifier mIdentifier; MockEventHubInterface mMockEventHub; sp<FakeInputReaderPolicy> mFakePolicy; std::shared_ptr<FakePointerController> mFakePointerController; @@ -64,6 +74,7 @@ protected: InputReaderConfiguration mReaderConfiguration; // The mapper should be created by the subclasses. std::unique_ptr<InputMapper> mMapper; + PropertyMap mPropertyMap; }; /** @@ -130,13 +141,13 @@ protected: void resetMapper(InputMapper& mapper, nsecs_t when); std::list<NotifyArgs> handleTimeout(InputMapper& mapper, nsecs_t when); - - static void assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source, - float min, float max, float flat, float fuzz); - static void assertPointerCoords(const PointerCoords& coords, float x, float y, float pressure, - float size, float touchMajor, float touchMinor, float toolMajor, - float toolMinor, float orientation, float distance, - float scaledAxisEpsilon = 1.f); }; +void assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source, float min, + float max, float flat, float fuzz); + +void assertPointerCoords(const PointerCoords& coords, float x, float y, float pressure, float size, + float touchMajor, float touchMinor, float toolMajor, float toolMinor, + float orientation, float distance, float scaledAxisEpsilon = 1.f); + } // namespace android diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index e0a3e94aad..476f755bae 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -97,8 +97,6 @@ static constexpr uint32_t STYLUS_FUSION_SOURCE = // Minimum timestamp separation between subsequent input events from a Bluetooth device. static constexpr nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4); -// Maximum smoothing time delta so that we don't generate events too far into the future. -constexpr static nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32); namespace input_flags = com::android::input::flags; @@ -1352,6 +1350,9 @@ protected: std::shared_ptr<FakePointerController> mFakePointerController; + constexpr static auto EVENT_HAPPENED_TIMEOUT = 2000ms; + constexpr static auto EVENT_DID_NOT_HAPPEN_TIMEOUT = 30ms; + void SetUp() override { #if !defined(__ANDROID__) GTEST_SKIP(); @@ -1383,8 +1384,8 @@ protected: } void setupInputReader() { - mTestListener = std::make_unique<TestInputListener>(/*eventHappenedTimeout=*/2000ms, - /*eventDidNotHappenTimeout=*/30ms); + mTestListener = std::make_unique<TestInputListener>(EVENT_HAPPENED_TIMEOUT, + EVENT_DID_NOT_HAPPEN_TIMEOUT); mReader = std::make_unique<InputReader>(std::make_shared<EventHub>(), mFakePolicy, *mTestListener); @@ -2415,17 +2416,29 @@ TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureNotReported) { mDevice->sendTrackingId(FIRST_TRACKING_ID); mDevice->sendToolType(MT_TOOL_FINGER); mDevice->sendDown(centerPoint); - auto waitUntil = std::chrono::system_clock::now() + - std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT)); + const auto syncTime = std::chrono::system_clock::now(); + // After 72 ms, the event *will* be generated. If we wait the full 72 ms to check that NO event + // is generated in that period, there will be a race condition between the event being generated + // and the test's wait timeout expiring. Thus, we wait for a shorter duration in the test, which + // will reduce the liklihood of the race condition occurring. + const auto waitUntilTimeForNoEvent = + syncTime + std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT / 2)); mDevice->sendSync(); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled(waitUntil)); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled(waitUntilTimeForNoEvent)); // Since the external stylus did not report a pressure value within the timeout, // it shows up as a finger pointer. - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS), - WithToolType(ToolType::FINGER), WithDeviceId(touchscreenId), WithPressure(1.f)))); + const auto waitUntilTimeForEvent = syncTime + + std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT)) + EVENT_HAPPENED_TIMEOUT; + ASSERT_NO_FATAL_FAILURE( + mTestListener->assertNotifyMotionWasCalled(AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_DOWN), + WithSource(AINPUT_SOURCE_TOUCHSCREEN | + AINPUT_SOURCE_STYLUS), + WithToolType(ToolType::FINGER), + WithDeviceId(touchscreenId), + WithPressure(1.f)), + waitUntilTimeForEvent)); // Change the pressure on the external stylus. Since the pressure was not present at the start // of the gesture, it is ignored for now. @@ -2693,6 +2706,31 @@ TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRe ASSERT_NO_FATAL_FAILURE(mapper2.assertProcessWasCalled()); } +TEST_F(InputDeviceTest, Configure_SmoothScrollViewBehaviorNotSet) { + // Set some behavior to force the configuration to be update. + mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "device.wake", "1"); + mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + + std::list<NotifyArgs> unused = + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + /*changes=*/{}); + + ASSERT_FALSE(mDevice->getDeviceInfo().getViewBehavior().shouldSmoothScroll.has_value()); +} + +TEST_F(InputDeviceTest, Configure_SmoothScrollViewBehaviorEnabled) { + mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "device.viewBehavior_smoothScroll", "1"); + mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + + std::list<NotifyArgs> unused = + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + /*changes=*/{}); + + ASSERT_TRUE(mDevice->getDeviceInfo().getViewBehavior().shouldSmoothScroll.value_or(false)); +} + TEST_F(InputDeviceTest, WakeDevice_AddsWakeFlagToProcessNotifyArgs) { mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "device.wake", "1"); FakeInputMapper& mapper = @@ -2855,9 +2893,12 @@ TEST_F(InputDeviceTest, Configure_UniqueId_CorrectlyMatches) { mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); + const auto initialGeneration = mDevice->getGeneration(); unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), InputReaderConfiguration::Change::DISPLAY_INFO); ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueId()); + ASSERT_GT(mDevice->getGeneration(), initialGeneration); + ASSERT_EQ(mDevice->getDeviceInfo().getAssociatedDisplayId(), SECONDARY_DISPLAY_ID); } /** @@ -2894,9 +2935,9 @@ TEST_F(InputDeviceTest, KernelBufferOverflowResetsMappers) { mapper.assertConfigureWasCalled(); mapper.assertResetWasNotCalled(); - RawEvent event{.deviceId = EVENTHUB_ID, + RawEvent event{.when = ARBITRARY_TIME, .readTime = ARBITRARY_TIME, - .when = ARBITRARY_TIME, + .deviceId = EVENTHUB_ID, .type = EV_SYN, .code = SYN_REPORT, .value = 0}; @@ -2906,13 +2947,11 @@ TEST_F(InputDeviceTest, KernelBufferOverflowResetsMappers) { mapper.assertProcessWasCalled(); // Simulate a kernel buffer overflow, which generates a SYN_DROPPED event. - // This should reset the mapper. event.type = EV_SYN; event.code = SYN_DROPPED; event.value = 0; unused = mDevice->process(&event, /*count=*/1); mapper.assertProcessWasNotCalled(); - mapper.assertResetWasCalled(); // All events until the next SYN_REPORT should be dropped. event.type = EV_KEY; @@ -2922,11 +2961,13 @@ TEST_F(InputDeviceTest, KernelBufferOverflowResetsMappers) { mapper.assertProcessWasNotCalled(); // We get the SYN_REPORT event now, which is not forwarded to mappers. + // This should reset the mapper. event.type = EV_SYN; event.code = SYN_REPORT; event.value = 0; unused = mDevice->process(&event, /*count=*/1); mapper.assertProcessWasNotCalled(); + mapper.assertResetWasCalled(); // The mapper receives events normally now. event.type = EV_KEY; @@ -4087,7 +4128,7 @@ protected: void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL); } }; -TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior) { +TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior_AlphabeticKeyboard) { // For external devices, keys will trigger wake on key down. Media keys should also trigger // wake if triggered from external devices. @@ -4126,6 +4167,36 @@ TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior) { ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); } +TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior_NoneAlphabeticKeyboard) { + // For external devices, keys will trigger wake on key down. Media keys should not trigger + // wake if triggered from external non-alphaebtic keyboard (e.g. headsets). + + mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAY, 0, AKEYCODE_MEDIA_PLAY, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAYPAUSE, 0, AKEYCODE_MEDIA_PLAY_PAUSE, + POLICY_FLAG_WAKE); + + KeyboardInputMapper& mapper = + constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC); + + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAY, 1); + NotifyKeyArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(uint32_t(0), args.policyFlags); + + process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAY, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(uint32_t(0), args.policyFlags); + + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); + + process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); +} + TEST_F(KeyboardInputMapperTest_ExternalDevice, DoNotWakeByDefaultBehavior) { // Tv Remote key's wake behavior is prescribed by the keylayout file. @@ -4164,1492 +4235,6 @@ TEST_F(KeyboardInputMapperTest_ExternalDevice, DoNotWakeByDefaultBehavior) { ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); } -// --- CursorInputMapperTestBase --- - -class CursorInputMapperTestBase : public InputMapperTest { -protected: - static const int32_t TRACKBALL_MOVEMENT_THRESHOLD; - - std::shared_ptr<FakePointerController> mFakePointerController; - - void SetUp() override { - InputMapperTest::SetUp(); - - mFakePointerController = std::make_shared<FakePointerController>(); - mFakePolicy->setPointerController(mFakePointerController); - } - - void testMotionRotation(CursorInputMapper& mapper, int32_t originalX, int32_t originalY, - int32_t rotatedX, int32_t rotatedY); - - void prepareDisplay(ui::Rotation orientation) { - setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation, - DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); - } - - void prepareSecondaryDisplay() { - setDisplayInfoAndReconfigure(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - ui::ROTATION_0, SECONDARY_DISPLAY_UNIQUE_ID, NO_PORT, - ViewportType::EXTERNAL); - } - - static void assertCursorPointerCoords(const PointerCoords& coords, float x, float y, - float pressure) { - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(coords, x, y, pressure, 0.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 0.0f, EPSILON)); - } -}; - -const int32_t CursorInputMapperTestBase::TRACKBALL_MOVEMENT_THRESHOLD = 6; - -void CursorInputMapperTestBase::testMotionRotation(CursorInputMapper& mapper, int32_t originalX, - int32_t originalY, int32_t rotatedX, - int32_t rotatedY) { - NotifyMotionArgs args; - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, originalX); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, originalY); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(args.pointerCoords[0], - float(rotatedX) / TRACKBALL_MOVEMENT_THRESHOLD, - float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f)); -} - -// --- CursorInputMapperTest --- - -class CursorInputMapperTest : public CursorInputMapperTestBase { -protected: - void SetUp() override { - input_flags::enable_pointer_choreographer(false); - CursorInputMapperTestBase::SetUp(); - } -}; - -TEST_F(CursorInputMapperTest, WhenModeIsPointer_GetSources_ReturnsMouse) { - addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); -} - -TEST_F(CursorInputMapperTest, WhenModeIsNavigation_GetSources_ReturnsTrackball) { - addConfigurationProperty("cursor.mode", "navigation"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, mapper.getSources()); -} - -TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeFromPointerController) { - addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - InputDeviceInfo info; - mapper.populateDeviceInfo(info); - - // Initially there may not be a valid motion range. - ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE)); - ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE)); - ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, - AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f)); - - // When the bounds are set, then there should be a valid motion range. - mFakePointerController->setBounds(1, 2, 800 - 1, 480 - 1); - configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - - InputDeviceInfo info2; - mapper.populateDeviceInfo(info2); - - ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, - AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, - 1, 800 - 1, 0.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, - AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, - 2, 480 - 1, 0.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, - AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_MOUSE, - 0.0f, 1.0f, 0.0f, 0.0f)); -} - -TEST_F(CursorInputMapperTest, WhenModeIsNavigation_PopulateDeviceInfo_ReturnsScaledRange) { - addConfigurationProperty("cursor.mode", "navigation"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - InputDeviceInfo info; - mapper.populateDeviceInfo(info); - - ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, - AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_TRACKBALL, - -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD)); - ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, - AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_TRACKBALL, - -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD)); - ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, - AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_TRACKBALL, - 0.0f, 1.0f, 0.0f, 0.0f)); -} - -TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaState) { - addConfigurationProperty("cursor.mode", "navigation"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); - - NotifyMotionArgs args; - - // Button press. - // Mostly testing non x/y behavior here so we don't need to check again elsewhere. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(ARBITRARY_TIME, args.eventTime); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source); - ASSERT_EQ(uint32_t(0), args.policyFlags); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); - ASSERT_EQ(0, args.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, args.buttonState); - ASSERT_EQ(0, args.edgeFlags); - ASSERT_EQ(uint32_t(1), args.getPointerCount()); - ASSERT_EQ(0, args.pointerProperties[0].id); - ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f)); - ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision); - ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision); - ASSERT_EQ(ARBITRARY_TIME, args.downTime); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(ARBITRARY_TIME, args.eventTime); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source); - ASSERT_EQ(uint32_t(0), args.policyFlags); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action); - ASSERT_EQ(0, args.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, args.buttonState); - ASSERT_EQ(0, args.edgeFlags); - ASSERT_EQ(uint32_t(1), args.getPointerCount()); - ASSERT_EQ(0, args.pointerProperties[0].id); - ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f)); - ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision); - ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision); - ASSERT_EQ(ARBITRARY_TIME, args.downTime); - - // Button release. Should have same down time. - process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, BTN_MOUSE, 0); - process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source); - ASSERT_EQ(uint32_t(0), args.policyFlags); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action); - ASSERT_EQ(0, args.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); - ASSERT_EQ(0, args.buttonState); - ASSERT_EQ(0, args.edgeFlags); - ASSERT_EQ(uint32_t(1), args.getPointerCount()); - ASSERT_EQ(0, args.pointerProperties[0].id); - ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision); - ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision); - ASSERT_EQ(ARBITRARY_TIME, args.downTime); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source); - ASSERT_EQ(uint32_t(0), args.policyFlags); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); - ASSERT_EQ(0, args.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); - ASSERT_EQ(0, args.buttonState); - ASSERT_EQ(0, args.edgeFlags); - ASSERT_EQ(uint32_t(1), args.getPointerCount()); - ASSERT_EQ(0, args.pointerProperties[0].id); - ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision); - ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision); - ASSERT_EQ(ARBITRARY_TIME, args.downTime); -} - -TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentXYUpdates) { - addConfigurationProperty("cursor.mode", "navigation"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - NotifyMotionArgs args; - - // Motion in X but not Y. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], - 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f, - 0.0f)); - - // Motion in Y but not X. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, -2); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, - -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f)); -} - -TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) { - addConfigurationProperty("cursor.mode", "navigation"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - NotifyMotionArgs args; - - // Button press. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f)); - - // Button release. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); -} - -TEST_F(CursorInputMapperTest, Process_ShouldHandleCombinedXYAndButtonUpdates) { - addConfigurationProperty("cursor.mode", "navigation"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - NotifyMotionArgs args; - - // Combined X, Y and Button. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, -2); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], - 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, - -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], - 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, - -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f)); - - // Move X, Y a bit while pressed. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 2); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], - 2.0f / TRACKBALL_MOVEMENT_THRESHOLD, - 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f)); - - // Release Button. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); -} - -TEST_F(CursorInputMapperTest, Process_WhenOrientationAware_ShouldNotRotateMotions) { - mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); - addConfigurationProperty("cursor.mode", "navigation"); - // InputReader works in the un-rotated coordinate space, so orientation-aware devices do not - // need to be rotated. - addConfigurationProperty("cursor.orientationAware", "1"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - prepareDisplay(ui::ROTATION_90); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, 1, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 0, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, -1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, 1)); -} - -TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotions) { - mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); - addConfigurationProperty("cursor.mode", "navigation"); - // Since InputReader works in the un-rotated coordinate space, only devices that are not - // orientation-aware are affected by display rotation. - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - clearViewports(); - prepareDisplay(ui::ROTATION_0); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, 1, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 0, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, -1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, 1)); - - clearViewports(); - prepareDisplay(ui::ROTATION_90); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, -1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, -1, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 0, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, 1, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, 1, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 0, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, -1)); - - clearViewports(); - prepareDisplay(ui::ROTATION_180); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, -1, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, -1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, -1, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 0, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, 1, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, 1, -1)); - - clearViewports(); - prepareDisplay(ui::ROTATION_270); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 0, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, -1, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, -1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 0, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, 1, 1)); -} - -TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { - addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); - mFakePointerController->setPosition(100, 200); - - NotifyMotionArgs motionArgs; - NotifyKeyArgs keyArgs; - - // press BTN_LEFT, release BTN_LEFT - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, - motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, - motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - // press BTN_BACK, release BTN_BACK - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); - ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); - ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); - - // press BTN_SIDE, release BTN_SIDE - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); - ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); - ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); - - // press BTN_FORWARD, release BTN_FORWARD - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); - ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); - ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); - - // press BTN_EXTRA, release BTN_EXTRA - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); - ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); - ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); -} - -TEST_F(CursorInputMapperTest, Process_WhenModeIsPointer_ShouldMoveThePointerAround) { - addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); - mFakePointerController->setPosition(100, 200); - - NotifyMotionArgs args; - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], - 110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f)); -} - -/** - * When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any - * pointer acceleration or speed processing should not be applied. - */ -TEST_F(CursorInputMapperTest, PointerCaptureDisablesVelocityProcessing) { - addConfigurationProperty("cursor.mode", "pointer"); - const VelocityControlParameters testParams(/*scale=*/5.f, /*low threshold=*/0.f, - /*high threshold=*/100.f, /*acceleration=*/10.f); - mFakePolicy->setVelocityControlParams(testParams); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - NotifyDeviceResetArgs resetArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime); - ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); - - NotifyMotionArgs args; - - // Move and verify scale is applied. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); - const float relX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); - const float relY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); - ASSERT_GT(relX, 10); - ASSERT_GT(relY, 20); - - // Enable Pointer Capture - mFakePolicy->setPointerCapture(true); - configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE); - NotifyPointerCaptureChangedArgs captureArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs)); - ASSERT_TRUE(captureArgs.request.enable); - - // Move and verify scale is not applied. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_EQ(10, args.pointerCoords[0].getX()); - ASSERT_EQ(20, args.pointerCoords[0].getY()); -} - -TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) { - addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - NotifyDeviceResetArgs resetArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime); - ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); - - // Ensure the display is rotated. - prepareDisplay(ui::ROTATION_90); - - NotifyMotionArgs args; - - // Verify that the coordinates are rotated. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); - ASSERT_EQ(-20, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X)); - ASSERT_EQ(10, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y)); - - // Enable Pointer Capture. - mFakePolicy->setPointerCapture(true); - configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE); - NotifyPointerCaptureChangedArgs captureArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs)); - ASSERT_TRUE(captureArgs.request.enable); - - // Move and verify rotation is not applied. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_EQ(10, args.pointerCoords[0].getX()); - ASSERT_EQ(20, args.pointerCoords[0].getY()); -} - -TEST_F(CursorInputMapperTest, ConfigureDisplayId_NoAssociatedViewport) { - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - // Set up the default display. - prepareDisplay(ui::ROTATION_90); - - // Set up the secondary display as the display on which the pointer should be shown. - // The InputDevice is not associated with any display. - prepareSecondaryDisplay(); - mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); - configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - - mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); - mFakePointerController->setPosition(100, 200); - - // Ensure input events are generated for the secondary display. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID), - WithCoords(110.0f, 220.0f)))); - ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f)); -} - -TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) { - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - // Set up the default display. - prepareDisplay(ui::ROTATION_90); - - // Set up the secondary display as the display on which the pointer should be shown, - // and associate the InputDevice with the secondary display. - prepareSecondaryDisplay(); - mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); - mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID); - configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - - mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); - mFakePointerController->setPosition(100, 200); - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID), - WithCoords(110.0f, 220.0f)))); - ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f)); -} - -TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPointerDisplay) { - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - // Set up the default display as the display on which the pointer should be shown. - prepareDisplay(ui::ROTATION_90); - mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); - - // Associate the InputDevice with the secondary display. - prepareSecondaryDisplay(); - mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID); - configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - - // The mapper should not generate any events because it is associated with a display that is - // different from the pointer display. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); -} - -// --- CursorInputMapperTestWithChoreographer --- - -// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging -// logic can be removed. -class CursorInputMapperTestWithChoreographer : public CursorInputMapperTestBase { -protected: - void SetUp() override { - input_flags::enable_pointer_choreographer(true); - CursorInputMapperTestBase::SetUp(); - } -}; - -TEST_F(CursorInputMapperTestWithChoreographer, PopulateDeviceInfoReturnsRangeFromPolicy) { - addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - InputDeviceInfo info; - mapper.populateDeviceInfo(info); - - // Initially there may not be a valid motion range. - ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE)); - ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE)); - ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE, - AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f)); - - // When the viewport and the default pointer display ID is set, then there should be a valid - // motion range. - mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); - mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, - /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL); - configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - - InputDeviceInfo info2; - mapper.populateDeviceInfo(info2); - - ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, 0, - DISPLAY_WIDTH - 1, 0.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, 0, - DISPLAY_HEIGHT - 1, 0.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_PRESSURE, - AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f)); -} - -TEST_F(CursorInputMapperTestWithChoreographer, ProcessShouldHandleAllButtonsWithZeroCoords) { - addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); - prepareDisplay(ui::ROTATION_0); - - mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); - mFakePointerController->setPosition(100, 200); - - NotifyMotionArgs motionArgs; - NotifyKeyArgs keyArgs; - - // press BTN_LEFT, release BTN_LEFT - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f)); - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, - motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, - motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f)); - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f)); - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - // press BTN_BACK, release BTN_BACK - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); - ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); - ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); - - // press BTN_SIDE, release BTN_SIDE - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); - ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); - ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); - - // press BTN_FORWARD, release BTN_FORWARD - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); - ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); - ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); - - // press BTN_EXTRA, release BTN_EXTRA - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); - ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); - ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); -} - -TEST_F(CursorInputMapperTestWithChoreographer, ProcessWhenModeIsPointerShouldKeepZeroCoords) { - addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); - prepareDisplay(ui::ROTATION_0); - - mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); - mFakePointerController->setPosition(100, 200); - - NotifyMotionArgs args; - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); -} - -TEST_F(CursorInputMapperTestWithChoreographer, PointerCaptureDisablesVelocityProcessing) { - addConfigurationProperty("cursor.mode", "pointer"); - const VelocityControlParameters testParams(/*scale=*/5.f, /*lowThreshold=*/0.f, - /*highThreshold=*/100.f, /*acceleration=*/10.f); - mFakePolicy->setVelocityControlParams(testParams); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); - prepareDisplay(ui::ROTATION_0); - - NotifyDeviceResetArgs resetArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime); - ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); - - NotifyMotionArgs args; - - // Move and verify scale is applied. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); - const float relX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); - const float relY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); - ASSERT_GT(relX, 10); - ASSERT_GT(relY, 20); - - // Enable Pointer Capture - mFakePolicy->setPointerCapture(true); - configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE); - NotifyPointerCaptureChangedArgs captureArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs)); - ASSERT_TRUE(captureArgs.request.enable); - - // Move and verify scale is not applied. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - const float relX2 = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); - const float relY2 = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); - ASSERT_EQ(10, relX2); - ASSERT_EQ(20, relY2); -} - -TEST_F(CursorInputMapperTestWithChoreographer, ConfigureDisplayIdNoAssociatedViewport) { - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - // Set up the default display. - prepareDisplay(ui::ROTATION_90); - - // Set up the secondary display as the display on which the pointer should be shown. - // The InputDevice is not associated with any display. - prepareSecondaryDisplay(); - mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); - configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - - mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); - mFakePointerController->setPosition(100, 200); - - // Ensure input events are generated without display ID and coords, - // because they will be decided later by PointerChoreographer. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(ADISPLAY_ID_NONE), - WithCoords(0.0f, 0.0f)))); -} - -TEST_F(CursorInputMapperTestWithChoreographer, ConfigureDisplayIdWithAssociatedViewport) { - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - // Set up the default display. - prepareDisplay(ui::ROTATION_90); - - // Set up the secondary display as the display on which the pointer should be shown, - // and associate the InputDevice with the secondary display. - prepareSecondaryDisplay(); - mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); - mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID); - configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - - mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); - mFakePointerController->setPosition(100, 200); - - // Ensure input events are generated with associated display ID but not with coords, - // because the coords will be decided later by PointerChoreographer. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID), - WithCoords(0.0f, 0.0f)))); -} - -TEST_F(CursorInputMapperTestWithChoreographer, - ConfigureDisplayIdShouldGenerateEventWithMismatchedPointerDisplay) { - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - // Set up the default display as the display on which the pointer should be shown. - prepareDisplay(ui::ROTATION_90); - mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); - - // Associate the InputDevice with the secondary display. - prepareSecondaryDisplay(); - mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID); - configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - - // With PointerChoreographer enabled, there could be a PointerController for the associated - // display even if it is different from the pointer display. So the mapper should generate an - // event. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID), - WithCoords(0.0f, 0.0f)))); -} - -// --- BluetoothCursorInputMapperTest --- - -class BluetoothCursorInputMapperTest : public CursorInputMapperTestBase { -protected: - void SetUp() override { - input_flags::enable_pointer_choreographer(false); - InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH); - - mFakePointerController = std::make_shared<FakePointerController>(); - mFakePolicy->setPointerController(mFakePointerController); - } -}; - -TEST_F(BluetoothCursorInputMapperTest, TimestampSmoothening) { - addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - nsecs_t kernelEventTime = ARBITRARY_TIME; - nsecs_t expectedEventTime = ARBITRARY_TIME; - process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); - process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithEventTime(expectedEventTime)))); - - // Process several events that come in quick succession, according to their timestamps. - for (int i = 0; i < 3; i++) { - constexpr static nsecs_t delta = ms2ns(1); - static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA); - kernelEventTime += delta; - expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; - - process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); - process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithEventTime(expectedEventTime)))); - } -} - -TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningIsCapped) { - addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - nsecs_t expectedEventTime = ARBITRARY_TIME; - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithEventTime(expectedEventTime)))); - - // Process several events with the same timestamp from the kernel. - // Ensure that we do not generate events too far into the future. - constexpr static int32_t numEvents = - MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA; - for (int i = 0; i < numEvents; i++) { - expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithEventTime(expectedEventTime)))); - } - - // By processing more events with the same timestamp, we should not generate events with a - // timestamp that is more than the specified max time delta from the timestamp at its injection. - const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA; - for (int i = 0; i < 3; i++) { - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithEventTime(cappedEventTime)))); - } -} - -TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningNotUsed) { - addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - nsecs_t kernelEventTime = ARBITRARY_TIME; - nsecs_t expectedEventTime = ARBITRARY_TIME; - process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); - process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithEventTime(expectedEventTime)))); - - // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp - // smoothening is not needed, its timestamp is not affected. - kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1); - expectedEventTime = kernelEventTime; - - process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); - process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithEventTime(expectedEventTime)))); -} - -// --- BluetoothCursorInputMapperTestWithChoreographer --- - -class BluetoothCursorInputMapperTestWithChoreographer : public CursorInputMapperTestBase { -protected: - void SetUp() override { - input_flags::enable_pointer_choreographer(true); - InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH); - - mFakePointerController = std::make_shared<FakePointerController>(); - mFakePolicy->setPointerController(mFakePointerController); - } -}; - -TEST_F(BluetoothCursorInputMapperTestWithChoreographer, TimestampSmoothening) { - addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - // Set up the default display. - prepareDisplay(ui::ROTATION_0); - mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); - configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - - nsecs_t kernelEventTime = ARBITRARY_TIME; - nsecs_t expectedEventTime = ARBITRARY_TIME; - process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); - process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithEventTime(expectedEventTime)))); - - // Process several events that come in quick succession, according to their timestamps. - for (int i = 0; i < 3; i++) { - constexpr static nsecs_t delta = ms2ns(1); - static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA); - kernelEventTime += delta; - expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; - - process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); - process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithEventTime(expectedEventTime)))); - } -} - -TEST_F(BluetoothCursorInputMapperTestWithChoreographer, TimestampSmootheningIsCapped) { - addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - // Set up the default display. - prepareDisplay(ui::ROTATION_0); - mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); - configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - - nsecs_t expectedEventTime = ARBITRARY_TIME; - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithEventTime(expectedEventTime)))); - - // Process several events with the same timestamp from the kernel. - // Ensure that we do not generate events too far into the future. - constexpr static int32_t numEvents = - MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA; - for (int i = 0; i < numEvents; i++) { - expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithEventTime(expectedEventTime)))); - } - - // By processing more events with the same timestamp, we should not generate events with a - // timestamp that is more than the specified max time delta from the timestamp at its injection. - const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA; - for (int i = 0; i < 3; i++) { - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithEventTime(cappedEventTime)))); - } -} - -TEST_F(BluetoothCursorInputMapperTestWithChoreographer, TimestampSmootheningNotUsed) { - addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); - - // Set up the default display. - prepareDisplay(ui::ROTATION_0); - mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); - configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - - nsecs_t kernelEventTime = ARBITRARY_TIME; - nsecs_t expectedEventTime = ARBITRARY_TIME; - process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); - process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithEventTime(expectedEventTime)))); - - // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp - // smoothening is not needed, its timestamp is not affected. - kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1); - expectedEventTime = kernelEventTime; - - process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); - process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithEventTime(expectedEventTime)))); -} - // --- TouchInputMapperTest --- class TouchInputMapperTest : public InputMapperTest { @@ -10971,15 +9556,16 @@ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) { ASSERT_EQ(uint32_t(1), motionArgs.getPointerCount()); } -TEST_F(MultiTouchInputMapperTest, ResetClearsTouchState) { +TEST_F(MultiTouchInputMapperTest, Reset_RepopulatesMultiTouchState) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>(); // First finger down. + constexpr int32_t x1 = 100, y1 = 200, x2 = 300, y2 = 400; processId(mapper, FIRST_TRACKING_ID); - processPosition(mapper, 100, 200); + processPosition(mapper, x1, y1); processPressure(mapper, RAW_PRESSURE_MAX); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( @@ -10988,42 +9574,49 @@ TEST_F(MultiTouchInputMapperTest, ResetClearsTouchState) { // Second finger down. processSlot(mapper, SECOND_SLOT); processId(mapper, SECOND_TRACKING_ID); - processPosition(mapper, 300, 400); + processPosition(mapper, x2, y2); processPressure(mapper, RAW_PRESSURE_MAX); processSync(mapper); ASSERT_NO_FATAL_FAILURE( mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(ACTION_POINTER_1_DOWN))); - // Reset the mapper. When the mapper is reset, the touch state is also cleared. + // Set MT Slot state to be repopulated for the required slots + std::vector<int32_t> mtSlotValues(RAW_SLOT_MAX + 1, -1); + mtSlotValues[0] = FIRST_TRACKING_ID; + mtSlotValues[1] = SECOND_TRACKING_ID; + mFakeEventHub->setMtSlotValues(EVENTHUB_ID, ABS_MT_TRACKING_ID, mtSlotValues); + + mtSlotValues[0] = x1; + mtSlotValues[1] = x2; + mFakeEventHub->setMtSlotValues(EVENTHUB_ID, ABS_MT_POSITION_X, mtSlotValues); + + mtSlotValues[0] = y1; + mtSlotValues[1] = y2; + mFakeEventHub->setMtSlotValues(EVENTHUB_ID, ABS_MT_POSITION_Y, mtSlotValues); + + mtSlotValues[0] = RAW_PRESSURE_MAX; + mtSlotValues[1] = RAW_PRESSURE_MAX; + mFakeEventHub->setMtSlotValues(EVENTHUB_ID, ABS_MT_PRESSURE, mtSlotValues); + + // Reset the mapper. When the mapper is reset, we expect the current multi-touch state to be + // repopulated. Resetting should cancel the ongoing gesture. resetMapper(mapper, ARBITRARY_TIME); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( WithMotionAction(AMOTION_EVENT_ACTION_CANCEL))); - // Move the second slot pointer, and ensure there are no events, because the touch state was - // cleared and no slots should be in use. + // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use + // the existing touch state to generate a down event. processPosition(mapper, 301, 302); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); - - // Release both fingers. - processId(mapper, INVALID_TRACKING_ID); - processSlot(mapper, FIRST_SLOT); - processId(mapper, INVALID_TRACKING_ID); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); - - // Start a new gesture, and ensure we get a DOWN event for it. - processId(mapper, FIRST_TRACKING_ID); - processPosition(mapper, 200, 300); - processPressure(mapper, RAW_PRESSURE_MAX); - processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPressure(1.f)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(ACTION_POINTER_1_DOWN), WithPressure(1.f)))); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } -TEST_F(MultiTouchInputMapperTest, ResetClearsTouchStateWithNoPointersDown) { +TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState_NoPointersDown) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); @@ -11151,66 +9744,6 @@ TEST_F(MultiTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirect ASSERT_FALSE(fakePointerController->isPointerShown()); } -TEST_F(MultiTouchInputMapperTest, SimulateKernelBufferOverflow) { - addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(ui::ROTATION_0); - prepareAxes(POSITION | ID | SLOT | PRESSURE); - MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>(); - - // First finger down. - processId(mapper, FIRST_TRACKING_ID); - processPosition(mapper, 100, 200); - processPressure(mapper, RAW_PRESSURE_MAX); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - WithMotionAction(AMOTION_EVENT_ACTION_DOWN))); - - // Assume the kernel buffer overflows, and we get a SYN_DROPPED event. - // This will reset the mapper, and thus also reset the touch state. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_DROPPED, 0); - resetMapper(mapper, ARBITRARY_TIME); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - WithMotionAction(AMOTION_EVENT_ACTION_CANCEL))); - - // Since the touch state was reset, it doesn't know which slots are active, so any movements - // are ignored. - processPosition(mapper, 101, 201); - processSync(mapper); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); - - // Second finger goes down. This is the first active finger, so we get a DOWN event. - processSlot(mapper, SECOND_SLOT); - processId(mapper, SECOND_TRACKING_ID); - processPosition(mapper, 400, 500); - processPressure(mapper, RAW_PRESSURE_MAX); - processSync(mapper); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - WithMotionAction(AMOTION_EVENT_ACTION_DOWN))); - - // First slot is still ignored, only the second one is active. - processSlot(mapper, FIRST_SLOT); - processPosition(mapper, 102, 202); - processSlot(mapper, SECOND_SLOT); - processPosition(mapper, 401, 501); - processSync(mapper); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - WithMotionAction(AMOTION_EVENT_ACTION_MOVE))); - - // Both slots up, so we get the UP event for the active pointer. - processSlot(mapper, FIRST_SLOT); - processId(mapper, INVALID_TRACKING_ID); - processSlot(mapper, SECOND_SLOT); - processId(mapper, INVALID_TRACKING_ID); - processSync(mapper); - - ASSERT_NO_FATAL_FAILURE( - mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP))); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); -} - // --- MultiTouchInputMapperTest_ExternalDevice --- class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest { diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h index 73949136f4..db8916880a 100644 --- a/services/inputflinger/tests/InterfaceMocks.h +++ b/services/inputflinger/tests/InterfaceMocks.h @@ -48,7 +48,7 @@ namespace android { class MockInputReaderContext : public InputReaderContext { public: MOCK_METHOD(void, updateGlobalMetaState, (), (override)); - int32_t getGlobalMetaState() override { return 0; }; + MOCK_METHOD(int32_t, getGlobalMetaState, (), (override)); MOCK_METHOD(void, disableVirtualKeysUntil, (nsecs_t time), (override)); MOCK_METHOD(bool, shouldDropVirtualKey, (nsecs_t now, int32_t keyCode, int32_t scanCode), @@ -132,6 +132,9 @@ public: MOCK_METHOD(status_t, getAbsoluteAxisValue, (int32_t deviceId, int32_t axis, int32_t* outValue), (const, override)); + MOCK_METHOD(base::Result<std::vector<int32_t>>, getMtSlotValues, + (int32_t deviceId, int32_t axis, size_t slotCount), (const, override)); + MOCK_METHOD(int32_t, getKeyCodeForKeyLocation, (int32_t deviceId, int32_t locationKeyCode), (const, override)); MOCK_METHOD(bool, markSupportedKeyCodes, diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp index 2ef79999a2..b44529bd04 100644 --- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp +++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp @@ -55,6 +55,7 @@ protected: void SetUp() override { InputMapperUnitTest::SetUp(); + createDevice(); // set key-codes expected in tests for (const auto& [scanCode, outKeycode] : mKeyCodeMap) { diff --git a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp new file mode 100644 index 0000000000..d726385240 --- /dev/null +++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp @@ -0,0 +1,276 @@ +/* + * Copyright 2024 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 "MultiTouchInputMapper.h" + +#include <android-base/logging.h> +#include <gtest/gtest.h> +#include <list> +#include <optional> + +#include "InputMapperTest.h" +#include "InterfaceMocks.h" +#include "TestEventMatchers.h" + +#define TAG "MultiTouchpadInputMapperUnit_test" + +namespace android { + +using testing::_; +using testing::IsEmpty; +using testing::Return; +using testing::SetArgPointee; +using testing::VariantWith; + +static constexpr int32_t DISPLAY_ID = 0; +static constexpr int32_t DISPLAY_WIDTH = 480; +static constexpr int32_t DISPLAY_HEIGHT = 800; +static constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified +static constexpr int32_t SLOT_COUNT = 5; + +static constexpr int32_t ACTION_POINTER_0_UP = + AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); +static constexpr int32_t ACTION_POINTER_1_DOWN = + AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + +/** + * Unit tests for MultiTouchInputMapper. + */ +class MultiTouchInputMapperUnitTest : public InputMapperUnitTest { +protected: + void SetUp() override { + InputMapperUnitTest::SetUp(); + + // Present scan codes + expectScanCodes(/*present=*/true, + {BTN_TOUCH, BTN_TOOL_FINGER, BTN_TOOL_DOUBLETAP, BTN_TOOL_TRIPLETAP, + BTN_TOOL_QUADTAP, BTN_TOOL_QUINTTAP}); + + // Missing scan codes that the mapper checks for. + expectScanCodes(/*present=*/false, + {BTN_TOOL_PEN, BTN_TOOL_RUBBER, BTN_TOOL_BRUSH, BTN_TOOL_PENCIL, + BTN_TOOL_AIRBRUSH}); + + // Current scan code state - all keys are UP by default + setScanCodeState(KeyState::UP, {BTN_LEFT, BTN_RIGHT, BTN_MIDDLE, + BTN_BACK, BTN_SIDE, BTN_FORWARD, + BTN_EXTRA, BTN_TASK, BTN_TOUCH, + BTN_STYLUS, BTN_STYLUS2, BTN_0, + BTN_TOOL_FINGER, BTN_TOOL_PEN, BTN_TOOL_RUBBER, + BTN_TOOL_BRUSH, BTN_TOOL_PENCIL, BTN_TOOL_AIRBRUSH, + BTN_TOOL_MOUSE, BTN_TOOL_LENS, BTN_TOOL_DOUBLETAP, + BTN_TOOL_TRIPLETAP, BTN_TOOL_QUADTAP, BTN_TOOL_QUINTTAP}); + + setKeyCodeState(KeyState::UP, + {AKEYCODE_STYLUS_BUTTON_PRIMARY, AKEYCODE_STYLUS_BUTTON_SECONDARY}); + + // Input properties - only INPUT_PROP_DIRECT for touchscreen + EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, _)).WillRepeatedly(Return(false)); + EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, INPUT_PROP_DIRECT)) + .WillRepeatedly(Return(true)); + + // Axes that the device has + setupAxis(ABS_MT_SLOT, /*valid=*/true, /*min=*/0, /*max=*/SLOT_COUNT - 1, /*resolution=*/0); + setupAxis(ABS_MT_TRACKING_ID, /*valid=*/true, /*min*/ 0, /*max=*/255, /*resolution=*/0); + setupAxis(ABS_MT_POSITION_X, /*valid=*/true, /*min=*/0, /*max=*/2000, /*resolution=*/24); + setupAxis(ABS_MT_POSITION_Y, /*valid=*/true, /*min=*/0, /*max=*/1000, /*resolution=*/24); + + // Axes that the device does not have + setupAxis(ABS_MT_PRESSURE, /*valid=*/false, /*min*/ 0, /*max=*/255, /*resolution=*/0); + setupAxis(ABS_MT_ORIENTATION, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_DISTANCE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_TOUCH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_TOUCH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_WIDTH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_WIDTH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_TOOL_TYPE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + + // reset current slot at the beginning + EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, _)) + .WillRepeatedly([](int32_t, int32_t, int32_t* outValue) { + *outValue = 0; + return OK; + }); + + // mark all slots not in use + mockSlotValues({}); + + mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); + mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + /*isActive=*/true, "local:0", NO_PORT, + ViewportType::INTERNAL); + createDevice(); + mMapper = createInputMapper<MultiTouchInputMapper>(*mDeviceContext, + mFakePolicy->getReaderConfiguration()); + } + + // Mocks position and tracking Ids for the provided slots. Remaining slots will be marked + // unused. + void mockSlotValues( + const std::unordered_map<int32_t /*slotIndex*/, + std::pair<Point /*position*/, int32_t /*trackingId*/>>& + slotValues) { + EXPECT_CALL(mMockEventHub, getMtSlotValues(EVENTHUB_ID, _, SLOT_COUNT)) + .WillRepeatedly([=](int32_t, int32_t axis, + size_t slotCount) -> base::Result<std::vector<int32_t>> { + // tracking Id for the unused slots must set to be < 0 + std::vector<int32_t> outMtSlotValues(slotCount + 1, -1); + outMtSlotValues[0] = axis; + switch (axis) { + case ABS_MT_POSITION_X: + for (const auto& [slotIndex, valuePair] : slotValues) { + outMtSlotValues[slotIndex] = valuePair.first.x; + } + return outMtSlotValues; + case ABS_MT_POSITION_Y: + for (const auto& [slotIndex, valuePair] : slotValues) { + outMtSlotValues[slotIndex] = valuePair.first.y; + } + return outMtSlotValues; + case ABS_MT_TRACKING_ID: + for (const auto& [slotIndex, valuePair] : slotValues) { + outMtSlotValues[slotIndex] = valuePair.second; + } + return outMtSlotValues; + default: + return base::ResultError("Axis not supported", NAME_NOT_FOUND); + } + }); + } + + std::list<NotifyArgs> processPosition(int32_t x, int32_t y) { + std::list<NotifyArgs> args; + args += process(EV_ABS, ABS_MT_POSITION_X, x); + args += process(EV_ABS, ABS_MT_POSITION_Y, y); + return args; + } + + std::list<NotifyArgs> processId(int32_t id) { return process(EV_ABS, ABS_MT_TRACKING_ID, id); } + + std::list<NotifyArgs> processKey(int32_t code, int32_t value) { + return process(EV_KEY, code, value); + } + + std::list<NotifyArgs> processSlot(int32_t slot) { return process(EV_ABS, ABS_MT_SLOT, slot); } + + std::list<NotifyArgs> processSync() { return process(EV_SYN, SYN_REPORT, 0); } +}; + +// This test simulates a multi-finger gesture with unexpected reset in between. This might happen +// due to buffer overflow and device with report a SYN_DROPPED. In this case we expect mapper to be +// reset, MT slot state to be re-populated and the gesture should be cancelled and restarted. +TEST_F(MultiTouchInputMapperUnitTest, MultiFingerGestureWithUnexpectedReset) { + std::list<NotifyArgs> args; + + // Two fingers down at once. + constexpr int32_t FIRST_TRACKING_ID = 1, SECOND_TRACKING_ID = 2; + int32_t x1 = 100, y1 = 125, x2 = 200, y2 = 225; + processKey(BTN_TOUCH, 1); + args += processPosition(x1, y1); + args += processId(FIRST_TRACKING_ID); + args += processSlot(1); + args += processPosition(x2, y2); + args += processId(SECOND_TRACKING_ID); + ASSERT_THAT(args, IsEmpty()); + + args = processSync(); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_DOWN)), + VariantWith<NotifyMotionArgs>( + WithMotionAction(ACTION_POINTER_1_DOWN)))); + + // Move. + x1 += 10; + y1 += 15; + x2 += 5; + y2 -= 10; + args = processSlot(0); + args += processPosition(x1, y1); + args += processSlot(1); + args += processPosition(x2, y2); + ASSERT_THAT(args, IsEmpty()); + + args = processSync(); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_MOVE)))); + const auto pointerCoordsBeforeReset = std::get<NotifyMotionArgs>(args.back()).pointerCoords; + + // On buffer overflow mapper will be reset and MT slots data will be repopulated + EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, _)) + .WillRepeatedly([=](int32_t, int32_t, int32_t* outValue) { + *outValue = 1; + return OK; + }); + + mockSlotValues( + {{1, {Point{x1, y1}, FIRST_TRACKING_ID}}, {2, {Point{x2, y2}, SECOND_TRACKING_ID}}}); + + setScanCodeState(KeyState::DOWN, {BTN_TOUCH}); + + args = mMapper->reset(systemTime(SYSTEM_TIME_MONOTONIC)); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)))); + + // SYN_REPORT should restart the gesture again + args = processSync(); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_DOWN)), + VariantWith<NotifyMotionArgs>( + WithMotionAction(ACTION_POINTER_1_DOWN)))); + ASSERT_EQ(std::get<NotifyMotionArgs>(args.back()).pointerCoords, pointerCoordsBeforeReset); + + // Move. + x1 += 10; + y1 += 15; + x2 += 5; + y2 -= 10; + args = processSlot(0); + args += processPosition(x1, y1); + args += processSlot(1); + args += processPosition(x2, y2); + ASSERT_THAT(args, IsEmpty()); + + args = processSync(); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_MOVE)))); + + // First finger up. + args = processSlot(0); + args += processId(-1); + ASSERT_THAT(args, IsEmpty()); + + args = processSync(); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_POINTER_0_UP)))); + + // Second finger up. + processKey(BTN_TOUCH, 0); + args = processSlot(1); + args += processId(-1); + ASSERT_THAT(args, IsEmpty()); + + args = processSync(); + ASSERT_THAT(args, + ElementsAre( + VariantWith<NotifyMotionArgs>(WithMotionAction(AMOTION_EVENT_ACTION_UP)))); +} + +} // namespace android diff --git a/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp index 9fa6cdd298..5e67506b48 100644 --- a/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp +++ b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp @@ -23,6 +23,11 @@ class MultiTouchMotionAccumulatorTest : public InputMapperUnitTest { protected: static constexpr size_t SLOT_COUNT = 8; + void SetUp() override { + InputMapperUnitTest::SetUp(); + createDevice(); + } + MultiTouchMotionAccumulator mMotionAccumulator; void processMotionEvent(int32_t type, int32_t code, int32_t value) { diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp index 193b84d313..e9e50619ee 100644 --- a/services/inputflinger/tests/PointerChoreographer_test.cpp +++ b/services/inputflinger/tests/PointerChoreographer_test.cpp @@ -145,15 +145,18 @@ private: }; TEST_F(PointerChoreographerTest, ForwardsArgsToInnerListener) { - const std::vector<NotifyArgs> allArgs{NotifyInputDevicesChangedArgs{}, - NotifyConfigurationChangedArgs{}, - NotifyKeyArgs{}, - NotifyMotionArgs{}, - NotifySensorArgs{}, - NotifySwitchArgs{}, - NotifyDeviceResetArgs{}, - NotifyPointerCaptureChangedArgs{}, - NotifyVibratorStateArgs{}}; + const std::vector<NotifyArgs> + allArgs{NotifyInputDevicesChangedArgs{}, + NotifyConfigurationChangedArgs{}, + KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build(), + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(FIRST_TOUCH_POINTER) + .build(), + NotifySensorArgs{}, + NotifySwitchArgs{}, + NotifyDeviceResetArgs{}, + NotifyPointerCaptureChangedArgs{}, + NotifyVibratorStateArgs{}}; for (auto notifyArgs : allArgs) { mChoreographer.notify(notifyArgs); @@ -1444,6 +1447,15 @@ TEST_F(PointerChoreographerTest, SetsPointerIconForStylus) { ASSERT_FALSE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, SECOND_DEVICE_ID)); pc->assertPointerIconNotSet(); + + // The stylus stops hovering. This should cause the icon to be reset. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + pc->assertPointerIconSet(PointerIconStyle::TYPE_NOT_SPECIFIED); } TEST_F(PointerChoreographerTest, SetsCustomPointerIconForStylus) { @@ -1545,4 +1557,122 @@ TEST_F(PointerChoreographerTest, SetsPointerIconForMouseAndStylus) { mousePc->assertPointerIconNotSet(); } +TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerOnDisplay) { + // Make sure there are two PointerControllers on different displays. + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID})); + mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE), + generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}}); + auto firstMousePc = assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_EQ(DISPLAY_ID, firstMousePc->getDisplayId()); + auto secondMousePc = assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_EQ(ANOTHER_DISPLAY_ID, secondMousePc->getDisplayId()); + + // Both pointers should be visible. + ASSERT_TRUE(firstMousePc->isPointerShown()); + ASSERT_TRUE(secondMousePc->isPointerShown()); + + // Hide the icon on the second display. + mChoreographer.setPointerIconVisibility(ANOTHER_DISPLAY_ID, false); + ASSERT_TRUE(firstMousePc->isPointerShown()); + ASSERT_FALSE(secondMousePc->isPointerShown()); + + // Move and set pointer icons for both mice. The second pointer should still be hidden. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .pointer(MOUSE_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .pointer(MOUSE_POINTER) + .deviceId(SECOND_DEVICE_ID) + .displayId(ANOTHER_DISPLAY_ID) + .build()); + ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID)); + ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, ANOTHER_DISPLAY_ID, + SECOND_DEVICE_ID)); + firstMousePc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT); + secondMousePc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT); + ASSERT_TRUE(firstMousePc->isPointerShown()); + ASSERT_FALSE(secondMousePc->isPointerShown()); + + // Allow the icon to be visible on the second display, and move the mouse. + mChoreographer.setPointerIconVisibility(ANOTHER_DISPLAY_ID, true); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .pointer(MOUSE_POINTER) + .deviceId(SECOND_DEVICE_ID) + .displayId(ANOTHER_DISPLAY_ID) + .build()); + ASSERT_TRUE(firstMousePc->isPointerShown()); + ASSERT_TRUE(secondMousePc->isPointerShown()); +} + +TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerWhenDeviceConnected) { + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); + + // Hide the pointer on the display, and then connect the mouse. + mChoreographer.setPointerIconVisibility(DISPLAY_ID, false); + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + auto mousePc = assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_EQ(DISPLAY_ID, mousePc->getDisplayId()); + + // The pointer should not be visible. + ASSERT_FALSE(mousePc->isPointerShown()); +} + +TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerForTouchpad) { + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); + + // Hide the pointer on the display. + mChoreographer.setPointerIconVisibility(DISPLAY_ID, false); + + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, + ADISPLAY_ID_NONE)}}); + auto touchpadPc = assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_EQ(DISPLAY_ID, touchpadPc->getDisplayId()); + + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, + AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD) + .pointer(TOUCHPAD_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + + // The pointer should not be visible. + ASSERT_FALSE(touchpadPc->isPointerShown()); +} + +TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerForStylus) { + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + mChoreographer.setStylusPointerIconEnabled(true); + + // Hide the pointer on the display. + mChoreographer.setPointerIconVisibility(DISPLAY_ID, false); + + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID)); + auto pc = assertPointerControllerCreated(ControllerType::STYLUS); + pc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT); + + // The pointer should not be visible. + ASSERT_FALSE(pc->isPointerShown()); +} + } // namespace android diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h index 66fdaa4520..a3e8eafea3 100644 --- a/services/inputflinger/tests/TestEventMatchers.h +++ b/services/inputflinger/tests/TestEventMatchers.h @@ -18,6 +18,7 @@ #include <cmath> #include <compare> +#include <ios> #include <android-base/stringprintf.h> #include <android/input.h> @@ -464,6 +465,53 @@ inline WithPointersMatcher WithPointers( return WithPointersMatcher(pointers); } +/// Pointer ids matcher +class WithPointerIdsMatcher { +public: + using is_gtest_matcher = void; + explicit WithPointerIdsMatcher(std::set<int32_t> pointerIds) : mPointerIds(pointerIds) {} + + bool MatchAndExplain(const MotionEvent& event, std::ostream* os) const { + std::set<int32_t> actualPointerIds; + for (size_t pointerIndex = 0; pointerIndex < event.getPointerCount(); pointerIndex++) { + const PointerProperties* properties = event.getPointerProperties(pointerIndex); + actualPointerIds.insert(properties->id); + } + + if (mPointerIds != actualPointerIds) { + *os << "expected pointer ids " << dumpSet(mPointerIds) << ", but got " + << dumpSet(actualPointerIds); + return false; + } + return true; + } + + bool MatchAndExplain(const NotifyMotionArgs& event, std::ostream* os) const { + std::set<int32_t> actualPointerIds; + for (const PointerProperties& properties : event.pointerProperties) { + actualPointerIds.insert(properties.id); + } + + if (mPointerIds != actualPointerIds) { + *os << "expected pointer ids " << dumpSet(mPointerIds) << ", but got " + << dumpSet(actualPointerIds); + return false; + } + return true; + } + + void DescribeTo(std::ostream* os) const { *os << "with pointer ids " << dumpSet(mPointerIds); } + + void DescribeNegationTo(std::ostream* os) const { *os << "wrong pointer ids"; } + +private: + const std::set<int32_t> mPointerIds; +}; + +inline WithPointerIdsMatcher WithPointerIds(const std::set<int32_t /*id*/>& pointerIds) { + return WithPointerIdsMatcher(pointerIds); +} + /// Key code class WithKeyCodeMatcher { public: @@ -631,6 +679,24 @@ MATCHER_P(WithPressure, pressure, "InputEvent with specified pressure") { return argPressure == pressure; } +MATCHER_P(WithSize, size, "MotionEvent with specified size") { + const auto argSize = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_SIZE); + *result_listener << "expected size " << size << ", but got " << argSize; + return argSize == size; +} + +MATCHER_P(WithOrientation, orientation, "MotionEvent with specified orientation") { + const auto argOrientation = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION); + *result_listener << "expected orientation " << orientation << ", but got " << argOrientation; + return argOrientation == orientation; +} + +MATCHER_P(WithDistance, distance, "MotionEvent with specified distance") { + const auto argDistance = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_DISTANCE); + *result_listener << "expected distance " << distance << ", but got " << argDistance; + return argDistance == distance; +} + MATCHER_P2(WithTouchDimensions, maj, min, "InputEvent with specified touch dimensions") { const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR); const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR); @@ -674,6 +740,12 @@ MATCHER_P(WithButtonState, buttons, "InputEvent with specified button state") { return arg.buttonState == buttons; } +MATCHER_P(WithMetaState, metaState, "InputEvent with specified meta state") { + *result_listener << "expected meta state 0x" << std::hex << metaState << ", but got 0x" + << arg.metaState; + return arg.metaState == metaState; +} + MATCHER_P(WithActionButton, actionButton, "InputEvent with specified action button") { *result_listener << "expected action button " << actionButton << ", but got " << arg.actionButton; @@ -696,4 +768,16 @@ MATCHER_P2(WithPrecision, xPrecision, yPrecision, "MotionEvent with specified pr return arg.xPrecision == xPrecision && arg.yPrecision == yPrecision; } +MATCHER_P(WithPolicyFlags, policyFlags, "InputEvent with specified policy flags") { + *result_listener << "expected policy flags 0x" << std::hex << policyFlags << ", but got 0x" + << arg.policyFlags; + return arg.policyFlags == static_cast<uint32_t>(policyFlags); +} + +MATCHER_P(WithEdgeFlags, edgeFlags, "InputEvent with specified edge flags") { + *result_listener << "expected edge flags 0x" << std::hex << edgeFlags << ", but got 0x" + << arg.edgeFlags; + return arg.edgeFlags == edgeFlags; +} + } // namespace android diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp index 8cf738cf4f..fbafbadb44 100644 --- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp +++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp @@ -37,6 +37,8 @@ constexpr auto ACTION_UP = AMOTION_EVENT_ACTION_UP; constexpr auto BUTTON_PRESS = AMOTION_EVENT_ACTION_BUTTON_PRESS; constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE; constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE; +constexpr auto HOVER_ENTER = AMOTION_EVENT_ACTION_HOVER_ENTER; +constexpr auto HOVER_EXIT = AMOTION_EVENT_ACTION_HOVER_EXIT; constexpr int32_t DISPLAY_ID = 0; constexpr int32_t DISPLAY_WIDTH = 480; constexpr int32_t DISPLAY_HEIGHT = 800; @@ -101,12 +103,20 @@ protected: setupAxis(ABS_MT_TOUCH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); setupAxis(ABS_MT_WIDTH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); setupAxis(ABS_MT_WIDTH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_TRACKING_ID, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_DISTANCE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_TOOL_TYPE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, testing::_)) .WillRepeatedly([](int32_t eventHubId, int32_t, int32_t* outValue) { *outValue = 0; return OK; }); + EXPECT_CALL(mMockEventHub, getMtSlotValues(EVENTHUB_ID, testing::_, testing::_)) + .WillRepeatedly([]() -> base::Result<std::vector<int32_t>> { + return base::ResultError("Axis not supported", NAME_NOT_FOUND); + }); + createDevice(); mMapper = createInputMapper<TouchpadInputMapper>(*mDeviceContext, mReaderConfiguration); } }; @@ -150,12 +160,14 @@ TEST_F(TouchpadInputMapperTest, HoverAndLeftButtonPress) { setScanCodeState(KeyState::UP, {BTN_LEFT}); args += process(EV_SYN, SYN_REPORT, 0); ASSERT_THAT(args, - ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)), + ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_ENTER)), + VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)), + VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_EXIT)), VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)), VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS)), VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)), VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)), - VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)))); + VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_ENTER)))); // Liftoff args.clear(); @@ -217,12 +229,14 @@ TEST_F(TouchpadInputMapperTestWithChoreographer, HoverAndLeftButtonPress) { setScanCodeState(KeyState::UP, {BTN_LEFT}); args += process(EV_SYN, SYN_REPORT, 0); ASSERT_THAT(args, - ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)), + ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_ENTER)), + VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)), + VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_EXIT)), VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)), VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS)), VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)), VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)), - VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)))); + VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_ENTER)))); // Liftoff args.clear(); diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index 81c570d77b..7898126944 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -201,6 +201,18 @@ public: int32_t* outValue) const override { return mFdp->ConsumeIntegral<status_t>(); } + base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis, + size_t slotCount) const override { + if (mFdp->ConsumeBool()) { + std::vector<int32_t> outValues(slotCount + 1); + for (size_t i = 0; i < outValues.size(); i++) { + outValues.push_back(mFdp->ConsumeIntegral<int32_t>()); + } + return std::move(outValues); + } else { + return base::ResultError("Fuzzer", UNKNOWN_ERROR); + } + } bool markSupportedKeyCodes(int32_t deviceId, const std::vector<int32_t>& keyCodes, uint8_t* outFlags) const override { return mFdp->ConsumeBool(); diff --git a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp index be765cc364..c2bf275611 100644 --- a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp @@ -123,6 +123,7 @@ void setTouchpadSettings(ThreadSafeFuzzedDataProvider& fdp, InputReaderConfigura config.touchpadPointerSpeed = fdp.ConsumeIntegralInRange(-7, 7); config.touchpadNaturalScrollingEnabled = fdp.ConsumeBool(); config.touchpadTapToClickEnabled = fdp.ConsumeBool(); + config.touchpadTapDraggingEnabled = fdp.ConsumeBool(); config.touchpadRightClickZoneEnabled = fdp.ConsumeBool(); } diff --git a/services/powermanager/PowerHalController.cpp b/services/powermanager/PowerHalController.cpp index 9a23c848c9..bc178bce8f 100644 --- a/services/powermanager/PowerHalController.cpp +++ b/services/powermanager/PowerHalController.cpp @@ -80,7 +80,7 @@ std::shared_ptr<HalWrapper> PowerHalController::initHal() { // Check if a call to Power HAL function failed; if so, log the failure and // invalidate the current Power HAL handle. template <typename T> -HalResult<T> PowerHalController::processHalResult(HalResult<T> result, const char* fnName) { +HalResult<T> PowerHalController::processHalResult(HalResult<T>&& result, const char* fnName) { if (result.isFailed()) { ALOGE("%s failed: %s", fnName, result.errorMessage()); std::lock_guard<std::mutex> lock(mConnectedHalMutex); @@ -88,21 +88,19 @@ HalResult<T> PowerHalController::processHalResult(HalResult<T> result, const cha mConnectedHal = nullptr; mHalConnector->reset(); } - return result; + return std::move(result); } HalResult<void> PowerHalController::setBoost(aidl::android::hardware::power::Boost boost, int32_t durationMs) { std::shared_ptr<HalWrapper> handle = initHal(); - auto result = handle->setBoost(boost, durationMs); - return processHalResult(result, "setBoost"); + return processHalResult(handle->setBoost(boost, durationMs), "setBoost"); } HalResult<void> PowerHalController::setMode(aidl::android::hardware::power::Mode mode, bool enabled) { std::shared_ptr<HalWrapper> handle = initHal(); - auto result = handle->setMode(mode, enabled); - return processHalResult(result, "setMode"); + return processHalResult(handle->setMode(mode, enabled), "setMode"); } HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> @@ -110,14 +108,35 @@ PowerHalController::createHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos) { std::shared_ptr<HalWrapper> handle = initHal(); - auto result = handle->createHintSession(tgid, uid, threadIds, durationNanos); - return processHalResult(result, "createHintSession"); + return processHalResult(handle->createHintSession(tgid, uid, threadIds, durationNanos), + "createHintSession"); +} + +HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> +PowerHalController::createHintSessionWithConfig( + int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos, + aidl::android::hardware::power::SessionTag tag, + aidl::android::hardware::power::SessionConfig* config) { + std::shared_ptr<HalWrapper> handle = initHal(); + return processHalResult(handle->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, + tag, config), + "createHintSessionWithConfig"); } HalResult<int64_t> PowerHalController::getHintSessionPreferredRate() { std::shared_ptr<HalWrapper> handle = initHal(); - auto result = handle->getHintSessionPreferredRate(); - return processHalResult(result, "getHintSessionPreferredRate"); + return processHalResult(handle->getHintSessionPreferredRate(), "getHintSessionPreferredRate"); +} + +HalResult<aidl::android::hardware::power::ChannelConfig> PowerHalController::getSessionChannel( + int tgid, int uid) { + std::shared_ptr<HalWrapper> handle = initHal(); + return processHalResult(handle->getSessionChannel(tgid, uid), "getSessionChannel"); +} + +HalResult<void> PowerHalController::closeSessionChannel(int tgid, int uid) { + std::shared_ptr<HalWrapper> handle = initHal(); + return processHalResult(handle->closeSessionChannel(tgid, uid), "closeSessionChannel"); } } // namespace power diff --git a/services/powermanager/PowerHalWrapper.cpp b/services/powermanager/PowerHalWrapper.cpp index 76afbfc646..1009100cc2 100644 --- a/services/powermanager/PowerHalWrapper.cpp +++ b/services/powermanager/PowerHalWrapper.cpp @@ -42,37 +42,58 @@ inline HalResult<void> toHalResult(const ndk::ScopedAStatus& result) { // ------------------------------------------------------------------------------------------------- HalResult<void> EmptyHalWrapper::setBoost(Aidl::Boost boost, int32_t durationMs) { - ALOGV("Skipped setBoost %s with duration %dms because Power HAL not available", - toString(boost).c_str(), durationMs); + ALOGV("Skipped setBoost %s with duration %dms because %s", toString(boost).c_str(), durationMs, + getUnsupportedMessage()); return HalResult<void>::unsupported(); } HalResult<void> EmptyHalWrapper::setMode(Aidl::Mode mode, bool enabled) { - ALOGV("Skipped setMode %s to %s because Power HAL not available", toString(mode).c_str(), - enabled ? "true" : "false"); + ALOGV("Skipped setMode %s to %s because %s", toString(mode).c_str(), enabled ? "true" : "false", + getUnsupportedMessage()); return HalResult<void>::unsupported(); } HalResult<std::shared_ptr<Aidl::IPowerHintSession>> EmptyHalWrapper::createHintSession( int32_t, int32_t, const std::vector<int32_t>& threadIds, int64_t) { - ALOGV("Skipped createHintSession(task num=%zu) because Power HAL not available", - threadIds.size()); + ALOGV("Skipped createHintSession(task num=%zu) because %s", threadIds.size(), + getUnsupportedMessage()); + return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::unsupported(); +} + +HalResult<std::shared_ptr<Aidl::IPowerHintSession>> EmptyHalWrapper::createHintSessionWithConfig( + int32_t, int32_t, const std::vector<int32_t>& threadIds, int64_t, Aidl::SessionTag, + Aidl::SessionConfig*) { + ALOGV("Skipped createHintSessionWithConfig(task num=%zu) because %s", threadIds.size(), + getUnsupportedMessage()); return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::unsupported(); } HalResult<int64_t> EmptyHalWrapper::getHintSessionPreferredRate() { - ALOGV("Skipped getHintSessionPreferredRate because Power HAL not available"); + ALOGV("Skipped getHintSessionPreferredRate because %s", getUnsupportedMessage()); return HalResult<int64_t>::unsupported(); } +HalResult<Aidl::ChannelConfig> EmptyHalWrapper::getSessionChannel(int, int) { + ALOGV("Skipped getSessionChannel because %s", getUnsupportedMessage()); + return HalResult<Aidl::ChannelConfig>::unsupported(); +} + +HalResult<void> EmptyHalWrapper::closeSessionChannel(int, int) { + ALOGV("Skipped closeSessionChannel because %s", getUnsupportedMessage()); + return HalResult<void>::unsupported(); +} + +const char* EmptyHalWrapper::getUnsupportedMessage() { + return "Power HAL is not supported"; +} + // ------------------------------------------------------------------------------------------------- HalResult<void> HidlHalWrapperV1_0::setBoost(Aidl::Boost boost, int32_t durationMs) { if (boost == Aidl::Boost::INTERACTION) { return sendPowerHint(V1_3::PowerHint::INTERACTION, durationMs); } else { - ALOGV("Skipped setBoost %s because Power HAL AIDL not available", toString(boost).c_str()); - return HalResult<void>::unsupported(); + return EmptyHalWrapper::setBoost(boost, durationMs); } } @@ -92,9 +113,7 @@ HalResult<void> HidlHalWrapperV1_0::setMode(Aidl::Mode mode, bool enabled) { case Aidl::Mode::DOUBLE_TAP_TO_WAKE: return setFeature(V1_0::Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE, enabled); default: - ALOGV("Skipped setMode %s because Power HAL AIDL not available", - toString(mode).c_str()); - return HalResult<void>::unsupported(); + return EmptyHalWrapper::setMode(mode, enabled); } } @@ -113,16 +132,8 @@ HalResult<void> HidlHalWrapperV1_0::setFeature(V1_0::Feature feature, bool enabl return HalResult<void>::fromReturn(ret); } -HalResult<std::shared_ptr<Aidl::IPowerHintSession>> HidlHalWrapperV1_0::createHintSession( - int32_t, int32_t, const std::vector<int32_t>& threadIds, int64_t) { - ALOGV("Skipped createHintSession(task num=%zu) because Power HAL not available", - threadIds.size()); - return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::unsupported(); -} - -HalResult<int64_t> HidlHalWrapperV1_0::getHintSessionPreferredRate() { - ALOGV("Skipped getHintSessionPreferredRate because Power HAL not available"); - return HalResult<int64_t>::unsupported(); +const char* HidlHalWrapperV1_0::getUnsupportedMessage() { + return "Power HAL AIDL is not supported"; } // ------------------------------------------------------------------------------------------------- @@ -191,7 +202,7 @@ HalResult<void> AidlHalWrapper::setBoost(Aidl::Boost boost, int32_t durationMs) // Quick return if boost is not supported by HAL if (idx >= mBoostSupportedArray.size() || mBoostSupportedArray[idx] == HalSupport::OFF) { - ALOGV("Skipped setBoost %s because Power HAL doesn't support it", toString(boost).c_str()); + ALOGV("Skipped setBoost %s because %s", toString(boost).c_str(), getUnsupportedMessage()); return HalResult<void>::unsupported(); } @@ -207,8 +218,8 @@ HalResult<void> AidlHalWrapper::setBoost(Aidl::Boost boost, int32_t durationMs) mBoostSupportedArray[idx] = isSupported ? HalSupport::ON : HalSupport::OFF; if (!isSupported) { - ALOGV("Skipped setBoost %s because Power HAL doesn't support it", - toString(boost).c_str()); + ALOGV("Skipped setBoost %s because %s", toString(boost).c_str(), + getUnsupportedMessage()); return HalResult<void>::unsupported(); } } @@ -223,7 +234,7 @@ HalResult<void> AidlHalWrapper::setMode(Aidl::Mode mode, bool enabled) { // Quick return if mode is not supported by HAL if (idx >= mModeSupportedArray.size() || mModeSupportedArray[idx] == HalSupport::OFF) { - ALOGV("Skipped setMode %s because Power HAL doesn't support it", toString(mode).c_str()); + ALOGV("Skipped setMode %s because %s", toString(mode).c_str(), getUnsupportedMessage()); return HalResult<void>::unsupported(); } @@ -236,8 +247,7 @@ HalResult<void> AidlHalWrapper::setMode(Aidl::Mode mode, bool enabled) { mModeSupportedArray[idx] = isSupported ? HalSupport::ON : HalSupport::OFF; if (!isSupported) { - ALOGV("Skipped setMode %s because Power HAL doesn't support it", - toString(mode).c_str()); + ALOGV("Skipped setMode %s because %s", toString(mode).c_str(), getUnsupportedMessage()); return HalResult<void>::unsupported(); } } @@ -251,7 +261,17 @@ HalResult<std::shared_ptr<Aidl::IPowerHintSession>> AidlHalWrapper::createHintSe std::shared_ptr<Aidl::IPowerHintSession> appSession; return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>:: fromStatus(mHandle->createHintSession(tgid, uid, threadIds, durationNanos, &appSession), - appSession); + std::move(appSession)); +} + +HalResult<std::shared_ptr<Aidl::IPowerHintSession>> AidlHalWrapper::createHintSessionWithConfig( + int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos, + Aidl::SessionTag tag, Aidl::SessionConfig* config) { + std::shared_ptr<Aidl::IPowerHintSession> appSession; + return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>:: + fromStatus(mHandle->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, + tag, config, &appSession), + std::move(appSession)); } HalResult<int64_t> AidlHalWrapper::getHintSessionPreferredRate() { @@ -260,6 +280,20 @@ HalResult<int64_t> AidlHalWrapper::getHintSessionPreferredRate() { return HalResult<int64_t>::fromStatus(result, rate); } +HalResult<Aidl::ChannelConfig> AidlHalWrapper::getSessionChannel(int tgid, int uid) { + Aidl::ChannelConfig config; + auto result = mHandle->getSessionChannel(tgid, uid, &config); + return HalResult<Aidl::ChannelConfig>::fromStatus(result, std::move(config)); +} + +HalResult<void> AidlHalWrapper::closeSessionChannel(int tgid, int uid) { + return toHalResult(mHandle->closeSessionChannel(tgid, uid)); +} + +const char* AidlHalWrapper::getUnsupportedMessage() { + return "Power HAL doesn't support it"; +} + // ------------------------------------------------------------------------------------------------- } // namespace power diff --git a/services/powermanager/WorkDuration.cpp b/services/powermanager/WorkDuration.cpp index ef723c229c..bd2b10a149 100644 --- a/services/powermanager/WorkDuration.cpp +++ b/services/powermanager/WorkDuration.cpp @@ -25,8 +25,9 @@ namespace android::os { WorkDuration::WorkDuration(int64_t startTimestampNanos, int64_t totalDurationNanos, int64_t cpuDurationNanos, int64_t gpuDurationNanos) - : workPeriodStartTimestampNanos(startTimestampNanos), + : timestampNanos(0), actualTotalDurationNanos(totalDurationNanos), + workPeriodStartTimestampNanos(startTimestampNanos), actualCpuDurationNanos(cpuDurationNanos), actualGpuDurationNanos(gpuDurationNanos) {} diff --git a/services/powermanager/include/android/WorkDuration.h b/services/powermanager/include/android/WorkDuration.h index 99b5b8b1b4..26a575f834 100644 --- a/services/powermanager/include/android/WorkDuration.h +++ b/services/powermanager/include/android/WorkDuration.h @@ -61,11 +61,11 @@ struct WorkDuration : AWorkDuration, android::Parcelable { return os; } - int64_t workPeriodStartTimestampNanos; + int64_t timestampNanos; int64_t actualTotalDurationNanos; + int64_t workPeriodStartTimestampNanos; int64_t actualCpuDurationNanos; int64_t actualGpuDurationNanos; - int64_t timestampNanos; }; } // namespace android::os diff --git a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp index 641ba9f44b..a7202969ad 100644 --- a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp +++ b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp @@ -29,9 +29,12 @@ #include <thread> using aidl::android::hardware::power::Boost; +using aidl::android::hardware::power::ChannelConfig; using aidl::android::hardware::power::IPower; using aidl::android::hardware::power::IPowerHintSession; using aidl::android::hardware::power::Mode; +using aidl::android::hardware::power::SessionConfig; +using aidl::android::hardware::power::SessionTag; using android::binder::Status; using namespace android; @@ -53,6 +56,14 @@ public: (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos, std::shared_ptr<IPowerHintSession>* session), (override)); + MOCK_METHOD(ndk::ScopedAStatus, createHintSessionWithConfig, + (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, + int64_t durationNanos, SessionTag tag, SessionConfig* config, + std::shared_ptr<IPowerHintSession>* _aidl_return), + (override)); + MOCK_METHOD(ndk::ScopedAStatus, getSessionChannel, + (int32_t tgid, int32_t uid, ChannelConfig* _aidl_return), (override)); + MOCK_METHOD(ndk::ScopedAStatus, closeSessionChannel, (int32_t tgid, int32_t uid), (override)); MOCK_METHOD(ndk::ScopedAStatus, getHintSessionPreferredRate, (int64_t * rate), (override)); MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override)); MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override)); @@ -245,6 +256,23 @@ TEST_F(PowerHalWrapperAidlTest, TestCreateHintSessionSuccessful) { ASSERT_TRUE(result.isOk()); } +TEST_F(PowerHalWrapperAidlTest, TestCreateHintSessionWithConfigSuccessful) { + std::vector<int> threadIds{gettid()}; + int32_t tgid = 999; + int32_t uid = 1001; + int64_t durationNanos = 16666666L; + SessionTag tag = SessionTag::OTHER; + SessionConfig out; + EXPECT_CALL(*mMockHal.get(), + createHintSessionWithConfig(Eq(tgid), Eq(uid), Eq(threadIds), Eq(durationNanos), + Eq(tag), _, _)) + .Times(Exactly(1)) + .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok()))); + auto result = + mWrapper->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, tag, &out); + ASSERT_TRUE(result.isOk()); +} + TEST_F(PowerHalWrapperAidlTest, TestCreateHintSessionFailed) { int32_t tgid = 999; int32_t uid = 1001; @@ -268,3 +296,18 @@ TEST_F(PowerHalWrapperAidlTest, TestGetHintSessionPreferredRate) { int64_t rate = result.value(); ASSERT_GE(0, rate); } + +TEST_F(PowerHalWrapperAidlTest, TestSessionChannel) { + int32_t tgid = 999; + int32_t uid = 1001; + EXPECT_CALL(*mMockHal.get(), getSessionChannel(Eq(tgid), Eq(uid), _)) + .Times(Exactly(1)) + .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok()))); + EXPECT_CALL(*mMockHal.get(), closeSessionChannel(Eq(tgid), Eq(uid))) + .Times(Exactly(1)) + .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok()))); + auto createResult = mWrapper->getSessionChannel(tgid, uid); + ASSERT_TRUE(createResult.isOk()); + auto closeResult = mWrapper->closeSessionChannel(tgid, uid); + ASSERT_TRUE(closeResult.isOk()); +} diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp index 11c56a8c56..afaf0ae84f 100644 --- a/services/sensorservice/Android.bp +++ b/services/sensorservice/Android.bp @@ -7,6 +7,19 @@ package { default_applicable_licenses: ["frameworks_native_license"], } +aconfig_declarations { + name: "sensorservice_flags", + package: "com.android.frameworks.sensorservice.flags", + container: "system", + srcs: ["senserservice_flags.aconfig"], +} + +cc_aconfig_library { + name: "sensorservice_flags_c_lib", + aconfig_declarations: "sensorservice_flags", + host_supported: true, +} + cc_library { name: "libsensorservice", @@ -70,6 +83,7 @@ cc_library { "android.hardware.sensors@2.1", "android.hardware.common-V2-ndk", "android.hardware.common.fmq-V1-ndk", + "server_configurable_flags", ], static_libs: [ @@ -77,6 +91,7 @@ cc_library { "android.hardware.sensors@1.0-convert", "android.hardware.sensors-V1-convert", "android.hardware.sensors-V2-ndk", + "sensorservice_flags_c_lib", ], generated_headers: ["framework-cppstream-protos"], diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp index dd83fdefc3..8e9dfea65e 100644 --- a/services/sensorservice/SensorDevice.cpp +++ b/services/sensorservice/SensorDevice.cpp @@ -16,15 +16,9 @@ #include "SensorDevice.h" -#include "android/hardware/sensors/2.0/types.h" -#include "android/hardware/sensors/2.1/types.h" -#include "convertV2_1.h" - -#include "AidlSensorHalWrapper.h" -#include "HidlSensorHalWrapper.h" - #include <android-base/logging.h> #include <android/util/ProtoOutputStream.h> +#include <com_android_frameworks_sensorservice_flags.h> #include <cutils/atomic.h> #include <frameworks/base/core/proto/android/service/sensor_service.proto.h> #include <hardware/sensors-base.h> @@ -35,13 +29,20 @@ #include <chrono> #include <cinttypes> +#include <condition_variable> #include <cstddef> -#include <thread> #include <mutex> -#include <condition_variable> +#include <thread> + +#include "AidlSensorHalWrapper.h" +#include "HidlSensorHalWrapper.h" +#include "android/hardware/sensors/2.0/types.h" +#include "android/hardware/sensors/2.1/types.h" +#include "convertV2_1.h" using namespace android::hardware::sensors; using android::util::ProtoOutputStream; +namespace sensorservice_flags = com::android::frameworks::sensorservice::flags; namespace android { // --------------------------------------------------------------------------- @@ -166,6 +167,9 @@ void SensorDevice::reconnect() { mActivationCount.clear(); mSensorList.clear(); + if (sensorservice_flags::dynamic_sensor_hal_reconnect_handling()) { + mConnectedDynamicSensors.clear(); + } if (mHalWrapper->connect(this)) { initializeSensorList(); @@ -340,6 +344,15 @@ void SensorDevice::dump(ProtoOutputStream* proto) const { } } +std::vector<int32_t> SensorDevice::getDynamicSensorHandles() { + std::vector<int32_t> sensorHandles; + std::lock_guard<std::mutex> lock(mDynamicSensorsMutex); + for (auto& sensors : mConnectedDynamicSensors) { + sensorHandles.push_back(sensors.first); + } + return sensorHandles; +} + ssize_t SensorDevice::getSensorList(sensor_t const** list) { *list = &mSensorList[0]; @@ -416,8 +429,15 @@ void SensorDevice::onDynamicSensorsConnected(const std::vector<sensor_t>& dynami } void SensorDevice::onDynamicSensorsDisconnected( - const std::vector<int32_t>& /* dynamicSensorHandlesRemoved */) { - // TODO: Currently dynamic sensors do not seem to be removed + const std::vector<int32_t>& dynamicSensorHandlesRemoved) { + if (sensorservice_flags::sensor_device_on_dynamic_sensor_disconnected()) { + for (auto handle : dynamicSensorHandlesRemoved) { + auto it = mConnectedDynamicSensors.find(handle); + if (it != mConnectedDynamicSensors.end()) { + mConnectedDynamicSensors.erase(it); + } + } + } } void SensorDevice::writeWakeLockHandled(uint32_t count) { @@ -483,12 +503,16 @@ status_t SensorDevice::activateLocked(void* ident, int handle, int enabled) { } else { ALOGD_IF(DEBUG_CONNECTIONS, "disable index=%zd", info.batchParams.indexOfKey(ident)); - // If a connected dynamic sensor is deactivated, remove it from the - // dictionary. + // TODO(b/316958439): Remove these line after + // sensor_device_on_dynamic_sensor_disconnected is ramped up. Bounded + // here since this function is coupled with + // dynamic_sensors_hal_disconnect_dynamic_sensor flag. If a connected + // dynamic sensor is deactivated, remove it from the dictionary. auto it = mConnectedDynamicSensors.find(handle); if (it != mConnectedDynamicSensors.end()) { - mConnectedDynamicSensors.erase(it); + mConnectedDynamicSensors.erase(it); } + // End of TODO(b/316958439) if (info.removeBatchParamsForIdent(ident) >= 0) { if (info.numActiveClients() == 0) { diff --git a/services/sensorservice/SensorDevice.h b/services/sensorservice/SensorDevice.h index f127c0f8cd..52f7cf2de8 100644 --- a/services/sensorservice/SensorDevice.h +++ b/services/sensorservice/SensorDevice.h @@ -60,6 +60,8 @@ public: ssize_t getSensorList(sensor_t const** list); + std::vector<int32_t> getDynamicSensorHandles(); + void handleDynamicSensorConnection(int handle, bool connected); status_t initCheck() const; int getHalDeviceVersion() const; diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp index 85043c9038..e1c43c6fec 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "SensorService.h" + #include <aidl/android/hardware/sensors/ISensors.h> #include <android-base/strings.h> #include <android/content/pm/IPackageManagerNative.h> @@ -22,20 +24,36 @@ #include <binder/IServiceManager.h> #include <binder/PermissionCache.h> #include <binder/PermissionController.h> +#include <com_android_frameworks_sensorservice_flags.h> #include <cutils/ashmem.h> #include <cutils/misc.h> #include <cutils/properties.h> #include <frameworks/base/core/proto/android/service/sensor_service.proto.h> #include <hardware/sensors.h> #include <hardware_legacy/power.h> +#include <inttypes.h> #include <log/log.h> +#include <math.h> #include <openssl/digest.h> #include <openssl/hmac.h> #include <openssl/rand.h> +#include <private/android_filesystem_config.h> +#include <sched.h> #include <sensor/SensorEventQueue.h> #include <sensorprivacy/SensorPrivacyManager.h> +#include <stdint.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> #include <utils/SystemClock.h> +#include <condition_variable> +#include <ctime> +#include <future> +#include <mutex> +#include <string> + #include "BatteryService.h" #include "CorrectedGyroSensor.h" #include "GravitySensor.h" @@ -43,35 +61,17 @@ #include "LinearAccelerationSensor.h" #include "OrientationSensor.h" #include "RotationVectorSensor.h" -#include "SensorFusion.h" -#include "SensorInterface.h" - -#include "SensorService.h" #include "SensorDirectConnection.h" #include "SensorEventAckReceiver.h" #include "SensorEventConnection.h" +#include "SensorFusion.h" +#include "SensorInterface.h" #include "SensorRecord.h" #include "SensorRegistrationInfo.h" #include "SensorServiceUtils.h" -#include <inttypes.h> -#include <math.h> -#include <sched.h> -#include <stdint.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include <condition_variable> -#include <ctime> -#include <future> -#include <mutex> -#include <string> - -#include <private/android_filesystem_config.h> - using namespace std::chrono_literals; +namespace sensorservice_flags = com::android::frameworks::sensorservice::flags; namespace android { // --------------------------------------------------------------------------- @@ -335,6 +335,11 @@ void SensorService::onFirstRef() { case SENSOR_TYPE_GYROSCOPE_UNCALIBRATED: hasGyroUncalibrated = true; break; + case SENSOR_TYPE_DYNAMIC_SENSOR_META: + if (sensorservice_flags::dynamic_sensor_hal_reconnect_handling()) { + mDynamicMetaSensorHandle = list[i].handle; + } + break; case SENSOR_TYPE_GRAVITY: case SENSOR_TYPE_LINEAR_ACCELERATION: case SENSOR_TYPE_ROTATION_VECTOR: @@ -1055,6 +1060,68 @@ void SensorService::cleanupAutoDisabledSensorLocked(const sp<SensorEventConnecti } } +void SensorService::sendEventsToAllClients( + const std::vector<sp<SensorEventConnection>>& activeConnections, + ssize_t count) { + // Send our events to clients. Check the state of wake lock for each client + // and release the lock if none of the clients need it. + bool needsWakeLock = false; + for (const sp<SensorEventConnection>& connection : activeConnections) { + connection->sendEvents(mSensorEventBuffer, count, mSensorEventScratch, + mMapFlushEventsToConnections); + needsWakeLock |= connection->needsWakeLock(); + // If the connection has one-shot sensors, it may be cleaned up after + // first trigger. Early check for one-shot sensors. + if (connection->hasOneShotSensors()) { + cleanupAutoDisabledSensorLocked(connection, mSensorEventBuffer, count); + } + } + + if (mWakeLockAcquired && !needsWakeLock) { + setWakeLockAcquiredLocked(false); + } +} + +void SensorService::disconnectDynamicSensor( + int handle, + const std::vector<sp<SensorEventConnection>>& activeConnections) { + ALOGI("Dynamic sensor handle 0x%x disconnected", handle); + SensorDevice::getInstance().handleDynamicSensorConnection( + handle, false /*connected*/); + if (!unregisterDynamicSensorLocked(handle)) { + ALOGE("Dynamic sensor release error."); + } + for (const sp<SensorEventConnection>& connection : activeConnections) { + connection->removeSensor(handle); + } +} + +void SensorService::handleDeviceReconnection(SensorDevice& device) { + if (sensorservice_flags::dynamic_sensor_hal_reconnect_handling()) { + const std::vector<sp<SensorEventConnection>> activeConnections = + mConnectionHolder.lock(mLock).getActiveConnections(); + + for (int32_t handle : device.getDynamicSensorHandles()) { + if (mDynamicMetaSensorHandle.has_value()) { + // Sending one event at a time to prevent the number of handle is more than the + // buffer can hold. + mSensorEventBuffer[0].type = SENSOR_TYPE_DYNAMIC_SENSOR_META; + mSensorEventBuffer[0].sensor = *mDynamicMetaSensorHandle; + mSensorEventBuffer[0].dynamic_sensor_meta.connected = false; + mSensorEventBuffer[0].dynamic_sensor_meta.handle = handle; + mMapFlushEventsToConnections[0] = nullptr; + + disconnectDynamicSensor(handle, activeConnections); + sendEventsToAllClients(activeConnections, 1); + } else { + ALOGE("Failed to find mDynamicMetaSensorHandle during init."); + break; + } + } + } + device.reconnect(); +} + bool SensorService::threadLoop() { ALOGD("nuSensorService thread starting..."); @@ -1071,8 +1138,8 @@ bool SensorService::threadLoop() { do { ssize_t count = device.poll(mSensorEventBuffer, numEventMax); if (count < 0) { - if(count == DEAD_OBJECT && device.isReconnecting()) { - device.reconnect(); + if (count == DEAD_OBJECT && device.isReconnecting()) { + handleDeviceReconnection(device); continue; } else { ALOGE("sensor poll failed (%s)", strerror(-count)); @@ -1176,7 +1243,6 @@ bool SensorService::threadLoop() { rec->removeFirstPendingFlushConnection(); } } - // handle dynamic sensor meta events, process registration and unregistration of dynamic // sensor based on content of event. if (mSensorEventBuffer[i].type == SENSOR_TYPE_DYNAMIC_SENSOR_META) { @@ -1206,37 +1272,14 @@ bool SensorService::threadLoop() { } } else { int handle = mSensorEventBuffer[i].dynamic_sensor_meta.handle; - ALOGI("Dynamic sensor handle 0x%x disconnected", handle); - - device.handleDynamicSensorConnection(handle, false /*connected*/); - if (!unregisterDynamicSensorLocked(handle)) { - ALOGE("Dynamic sensor release error."); - } - - for (const sp<SensorEventConnection>& connection : activeConnections) { - connection->removeSensor(handle); - } + disconnectDynamicSensor(handle, activeConnections); } } } // Send our events to clients. Check the state of wake lock for each client and release the // lock if none of the clients need it. - bool needsWakeLock = false; - for (const sp<SensorEventConnection>& connection : activeConnections) { - connection->sendEvents(mSensorEventBuffer, count, mSensorEventScratch, - mMapFlushEventsToConnections); - needsWakeLock |= connection->needsWakeLock(); - // If the connection has one-shot sensors, it may be cleaned up after first trigger. - // Early check for one-shot sensors. - if (connection->hasOneShotSensors()) { - cleanupAutoDisabledSensorLocked(connection, mSensorEventBuffer, count); - } - } - - if (mWakeLockAcquired && !needsWakeLock) { - setWakeLockAcquiredLocked(false); - } + sendEventsToAllClients(activeConnections, count); } while (!Thread::exitPending()); ALOGW("Exiting SensorService::threadLoop => aborting..."); diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h index b643f6b382..118d9281fc 100644 --- a/services/sensorservice/SensorService.h +++ b/services/sensorservice/SensorService.h @@ -17,9 +17,6 @@ #ifndef ANDROID_SENSOR_SERVICE_H #define ANDROID_SENSOR_SERVICE_H -#include "SensorList.h" -#include "RecentEventLogger.h" - #include <android-base/macros.h> #include <binder/AppOpsManager.h> #include <binder/BinderService.h> @@ -27,11 +24,11 @@ #include <cutils/compiler.h> #include <cutils/multiuser.h> #include <private/android_filesystem_config.h> -#include <sensor/ISensorServer.h> #include <sensor/ISensorEventConnection.h> +#include <sensor/ISensorServer.h> #include <sensor/Sensor.h> -#include "android/hardware/BnSensorPrivacyListener.h" - +#include <stdint.h> +#include <sys/types.h> #include <utils/AndroidThreads.h> #include <utils/KeyedVector.h> #include <utils/Looper.h> @@ -40,8 +37,6 @@ #include <utils/Vector.h> #include <utils/threads.h> -#include <stdint.h> -#include <sys/types.h> #include <condition_variable> #include <mutex> #include <queue> @@ -49,6 +44,10 @@ #include <unordered_set> #include <vector> +#include "RecentEventLogger.h" +#include "SensorList.h" +#include "android/hardware/BnSensorPrivacyListener.h" + #if __clang__ // Clang warns about SensorEventConnection::dump hiding BBinder::dump. The cause isn't fixable // without changing the API, so let's tell clang this is indeed intentional. @@ -57,7 +56,7 @@ // --------------------------------------------------------------------------- #define IGNORE_HARDWARE_FUSION false -#define DEBUG_CONNECTIONS false +#define DEBUG_CONNECTIONS false // Max size is 100 KB which is enough to accept a batch of about 1000 events. #define MAX_SOCKET_BUFFER_SIZE_BATCHED (100 * 1024) // For older HALs which don't support batching, use a smaller socket buffer size. @@ -341,6 +340,12 @@ private: binder::Status onSensorPrivacyChanged(int toggleType, int sensor, bool enabled); + // This callback is used for additional automotive-specific states for sensor privacy + // such as AUTO_DRIVER_ASSISTANCE_APPS. The newly defined states will only be valid + // for camera privacy on automotive devices. onSensorPrivacyChanged() will still be + // invoked whenever the enabled status of a toggle changes. + binder::Status onSensorPrivacyStateChanged(int, int, int) {return binder::Status::ok();} + protected: std::atomic_bool mSensorPrivacyEnabled; wp<SensorService> mService; @@ -453,6 +458,11 @@ private: // Send events from the event cache for this particular connection. void sendEventsFromCache(const sp<SensorEventConnection>& connection); + // Send all events in the buffer to all clients. + void sendEventsToAllClients( + const std::vector<sp<SensorEventConnection>>& activeConnections, + ssize_t count); + // If SensorService is operating in RESTRICTED mode, only select whitelisted packages are // allowed to register for or call flush on sensors. Typically only cts test packages are // allowed. @@ -516,6 +526,14 @@ private: bool isInjectionMode(int mode); + void handleDeviceReconnection(SensorDevice& device); + + // Removes a connected dynamic sensor and send the corresponding event to + // all connections. + void disconnectDynamicSensor( + int handle, + const std::vector<sp<SensorEventConnection>>& activeConnections); + static inline bool isAudioServerOrSystemServerUid(uid_t uid) { return multiuser_get_app_id(uid) == AID_SYSTEM || uid == AID_AUDIOSERVER; } @@ -583,6 +601,10 @@ private: bool mLastReportedProxIsActive; // Listeners subscribed to receive updates on the proximity sensor active state. std::vector<sp<ProximityActiveListener>> mProximityActiveListeners; + + // Stores the handle of the dynamic_meta sensor to send clean up event once + // HAL crashes. + std::optional<int> mDynamicMetaSensorHandle; }; } // namespace android diff --git a/services/sensorservice/senserservice_flags.aconfig b/services/sensorservice/senserservice_flags.aconfig new file mode 100644 index 0000000000..8d43f79950 --- /dev/null +++ b/services/sensorservice/senserservice_flags.aconfig @@ -0,0 +1,16 @@ +package: "com.android.frameworks.sensorservice.flags" +container: "system" + +flag { + name: "dynamic_sensor_hal_reconnect_handling" + namespace: "sensors" + description: "This flag controls if the dynamic sensor data will be clean up after HAL is disconnected." + bug: "307782607" +} + +flag { + name: "sensor_device_on_dynamic_sensor_disconnected" + namespace: "sensors" + description: "This flag controls if the callback onDynamicSensorsDisconnected is implemented or not." + bug: "316958439" +}
\ No newline at end of file diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index 0989863b7d..dcef9a3775 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -10,6 +10,7 @@ package { aconfig_declarations { name: "surfaceflinger_flags", package: "com.android.graphics.surfaceflinger.flags", + container: "system", srcs: ["surfaceflinger_flags.aconfig"], } diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h index 1a235e9401..18a96f4de7 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h @@ -24,6 +24,7 @@ #include <compositionengine/LayerFE.h> #include <compositionengine/OutputColorSetting.h> #include <math/mat4.h> +#include <scheduler/interface/ICompositor.h> #include <ui/FenceTime.h> #include <ui/Transform.h> @@ -89,17 +90,14 @@ struct CompositionRefreshArgs { // If set, causes the dirty regions to flash with the delay std::optional<std::chrono::microseconds> devOptFlashDirtyRegionsDelay; - // Optional. - // The earliest time to send the present command to the HAL. - std::optional<std::chrono::steady_clock::time_point> earliestPresentTime; - - // The expected time for the next present - nsecs_t expectedPresentTime{0}; + scheduler::FrameTargets frameTargets; // The frameInterval for the next present - Fps frameInterval{}; + // TODO (b/315371484): Calculate per display and store on `FrameTarget`. + Fps frameInterval; // If set, a frame has been scheduled for that time. + // TODO (b/255601557): Calculate per display. std::optional<std::chrono::steady_clock::time_point> scheduledFrameTime; std::vector<BorderRenderInfo> borderInfoList; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h index 5e84be1841..39748b8417 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h @@ -40,6 +40,9 @@ public: // True if the display is secure virtual bool isSecure() const = 0; + // Sets the secure flag for the display + virtual void setSecure(bool secure) = 0; + // True if the display is virtual virtual bool isVirtual() const = 0; @@ -57,7 +60,7 @@ public: virtual void createClientCompositionCache(uint32_t cacheSize) = 0; // Sends the brightness setting to HWC - virtual void applyDisplayBrightness(const bool applyImmediately) = 0; + virtual void applyDisplayBrightness(bool applyImmediately) = 0; protected: ~Display() = default; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h index eac5d97df3..2dc9a1a49a 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h @@ -73,7 +73,8 @@ public: const compositionengine::DisplayColorProfileCreationArgs&) override; void createRenderSurface(const compositionengine::RenderSurfaceCreationArgs&) override; void createClientCompositionCache(uint32_t cacheSize) override; - void applyDisplayBrightness(const bool applyImmediately) override; + void applyDisplayBrightness(bool applyImmediately) override; + void setSecure(bool secure) override; // Internal helpers used by chooseCompositionStrategy() using ChangedTypes = android::HWComposer::DeviceRequestedChanges::ChangedTypes; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h index d26ca9dd00..e2d17ee502 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h @@ -147,8 +147,6 @@ public: bool hasProtectedLayers() const; - bool hasSolidColorLayers() const; - // True if any layer in this cached set has CachingHint::Disabled bool cachingHintExcludesLayers() const; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h index 1f241b091c..dc3821ca43 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h @@ -258,11 +258,6 @@ public: gui::CachingHint getCachingHint() const { return mCachingHint.get(); } - bool hasSolidColorCompositionType() const { - return getOutputLayer()->getLayerFE().getCompositionState()->compositionType == - aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR; - } - float getFps() const { return getOutputLayer()->getLayerFE().getCompositionState()->fps; } void dump(std::string& result) const; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h index 7e99ec2f5a..46cb95ef25 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h @@ -33,6 +33,7 @@ public: MOCK_CONST_METHOD0(getId, DisplayId()); MOCK_CONST_METHOD0(isSecure, bool()); + MOCK_METHOD1(setSecure, void(bool)); MOCK_CONST_METHOD0(isVirtual, bool()); MOCK_CONST_METHOD0(getPreferredBootHwcConfigId, int32_t()); diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp index 7a727fdb83..6428d089a7 100644 --- a/services/surfaceflinger/CompositionEngine/src/Display.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp @@ -74,6 +74,10 @@ bool Display::isSecure() const { return getState().isSecure; } +void Display::setSecure(bool secure) { + editState().isSecure = secure; +} + bool Display::isVirtual() const { return VirtualDisplayId::tryCast(mId).has_value(); } @@ -200,13 +204,12 @@ void Display::setReleasedLayers(const compositionengine::CompositionRefreshArgs& setReleasedLayers(std::move(releasedLayers)); } -void Display::applyDisplayBrightness(const bool applyImmediately) { - auto& hwc = getCompositionEngine().getHwComposer(); - const auto halDisplayId = HalDisplayId::tryCast(*getDisplayId()); - if (const auto physicalDisplayId = PhysicalDisplayId::tryCast(*halDisplayId); - physicalDisplayId && getState().displayBrightness) { +void Display::applyDisplayBrightness(bool applyImmediately) { + if (const auto displayId = ftl::Optional(getDisplayId()).and_then(PhysicalDisplayId::tryCast); + displayId && getState().displayBrightness) { + auto& hwc = getCompositionEngine().getHwComposer(); const status_t result = - hwc.setDisplayBrightness(*physicalDisplayId, *getState().displayBrightness, + hwc.setDisplayBrightness(*displayId, *getState().displayBrightness, getState().displayBrightnessNits, Hwc2::Composer::DisplayBrightnessOptions{ .applyImmediately = applyImmediately}) diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 09c7c9933a..1c2f6cb686 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -28,8 +28,11 @@ #include <compositionengine/impl/OutputLayer.h> #include <compositionengine/impl/OutputLayerCompositionState.h> #include <compositionengine/impl/planner/Planner.h> +#include <ftl/algorithm.h> #include <ftl/future.h> #include <gui/TraceUtils.h> +#include <scheduler/FrameTargeter.h> +#include <scheduler/Time.h> #include <optional> #include <thread> @@ -429,7 +432,28 @@ void Output::prepare(const compositionengine::CompositionRefreshArgs& refreshArg ftl::Future<std::monostate> Output::present( const compositionengine::CompositionRefreshArgs& refreshArgs) { - ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str()); + const auto stringifyExpectedPresentTime = [this, &refreshArgs]() -> std::string { + return ftl::Optional(getDisplayId()) + .and_then(PhysicalDisplayId::tryCast) + .and_then([&refreshArgs](PhysicalDisplayId id) { + return refreshArgs.frameTargets.get(id); + }) + .transform([](const auto& frameTargetPtr) { + return frameTargetPtr.get()->expectedPresentTime(); + }) + .transform([](TimePoint expectedPresentTime) { + return base::StringPrintf(" vsyncIn %.2fms", + ticks<std::milli, float>(expectedPresentTime - + TimePoint::now())); + }) + .or_else([] { + // There is no vsync for this output. + return std::make_optional(std::string()); + }) + .value(); + }; + ATRACE_FORMAT("%s for %s%s", __func__, mNamePlusId.c_str(), + stringifyExpectedPresentTime().c_str()); ALOGV(__FUNCTION__); updateColorProfile(refreshArgs); @@ -853,8 +877,14 @@ void Output::writeCompositionState(const compositionengine::CompositionRefreshAr return; } - editState().earliestPresentTime = refreshArgs.earliestPresentTime; - editState().expectedPresentTime = refreshArgs.expectedPresentTime; + if (auto frameTargetPtrOpt = ftl::Optional(getDisplayId()) + .and_then(PhysicalDisplayId::tryCast) + .and_then([&refreshArgs](PhysicalDisplayId id) { + return refreshArgs.frameTargets.get(id); + })) { + editState().earliestPresentTime = frameTargetPtrOpt->get()->earliestPresentTime(); + editState().expectedPresentTime = frameTargetPtrOpt->get()->expectedPresentTime().ns(); + } editState().frameInterval = refreshArgs.frameInterval; editState().powerCallback = refreshArgs.powerCallback; diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp index 7fe3369f88..091c207464 100644 --- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp +++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp @@ -394,7 +394,6 @@ void OutputLayer::writeStateToHWC(bool includeGeometry, bool skipLayer, uint32_t auto requestedCompositionType = outputIndependentState->compositionType; if (requestedCompositionType == Composition::SOLID_COLOR && state.overrideInfo.buffer) { - // this should never happen, as SOLID_COLOR is skipped from caching, b/230073351 requestedCompositionType = Composition::DEVICE; } @@ -665,6 +664,9 @@ void OutputLayer::uncacheBuffers(const std::vector<uint64_t>& bufferIdsToUncache void OutputLayer::writeBufferStateToHWC(HWC2::Layer* hwcLayer, const LayerFECompositionState& outputIndependentState, bool skipLayer) { + if (skipLayer && outputIndependentState.buffer == nullptr) { + return; + } auto supportedPerFrameMetadata = getOutput().getDisplayColorProfile()->getSupportedPerFrameMetadata(); if (auto error = hwcLayer->setPerFrameMetadata(supportedPerFrameMetadata, diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp index 869dda61bb..1f53588412 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp @@ -393,12 +393,6 @@ bool CachedSet::hasProtectedLayers() const { [](const Layer& layer) { return layer.getState()->isProtected(); }); } -bool CachedSet::hasSolidColorLayers() const { - return std::any_of(mLayers.cbegin(), mLayers.cend(), [](const Layer& layer) { - return layer.getState()->hasSolidColorCompositionType(); - }); -} - bool CachedSet::cachingHintExcludesLayers() const { const bool shouldExcludeLayers = std::any_of(mLayers.cbegin(), mLayers.cend(), [](const Layer& layer) { diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp index a18397d8ba..0a5c43a99b 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp @@ -56,7 +56,7 @@ bool isSameStack(const std::vector<const LayerState*>& incomingLayers, } // Do not unflatten if source crop is only moved. - if (FlagManager::getInstance().cache_if_source_crop_layer_only_moved() && + if (FlagManager::getInstance().cache_when_source_crop_layer_only_moved() && incomingLayers[i]->isSourceCropSizeEqual(*(existingLayers[i])) && incomingLayers[i]->getDifferingFields(*(existingLayers[i])) == LayerStateField::SourceCrop) { @@ -513,13 +513,6 @@ void Flattener::buildCachedSets(time_point now) { } } - for (const CachedSet& layer : mLayers) { - if (layer.hasSolidColorLayers()) { - ATRACE_NAME("layer->hasSolidColorLayers()"); - return; - } - } - std::vector<Run> runs = findCandidateRuns(now); std::optional<Run> bestRun = findBestRun(runs); diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h index 9e35717c95..575a30e522 100644 --- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h +++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h @@ -97,13 +97,13 @@ public: MOCK_CONST_METHOD1(isConnected, bool(PhysicalDisplayId)); MOCK_CONST_METHOD2(getModes, std::vector<HWComposer::HWCDisplayMode>(PhysicalDisplayId, int32_t)); - MOCK_CONST_METHOD1(getActiveMode, std::optional<hal::HWConfigId>(PhysicalDisplayId)); + MOCK_CONST_METHOD1(getActiveMode, ftl::Expected<hal::HWConfigId, status_t>(PhysicalDisplayId)); MOCK_CONST_METHOD1(getColorModes, std::vector<ui::ColorMode>(PhysicalDisplayId)); MOCK_METHOD3(setActiveColorMode, status_t(PhysicalDisplayId, ui::ColorMode, ui::RenderIntent)); MOCK_CONST_METHOD0(isUsingVrComposer, bool()); MOCK_CONST_METHOD1(getDisplayConnectionType, ui::DisplayConnectionType(PhysicalDisplayId)); MOCK_CONST_METHOD1(isVsyncPeriodSwitchSupported, bool(PhysicalDisplayId)); - MOCK_CONST_METHOD2(getDisplayVsyncPeriod, status_t(PhysicalDisplayId, nsecs_t*)); + MOCK_CONST_METHOD1(getDisplayVsyncPeriod, ftl::Expected<nsecs_t, status_t>(PhysicalDisplayId)); MOCK_METHOD4(setActiveModeWithConstraints, status_t(PhysicalDisplayId, hal::HWConfigId, const hal::VsyncPeriodChangeConstraints&, diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index c337ca4fdb..62cfaf412c 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -4018,7 +4018,6 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, usesExpectedDisplaySettingsWithFp16Buffer) { SET_FLAG_FOR_TEST(flags::fp16_client_target, true); - ALOGE("alecmouri: %d", flags::fp16_client_target()); verify().ifMixedCompositionIs(false) .andIfUsesHdr(true) .withDisplayBrightnessNits(kDisplayLuminance) @@ -4092,7 +4091,11 @@ struct OutputComposeSurfacesTest_HandlesProtectedContent : public OutputComposeS }; TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNoProtectedContentLayers) { - mOutput.mState.isSecure = true; + if (FlagManager::getInstance().display_protected()) { + mOutput.mState.isProtected = true; + } else { + mOutput.mState.isSecure = true; + } mLayer2.mLayerFEState.hasProtectedContent = false; EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true)); @@ -4106,7 +4109,11 @@ TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNoProtectedContentLa } TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNotEnabled) { - mOutput.mState.isSecure = true; + if (FlagManager::getInstance().display_protected()) { + mOutput.mState.isProtected = true; + } else { + mOutput.mState.isSecure = true; + } mLayer2.mLayerFEState.hasProtectedContent = true; EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); @@ -4128,7 +4135,11 @@ TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNotEnabled) { } TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledEverywhere) { - mOutput.mState.isSecure = true; + if (FlagManager::getInstance().display_protected()) { + mOutput.mState.isProtected = true; + } else { + mOutput.mState.isSecure = true; + } mLayer2.mLayerFEState.hasProtectedContent = true; EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true)); @@ -4141,7 +4152,11 @@ TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledEveryw } TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledInRenderSurface) { - mOutput.mState.isSecure = true; + if (FlagManager::getInstance().display_protected()) { + mOutput.mState.isProtected = true; + } else { + mOutput.mState.isSecure = true; + } mLayer2.mLayerFEState.hasProtectedContent = true; EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true)); @@ -4220,6 +4235,7 @@ struct GenerateClientCompositionRequestsTest : public testing::Test { GenerateClientCompositionRequestsTest() { mOutput.mState.needsFiltering = false; + mOutput.mState.isProtected = true; mOutput.setDisplayColorProfileForTest( std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile)); @@ -4244,6 +4260,7 @@ struct GenerateClientCompositionRequestsTest_ThreeLayers mOutput.mState.displaySpace.setOrientation(kDisplayOrientation); mOutput.mState.needsFiltering = false; mOutput.mState.isSecure = false; + mOutput.mState.isProtected = true; for (size_t i = 0; i < mLayers.size(); i++) { mLayers[i].mOutputLayerState.clearClientTarget = false; @@ -4706,7 +4723,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, Region(kDisplayFrame), false, /* needs filtering */ false, /* secure */ - true, /* supports protected content */ + true, /* isProtected */ kDisplayViewport, kDisplayDataspace, true /* realContentIsVisible */, @@ -4719,7 +4736,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, Region(kDisplayFrame), false, /* needs filtering */ false, /* secure */ - true, /* supports protected content */ + true, /* isProtected */ kDisplayViewport, kDisplayDataspace, true /* realContentIsVisible */, @@ -4732,7 +4749,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, Region(kDisplayFrame), false, /* needs filtering */ false, /* secure */ - true, /* supports protected content */ + true, /* isProtected */ kDisplayViewport, kDisplayDataspace, true /* realContentIsVisible */, @@ -4910,7 +4927,7 @@ TEST_F(GenerateClientCompositionRequestsTest, handlesLandscapeModeSplitScreenReq Region(Rect(0, 0, 1000, 1000)), false, /* needs filtering */ true, /* secure */ - true, /* supports protected content */ + true, /* isProtected */ kPortraitViewport, kOutputDataspace, true /* realContentIsVisible */, @@ -4929,7 +4946,7 @@ TEST_F(GenerateClientCompositionRequestsTest, handlesLandscapeModeSplitScreenReq Region(Rect(1000, 0, 2000, 1000)), false, /* needs filtering */ true, /* secure */ - true, /* supports protected content */ + true, /* isProtected */ kPortraitViewport, kOutputDataspace, true /* realContentIsVisible */, diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp index 763b998b3d..d2eff75fb6 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp @@ -244,7 +244,7 @@ TEST_F(FlattenerTest, flattenLayers_ActiveLayersWithLowFpsAreFlattened) { TEST_F(FlattenerTest, unflattenLayers_onlySourceCropMoved) { SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags:: - cache_if_source_crop_layer_only_moved, + cache_when_source_crop_layer_only_moved, true); auto& layerState1 = mTestLayers[0]->layerState; diff --git a/services/surfaceflinger/Display/DisplayModeRequest.h b/services/surfaceflinger/Display/DisplayModeRequest.h index d07cdf55d2..ec3ec526a1 100644 --- a/services/surfaceflinger/Display/DisplayModeRequest.h +++ b/services/surfaceflinger/Display/DisplayModeRequest.h @@ -16,6 +16,7 @@ #pragma once +#include <android-base/stringprintf.h> #include <ftl/non_null.h> #include <scheduler/FrameRateMode.h> @@ -27,10 +28,19 @@ struct DisplayModeRequest { // Whether to emit DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE. bool emitEvent = false; + + // Whether to force the request to be applied, even if the mode is unchanged. + bool force = false; }; inline bool operator==(const DisplayModeRequest& lhs, const DisplayModeRequest& rhs) { return lhs.mode == rhs.mode && lhs.emitEvent == rhs.emitEvent; } +inline std::string to_string(const DisplayModeRequest& request) { + constexpr const char* kBool[] = {"false", "true"}; + return base::StringPrintf("{mode=%s, emitEvent=%s, force=%s}", to_string(request.mode).c_str(), + kBool[request.emitEvent], kBool[request.force]); +} + } // namespace android::display diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 950b05e1da..62f8fb16f0 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -24,6 +24,7 @@ #define ATRACE_TAG ATRACE_TAG_GRAPHICS +#include <common/FlagManager.h> #include <compositionengine/CompositionEngine.h> #include <compositionengine/Display.h> #include <compositionengine/DisplayColorProfile.h> @@ -67,6 +68,7 @@ DisplayDevice::DisplayDevice(DisplayDeviceCreationArgs& args) mActiveModeFpsTrace(concatId("ActiveModeFps")), mRenderRateFpsTrace(concatId("RenderRateFps")), mPhysicalOrientation(args.physicalOrientation), + mPowerMode(ftl::Concat("PowerMode ", getId().value).c_str(), args.initialPowerMode), mIsPrimary(args.isPrimary), mRequestedRefreshRate(args.requestedRefreshRate), mRefreshRateSelector(std::move(args.refreshRateSelector)), @@ -105,9 +107,7 @@ DisplayDevice::DisplayDevice(DisplayDeviceCreationArgs& args) mCompositionDisplay->getRenderSurface()->initialize(); - if (const auto powerModeOpt = args.initialPowerMode) { - setPowerMode(*powerModeOpt); - } + setPowerMode(args.initialPowerMode); // initialize the display orientation transform. setProjection(ui::ROTATION_0, Rect::INVALID_RECT, Rect::INVALID_RECT); @@ -172,6 +172,7 @@ auto DisplayDevice::getFrontEndInfo() const -> frontend::DisplayInfo { } void DisplayDevice::setPowerMode(hal::PowerMode mode) { + // TODO(b/241285876): Skip this for virtual displays. if (mode == hal::PowerMode::OFF || mode == hal::PowerMode::ON) { if (mStagedBrightness && mBrightness != mStagedBrightness) { getCompositionDisplay()->setNextBrightness(*mStagedBrightness); @@ -181,33 +182,26 @@ void DisplayDevice::setPowerMode(hal::PowerMode mode) { getCompositionDisplay()->applyDisplayBrightness(true); } - if (mPowerMode) { - *mPowerMode = mode; - } else { - mPowerMode.emplace("PowerMode -" + to_string(getId()), mode); - } + mPowerMode = mode; getCompositionDisplay()->setCompositionEnabled(isPoweredOn()); } void DisplayDevice::tracePowerMode() { - // assign the same value for tracing - if (mPowerMode) { - const hal::PowerMode powerMode = *mPowerMode; - *mPowerMode = powerMode; - } + // Assign the same value for tracing. + mPowerMode = mPowerMode.get(); } void DisplayDevice::enableLayerCaching(bool enable) { getCompositionDisplay()->setLayerCachingEnabled(enable); } -std::optional<hal::PowerMode> DisplayDevice::getPowerMode() const { +hal::PowerMode DisplayDevice::getPowerMode() const { return mPowerMode; } bool DisplayDevice::isPoweredOn() const { - return mPowerMode && *mPowerMode != hal::PowerMode::OFF; + return mPowerMode != hal::PowerMode::OFF; } void DisplayDevice::setActiveMode(DisplayModeId modeId, Fps vsyncRate, Fps renderFps) { @@ -221,6 +215,17 @@ void DisplayDevice::setActiveMode(DisplayModeId modeId, Fps vsyncRate, Fps rende bool DisplayDevice::initiateModeChange(display::DisplayModeRequest&& desiredMode, const hal::VsyncPeriodChangeConstraints& constraints, hal::VsyncPeriodChangeTimeline& outTimeline) { + // TODO(b/255635711): Flow the DisplayModeRequest through the desired/pending/active states. For + // now, `desiredMode` and `mDesiredModeOpt` are one and the same, but the latter is not cleared + // until the next `SF::initiateDisplayModeChanges`. However, the desired mode has been consumed + // at this point, so clear the `force` flag to prevent an endless loop of `initiateModeChange`. + if (FlagManager::getInstance().connected_display()) { + std::scoped_lock lock(mDesiredModeLock); + if (mDesiredModeOpt) { + mDesiredModeOpt->force = false; + } + } + mPendingModeOpt = std::move(desiredMode); mIsModeSetPending = true; @@ -246,10 +251,8 @@ nsecs_t DisplayDevice::getVsyncPeriodFromHWC() const { return 0; } - nsecs_t vsyncPeriod; - const auto status = mHwComposer.getDisplayVsyncPeriod(physicalId, &vsyncPeriod); - if (status == NO_ERROR) { - return vsyncPeriod; + if (const auto vsyncPeriodOpt = mHwComposer.getDisplayVsyncPeriod(physicalId).value_opt()) { + return *vsyncPeriodOpt; } return refreshRateSelector().getActiveMode().modePtr->getVsyncRate().getPeriodNsecs(); @@ -353,6 +356,10 @@ bool DisplayDevice::isSecure() const { return mCompositionDisplay->isSecure(); } +void DisplayDevice::setSecure(bool secure) { + mCompositionDisplay->setSecure(secure); +} + const Rect DisplayDevice::getBounds() const { return mCompositionDisplay->getState().displaySpace.getBoundsAsRect(); } @@ -522,29 +529,27 @@ void DisplayDevice::animateOverlay() { } } -auto DisplayDevice::setDesiredMode(display::DisplayModeRequest&& desiredMode, bool force) - -> DesiredModeAction { - ATRACE_CALL(); - - const auto& desiredModePtr = desiredMode.mode.modePtr; - - LOG_ALWAYS_FATAL_IF(getPhysicalId() != desiredModePtr->getPhysicalDisplayId(), - "DisplayId mismatch"); - - ALOGV("%s(%s)", __func__, to_string(*desiredModePtr).c_str()); +auto DisplayDevice::setDesiredMode(display::DisplayModeRequest&& desiredMode) -> DesiredModeAction { + ATRACE_NAME(concatId(__func__).c_str()); + ALOGD("%s %s", concatId(__func__).c_str(), to_string(desiredMode).c_str()); std::scoped_lock lock(mDesiredModeLock); if (mDesiredModeOpt) { // A mode transition was already scheduled, so just override the desired mode. const bool emitEvent = mDesiredModeOpt->emitEvent; + const bool force = mDesiredModeOpt->force; mDesiredModeOpt = std::move(desiredMode); mDesiredModeOpt->emitEvent |= emitEvent; + if (FlagManager::getInstance().connected_display()) { + mDesiredModeOpt->force |= force; + } return DesiredModeAction::None; } // If the desired mode is already active... const auto activeMode = refreshRateSelector().getActiveMode(); - if (!force && activeMode.modePtr->getId() == desiredModePtr->getId()) { + if (const auto& desiredModePtr = desiredMode.mode.modePtr; + !desiredMode.force && activeMode.modePtr->getId() == desiredModePtr->getId()) { if (activeMode == desiredMode.mode) { return DesiredModeAction::None; } diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index ac390cb8ff..edd57cce91 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -95,6 +95,7 @@ public: // isSecure indicates whether this display can be trusted to display // secure surfaces. bool isSecure() const; + void setSecure(bool secure); int getWidth() const; int getHeight() const; @@ -172,8 +173,8 @@ public: /* ------------------------------------------------------------------------ * Display power mode management. */ - std::optional<hardware::graphics::composer::hal::PowerMode> getPowerMode() const; - void setPowerMode(hardware::graphics::composer::hal::PowerMode mode); + hardware::graphics::composer::hal::PowerMode getPowerMode() const; + void setPowerMode(hardware::graphics::composer::hal::PowerMode); bool isPoweredOn() const; void tracePowerMode(); @@ -188,8 +189,7 @@ public: enum class DesiredModeAction { None, InitiateDisplayModeSwitch, InitiateRenderRateSwitch }; - DesiredModeAction setDesiredMode(display::DisplayModeRequest&&, bool force = false) - EXCLUDES(mDesiredModeLock); + DesiredModeAction setDesiredMode(display::DisplayModeRequest&&) EXCLUDES(mDesiredModeLock); using DisplayModeRequestOpt = ftl::Optional<display::DisplayModeRequest>; @@ -270,9 +270,7 @@ private: ui::Rotation mOrientation = ui::ROTATION_0; bool mIsOrientationChanged = false; - // Allow nullopt as initial power mode. - using TracedPowerMode = TracedOrdinal<hardware::graphics::composer::hal::PowerMode>; - std::optional<TracedPowerMode> mPowerMode; + TracedOrdinal<hardware::graphics::composer::hal::PowerMode> mPowerMode; std::optional<float> mStagedBrightness; std::optional<float> mBrightness; @@ -362,7 +360,8 @@ struct DisplayDeviceCreationArgs { HdrCapabilities hdrCapabilities; int32_t supportedPerFrameMetadata{0}; std::unordered_map<ui::ColorMode, std::vector<ui::RenderIntent>> hwcColorModes; - std::optional<hardware::graphics::composer::hal::PowerMode> initialPowerMode; + hardware::graphics::composer::hal::PowerMode initialPowerMode{ + hardware::graphics::composer::hal::PowerMode::OFF}; bool isPrimary{false}; DisplayModeId activeModeId; // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp index 6250b1155e..362ab9c39e 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp @@ -330,7 +330,11 @@ std::string AidlComposer::dumpDebugInfo() { t.join(); close(pipefds[0]); - return str; + + std::string hash; + mAidlComposer->getInterfaceHash(&hash); + return std::string(mAidlComposer->descriptor) + + " version:" + std::to_string(mComposerInterfaceVersion) + " hash:" + hash + str; } void AidlComposer::registerCallback(HWC2::ComposerCallback& callback) { diff --git a/services/surfaceflinger/DisplayHardware/DisplayMode.h b/services/surfaceflinger/DisplayHardware/DisplayMode.h index ba0825c5af..224f50e78e 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayMode.h +++ b/services/surfaceflinger/DisplayHardware/DisplayMode.h @@ -21,17 +21,17 @@ #include <android-base/stringprintf.h> #include <android/configuration.h> +#include <ftl/mixins.h> #include <ftl/small_map.h> #include <ui/DisplayId.h> #include <ui/DisplayMode.h> #include <ui/Size.h> #include <utils/Timers.h> +#include <common/FlagManager.h> #include <scheduler/Fps.h> -#include <common/FlagManager.h> #include "DisplayHardware/Hal.h" -#include "Scheduler/StrongTyping.h" namespace android { @@ -46,7 +46,12 @@ bool operator>(const DisplayModePtr&, const DisplayModePtr&) = delete; bool operator<=(const DisplayModePtr&, const DisplayModePtr&) = delete; bool operator>=(const DisplayModePtr&, const DisplayModePtr&) = delete; -using DisplayModeId = StrongTyping<ui::DisplayModeId, struct DisplayModeIdTag, Compare>; +struct DisplayModeId : ftl::DefaultConstructible<DisplayModeId, ui::DisplayModeId>, + ftl::Incrementable<DisplayModeId>, + ftl::Equatable<DisplayModeId>, + ftl::Orderable<DisplayModeId> { + using DefaultConstructible::DefaultConstructible; +}; using DisplayModes = ftl::SmallMap<DisplayModeId, DisplayModePtr, 3>; using DisplayModeIterator = DisplayModes::const_iterator; @@ -185,7 +190,7 @@ inline bool equalsExceptDisplayModeId(const DisplayMode& lhs, const DisplayMode& inline std::string to_string(const DisplayMode& mode) { return base::StringPrintf("{id=%d, hwcId=%d, resolution=%dx%d, vsyncRate=%s, " "dpi=%.2fx%.2f, group=%d, vrrConfig=%s}", - mode.getId().value(), mode.getHwcId(), mode.getWidth(), + ftl::to_underlying(mode.getId()), mode.getHwcId(), mode.getWidth(), mode.getHeight(), to_string(mode.getVsyncRate()).c_str(), mode.getDpi().x, mode.getDpi().y, mode.getGroup(), to_string(mode.getVrrConfig()).c_str()); diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp index 704ece516d..84f668d9df 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.cpp +++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp @@ -27,6 +27,7 @@ #include "HWC2.h" #include <android/configuration.h> +#include <common/FlagManager.h> #include <ui/Fence.h> #include <ui/FloatRect.h> #include <ui/GraphicBuffer.h> @@ -281,19 +282,28 @@ Error Display::getRequests(HWC2::DisplayRequest* outDisplayRequests, return Error::NONE; } -Error Display::getConnectionType(ui::DisplayConnectionType* outType) const { - if (mType != DisplayType::PHYSICAL) return Error::BAD_DISPLAY; +ftl::Expected<ui::DisplayConnectionType, hal::Error> Display::getConnectionType() const { + if (!mConnectionType) { + mConnectionType = [this]() -> decltype(mConnectionType) { + if (mType != DisplayType::PHYSICAL) { + return ftl::Unexpected(Error::BAD_DISPLAY); + } - using ConnectionType = Hwc2::IComposerClient::DisplayConnectionType; - ConnectionType connectionType; - const auto error = static_cast<Error>(mComposer.getDisplayConnectionType(mId, &connectionType)); - if (error != Error::NONE) { - return error; + using ConnectionType = Hwc2::IComposerClient::DisplayConnectionType; + ConnectionType connectionType; + + if (const auto error = static_cast<Error>( + mComposer.getDisplayConnectionType(mId, &connectionType)); + error != Error::NONE) { + return ftl::Unexpected(error); + } + + return connectionType == ConnectionType::INTERNAL ? ui::DisplayConnectionType::Internal + : ui::DisplayConnectionType::External; + }(); } - *outType = connectionType == ConnectionType::INTERNAL ? ui::DisplayConnectionType::Internal - : ui::DisplayConnectionType::External; - return Error::NONE; + return *mConnectionType; } bool Display::hasCapability(DisplayCapability capability) const { @@ -416,7 +426,14 @@ Error Display::setActiveConfigWithConstraints(hal::HWConfigId configId, VsyncPeriodChangeTimeline* outTimeline) { ALOGV("[%" PRIu64 "] setActiveConfigWithConstraints", mId); - if (isVsyncPeriodSwitchSupported()) { + // FIXME (b/319505580): At least the first config set on an external display must be + // `setActiveConfig`, so skip over the block that calls `setActiveConfigWithConstraints` + // for simplicity. + const bool connected_display = FlagManager::getInstance().connected_display(); + + if (isVsyncPeriodSwitchSupported() && + (!connected_display || + getConnectionType().value_opt() != ui::DisplayConnectionType::External)) { Hwc2::IComposerClient::VsyncPeriodChangeConstraints hwc2Constraints; hwc2Constraints.desiredTimeNanos = constraints.desiredTimeNanos; hwc2Constraints.seamlessRequired = constraints.seamlessRequired; diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h index f907061774..de044e0b76 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.h +++ b/services/surfaceflinger/DisplayHardware/HWC2.h @@ -18,6 +18,7 @@ #include <android-base/expected.h> #include <android-base/thread_annotations.h> +#include <ftl/expected.h> #include <ftl/future.h> #include <gui/HdrMetadata.h> #include <math/mat4.h> @@ -120,7 +121,8 @@ public: [[nodiscard]] virtual hal::Error getRequests( hal::DisplayRequest* outDisplayRequests, std::unordered_map<Layer*, hal::LayerRequest>* outLayerRequests) = 0; - [[nodiscard]] virtual hal::Error getConnectionType(ui::DisplayConnectionType*) const = 0; + [[nodiscard]] virtual ftl::Expected<ui::DisplayConnectionType, hal::Error> getConnectionType() + const = 0; [[nodiscard]] virtual hal::Error supportsDoze(bool* outSupport) const = 0; [[nodiscard]] virtual hal::Error getHdrCapabilities( android::HdrCapabilities* outCapabilities) const = 0; @@ -213,7 +215,7 @@ public: hal::Error getRequests( hal::DisplayRequest* outDisplayRequests, std::unordered_map<HWC2::Layer*, hal::LayerRequest>* outLayerRequests) override; - hal::Error getConnectionType(ui::DisplayConnectionType*) const override; + ftl::Expected<ui::DisplayConnectionType, hal::Error> getConnectionType() const override; hal::Error supportsDoze(bool* outSupport) const override EXCLUDES(mDisplayCapabilitiesMutex); hal::Error getHdrCapabilities(android::HdrCapabilities* outCapabilities) const override; hal::Error getOverlaySupport(aidl::android::hardware::graphics::composer3::OverlayProperties* @@ -294,6 +296,8 @@ private: const hal::HWDisplayId mId; hal::DisplayType mType; + // Cached on first call to getConnectionType. + mutable std::optional<ftl::Expected<ui::DisplayConnectionType, hal::Error>> mConnectionType; bool mIsConnected = false; using Layers = std::unordered_map<hal::HWLayerId, std::weak_ptr<HWC2::impl::Layer>>; diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp index 3ffd8ea316..cfa03397d6 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -77,6 +77,8 @@ using aidl::android::hardware::graphics::common::HdrConversionCapability; using aidl::android::hardware::graphics::common::HdrConversionStrategy; using aidl::android::hardware::graphics::composer3::Capability; using aidl::android::hardware::graphics::composer3::DisplayCapability; +using aidl::android::hardware::graphics::composer3::VrrConfig; +using namespace std::string_literals; namespace hal = android::hardware::graphics::composer::hal; namespace android { @@ -89,7 +91,8 @@ HWComposer::HWComposer(std::unique_ptr<Hwc2::Composer> composer) : mComposer(std::move(composer)), mMaxVirtualDisplayDimension(static_cast<size_t>(sysprop::max_virtual_display_dimension(0))), mUpdateDeviceProductInfoOnHotplugReconnect( - sysprop::update_device_product_info_on_hotplug_reconnect(false)) {} + sysprop::update_device_product_info_on_hotplug_reconnect(false)), + mEnableVrrTimeout(base::GetBoolProperty("debug.sf.vrr_timeout_hint_enabled"s, false)) {} HWComposer::HWComposer(const std::string& composerServiceName) : HWComposer(Hwc2::Composer::create(composerServiceName)) {} @@ -299,6 +302,10 @@ std::vector<HWComposer::HWCDisplayMode> HWComposer::getModesFromDisplayConfigura hwcMode.dpiY = config.dpi->y; } + if (!mEnableVrrTimeout) { + hwcMode.vrrConfig->notifyExpectedPresentConfig = {}; + } + modes.push_back(hwcMode); } @@ -336,14 +343,18 @@ std::vector<HWComposer::HWCDisplayMode> HWComposer::getModesFromLegacyDisplayCon return modes; } -std::optional<hal::HWConfigId> HWComposer::getActiveMode(PhysicalDisplayId displayId) const { - RETURN_IF_INVALID_DISPLAY(displayId, std::nullopt); +ftl::Expected<hal::HWConfigId, status_t> HWComposer::getActiveMode( + PhysicalDisplayId displayId) const { + RETURN_IF_INVALID_DISPLAY(displayId, ftl::Unexpected(BAD_INDEX)); const auto hwcId = *fromPhysicalDisplayId(displayId); hal::HWConfigId configId; const auto error = static_cast<hal::Error>(mComposer->getActiveConfig(hwcId, &configId)); + if (error == hal::Error::BAD_CONFIG) { + return ftl::Unexpected(NO_INIT); + } - RETURN_IF_HWC_ERROR_FOR("getActiveConfig", error, displayId, std::nullopt); + RETURN_IF_HWC_ERROR_FOR("getActiveConfig", error, displayId, ftl::Unexpected(UNKNOWN_ERROR)); return configId; } @@ -353,15 +364,13 @@ ui::DisplayConnectionType HWComposer::getDisplayConnectionType(PhysicalDisplayId RETURN_IF_INVALID_DISPLAY(displayId, ui::DisplayConnectionType::Internal); const auto& hwcDisplay = mDisplayData.at(displayId).hwcDisplay; - ui::DisplayConnectionType type; - const auto error = hwcDisplay->getConnectionType(&type); - - const auto FALLBACK_TYPE = hwcDisplay->getId() == mPrimaryHwcDisplayId - ? ui::DisplayConnectionType::Internal - : ui::DisplayConnectionType::External; - - RETURN_IF_HWC_ERROR(error, displayId, FALLBACK_TYPE); - return type; + if (const auto connectionType = hwcDisplay->getConnectionType()) { + return connectionType.value(); + } else { + LOG_HWC_ERROR(__func__, connectionType.error(), displayId); + return hwcDisplay->getId() == mPrimaryHwcDisplayId ? ui::DisplayConnectionType::Internal + : ui::DisplayConnectionType::External; + } } bool HWComposer::isVsyncPeriodSwitchSupported(PhysicalDisplayId displayId) const { @@ -369,20 +378,20 @@ bool HWComposer::isVsyncPeriodSwitchSupported(PhysicalDisplayId displayId) const return mDisplayData.at(displayId).hwcDisplay->isVsyncPeriodSwitchSupported(); } -status_t HWComposer::getDisplayVsyncPeriod(PhysicalDisplayId displayId, - nsecs_t* outVsyncPeriod) const { - RETURN_IF_INVALID_DISPLAY(displayId, 0); +ftl::Expected<nsecs_t, status_t> HWComposer::getDisplayVsyncPeriod( + PhysicalDisplayId displayId) const { + RETURN_IF_INVALID_DISPLAY(displayId, ftl::Unexpected(BAD_INDEX)); if (!isVsyncPeriodSwitchSupported(displayId)) { - return INVALID_OPERATION; + return ftl::Unexpected(INVALID_OPERATION); } + const auto hwcId = *fromPhysicalDisplayId(displayId); Hwc2::VsyncPeriodNanos vsyncPeriodNanos = 0; - auto error = + const auto error = static_cast<hal::Error>(mComposer->getDisplayVsyncPeriod(hwcId, &vsyncPeriodNanos)); - RETURN_IF_HWC_ERROR(error, displayId, 0); - *outVsyncPeriod = static_cast<nsecs_t>(vsyncPeriodNanos); - return NO_ERROR; + RETURN_IF_HWC_ERROR(error, displayId, ftl::Unexpected(UNKNOWN_ERROR)); + return static_cast<nsecs_t>(vsyncPeriodNanos); } std::vector<ui::ColorMode> HWComposer::getColorModes(PhysicalDisplayId displayId) const { diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h index 4ca528a6e7..7fbbb1a672 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.h +++ b/services/surfaceflinger/DisplayHardware/HWComposer.h @@ -25,6 +25,7 @@ #include <vector> #include <android-base/thread_annotations.h> +#include <ftl/expected.h> #include <ftl/future.h> #include <ui/DisplayIdentification.h> #include <ui/FenceTime.h> @@ -237,7 +238,7 @@ public: virtual std::vector<HWCDisplayMode> getModes(PhysicalDisplayId, int32_t maxFrameIntervalNs) const = 0; - virtual std::optional<hal::HWConfigId> getActiveMode(PhysicalDisplayId) const = 0; + virtual ftl::Expected<hal::HWConfigId, status_t> getActiveMode(PhysicalDisplayId) const = 0; virtual std::vector<ui::ColorMode> getColorModes(PhysicalDisplayId) const = 0; @@ -247,8 +248,7 @@ public: // Composer 2.4 virtual ui::DisplayConnectionType getDisplayConnectionType(PhysicalDisplayId) const = 0; virtual bool isVsyncPeriodSwitchSupported(PhysicalDisplayId) const = 0; - virtual status_t getDisplayVsyncPeriod(PhysicalDisplayId displayId, - nsecs_t* outVsyncPeriod) const = 0; + virtual ftl::Expected<nsecs_t, status_t> getDisplayVsyncPeriod(PhysicalDisplayId) const = 0; virtual status_t setActiveModeWithConstraints(PhysicalDisplayId, hal::HWConfigId, const hal::VsyncPeriodChangeConstraints&, hal::VsyncPeriodChangeTimeline* outTimeline) = 0; @@ -424,7 +424,7 @@ public: std::vector<HWCDisplayMode> getModes(PhysicalDisplayId, int32_t maxFrameIntervalNs) const override; - std::optional<hal::HWConfigId> getActiveMode(PhysicalDisplayId) const override; + ftl::Expected<hal::HWConfigId, status_t> getActiveMode(PhysicalDisplayId) const override; std::vector<ui::ColorMode> getColorModes(PhysicalDisplayId) const override; @@ -435,8 +435,7 @@ public: // Composer 2.4 ui::DisplayConnectionType getDisplayConnectionType(PhysicalDisplayId) const override; bool isVsyncPeriodSwitchSupported(PhysicalDisplayId) const override; - status_t getDisplayVsyncPeriod(PhysicalDisplayId displayId, - nsecs_t* outVsyncPeriod) const override; + ftl::Expected<nsecs_t, status_t> getDisplayVsyncPeriod(PhysicalDisplayId) const override; status_t setActiveModeWithConstraints(PhysicalDisplayId, hal::HWConfigId, const hal::VsyncPeriodChangeConstraints&, hal::VsyncPeriodChangeTimeline* outTimeline) override; @@ -491,6 +490,7 @@ public: private: // For unit tests friend TestableSurfaceFlinger; + friend HWComposerTest; struct DisplayData { std::unique_ptr<HWC2::Display> hwcDisplay; @@ -542,6 +542,7 @@ private: const size_t mMaxVirtualDisplayDimension; const bool mUpdateDeviceProductInfoOnHotplugReconnect; + bool mEnableVrrTimeout; }; } // namespace impl diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h index 31c2833229..e3d962237b 100644 --- a/services/surfaceflinger/DisplayHardware/Hal.h +++ b/services/surfaceflinger/DisplayHardware/Hal.h @@ -169,10 +169,8 @@ inline std::string to_string( out << "}, "; out << "notifyExpectedPresentConfig={"; if (vrrConfig->notifyExpectedPresentConfig) { - out << "notifyExpectedPresentHeadsUpNs=" - << vrrConfig->notifyExpectedPresentConfig->notifyExpectedPresentHeadsUpNs - << ", notifyExpectedPresentTimeoutNs=" - << vrrConfig->notifyExpectedPresentConfig->notifyExpectedPresentTimeoutNs; + out << "headsUpNs=" << vrrConfig->notifyExpectedPresentConfig->headsUpNs + << ", timeoutNs=" << vrrConfig->notifyExpectedPresentConfig->timeoutNs; } out << "}}"; return out.str(); diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp index c4ff9cc6be..12ab2c284a 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp @@ -291,6 +291,7 @@ std::vector<Capability> HidlComposer::getCapabilities() { std::string HidlComposer::dumpDebugInfo() { std::string info; + info += std::string(mComposer->descriptor) + "\n"; mComposer->dumpDebugInfo([&](const auto& tmpInfo) { info = tmpInfo.c_str(); }); return info; diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp index dd228b4523..a0c943ba72 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp @@ -31,10 +31,6 @@ #include <utils/Mutex.h> #include <utils/Trace.h> -#include <aidl/android/hardware/power/IPower.h> -#include <aidl/android/hardware/power/IPowerHintSession.h> -#include <aidl/android/hardware/power/WorkDuration.h> - #include <binder/IServiceManager.h> #include "../SurfaceFlingerProperties.h" diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h index 0276e44986..bbe51cc09d 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h @@ -25,9 +25,14 @@ #include <ui/FenceTime.h> #include <utils/Mutex.h> +// FMQ library in IPower does questionable conversions +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wconversion" #include <aidl/android/hardware/power/IPower.h> -#include <compositionengine/impl/OutputCompositionState.h> #include <powermanager/PowerHalController.h> +#pragma clang diagnostic pop + +#include <compositionengine/impl/OutputCompositionState.h> #include <scheduler/Time.h> #include <ui/DisplayIdentification.h> #include "../Scheduler/OneShotTimer.h" diff --git a/services/surfaceflinger/FpsReporter.cpp b/services/surfaceflinger/FpsReporter.cpp index 155cf4da58..a47348fbc4 100644 --- a/services/surfaceflinger/FpsReporter.cpp +++ b/services/surfaceflinger/FpsReporter.cpp @@ -26,13 +26,12 @@ namespace android { -FpsReporter::FpsReporter(frametimeline::FrameTimeline& frameTimeline, SurfaceFlinger& flinger, - std::unique_ptr<Clock> clock) - : mFrameTimeline(frameTimeline), mFlinger(flinger), mClock(std::move(clock)) { +FpsReporter::FpsReporter(frametimeline::FrameTimeline& frameTimeline, std::unique_ptr<Clock> clock) + : mFrameTimeline(frameTimeline), mClock(std::move(clock)) { LOG_ALWAYS_FATAL_IF(mClock == nullptr, "Passed in null clock when constructing FpsReporter!"); } -void FpsReporter::dispatchLayerFps() { +void FpsReporter::dispatchLayerFps(const frontend::LayerHierarchy& layerHierarchy) { const auto now = mClock->now(); if (now - mLastDispatch < kMinDispatchDuration) { return; @@ -52,31 +51,42 @@ void FpsReporter::dispatchLayerFps() { } std::unordered_set<int32_t> seenTasks; - std::vector<std::pair<TrackedListener, sp<Layer>>> listenersAndLayersToReport; + std::vector<std::pair<TrackedListener, const frontend::LayerHierarchy*>> + listenersAndLayersToReport; - mFlinger.mCurrentState.traverse([&](Layer* layer) { - auto& currentState = layer->getDrawingState(); - if (currentState.metadata.has(gui::METADATA_TASK_ID)) { - int32_t taskId = currentState.metadata.getInt32(gui::METADATA_TASK_ID, 0); + layerHierarchy.traverse([&](const frontend::LayerHierarchy& hierarchy, + const frontend::LayerHierarchy::TraversalPath& traversalPath) { + if (traversalPath.variant == frontend::LayerHierarchy::Variant::Detached) { + return false; + } + const auto& metadata = hierarchy.getLayer()->metadata; + if (metadata.has(gui::METADATA_TASK_ID)) { + int32_t taskId = metadata.getInt32(gui::METADATA_TASK_ID, 0); if (seenTasks.count(taskId) == 0) { // localListeners is expected to be tiny for (TrackedListener& listener : localListeners) { if (listener.taskId == taskId) { seenTasks.insert(taskId); - listenersAndLayersToReport.push_back( - {listener, sp<Layer>::fromExisting(layer)}); + listenersAndLayersToReport.push_back({listener, &hierarchy}); break; } } } } + return true; }); - for (const auto& [listener, layer] : listenersAndLayersToReport) { + for (const auto& [listener, hierarchy] : listenersAndLayersToReport) { std::unordered_set<int32_t> layerIds; - layer->traverse(LayerVector::StateSet::Current, - [&](Layer* layer) { layerIds.insert(layer->getSequence()); }); + hierarchy->traverse([&](const frontend::LayerHierarchy& hierarchy, + const frontend::LayerHierarchy::TraversalPath& traversalPath) { + if (traversalPath.variant == frontend::LayerHierarchy::Variant::Detached) { + return false; + } + layerIds.insert(static_cast<int32_t>(hierarchy.getLayer()->id)); + return true; + }); listener.listener->onFpsReported(mFrameTimeline.computeFps(layerIds)); } diff --git a/services/surfaceflinger/FpsReporter.h b/services/surfaceflinger/FpsReporter.h index 438b1aa362..01f1e07b26 100644 --- a/services/surfaceflinger/FpsReporter.h +++ b/services/surfaceflinger/FpsReporter.h @@ -24,6 +24,7 @@ #include "Clock.h" #include "FrameTimeline/FrameTimeline.h" +#include "FrontEnd/LayerHierarchy.h" #include "WpHash.h" namespace android { @@ -33,13 +34,13 @@ class SurfaceFlinger; class FpsReporter : public IBinder::DeathRecipient { public: - FpsReporter(frametimeline::FrameTimeline& frameTimeline, SurfaceFlinger& flinger, + FpsReporter(frametimeline::FrameTimeline& frameTimeline, std::unique_ptr<Clock> clock = std::make_unique<SteadyClock>()); // Dispatches updated layer fps values for the registered listeners // This method promotes Layer weak pointers and performs layer stack traversals, so mStateLock // must be held when calling this method. - void dispatchLayerFps() EXCLUDES(mMutex); + void dispatchLayerFps(const frontend::LayerHierarchy&) EXCLUDES(mMutex); // Override for IBinder::DeathRecipient void binderDied(const wp<IBinder>&) override; @@ -58,7 +59,6 @@ private: }; frametimeline::FrameTimeline& mFrameTimeline; - SurfaceFlinger& mFlinger; static const constexpr std::chrono::steady_clock::duration kMinDispatchDuration = std::chrono::milliseconds(500); std::unique_ptr<Clock> mClock; diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp index 803299cf6f..d0e2d7a451 100644 --- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp +++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp @@ -390,6 +390,22 @@ void SurfaceFrame::setGpuComposition() { mGpuComposition = true; } +// TODO(b/316171339): migrate from perfetto side +bool SurfaceFrame::isSelfJanky() const { + int32_t jankType = getJankType().value_or(JankType::None); + + if (jankType == JankType::None) { + return false; + } + + int32_t jankBitmask = JankType::AppDeadlineMissed | JankType::Unknown; + if (jankType & jankBitmask) { + return true; + } + + return false; +} + std::optional<int32_t> SurfaceFrame::getJankType() const { std::scoped_lock lock(mMutex); if (mPresentState == PresentState::Dropped) { @@ -1113,20 +1129,23 @@ void FrameTimeline::DisplayFrame::tracePredictions(pid_t surfaceFlingerPid, } void FrameTimeline::DisplayFrame::addSkippedFrame(pid_t surfaceFlingerPid, nsecs_t monoBootOffset, - nsecs_t previousActualPresentTime) const { + nsecs_t previousPredictionPresentTime) const { nsecs_t skippedFrameStartTime = 0, skippedFramePresentTime = 0; const constexpr float kThresh = 0.5f; const constexpr float kRange = 1.5f; for (auto& surfaceFrame : mSurfaceFrames) { - if (previousActualPresentTime != 0 && - static_cast<float>(mSurfaceFlingerActuals.presentTime - previousActualPresentTime) >= + if (previousPredictionPresentTime != 0 && + static_cast<float>(mSurfaceFlingerPredictions.presentTime - + previousPredictionPresentTime) >= static_cast<float>(mRenderRate.getPeriodNsecs()) * kRange && static_cast<float>(surfaceFrame->getPredictions().presentTime) <= - (static_cast<float>(mSurfaceFlingerActuals.presentTime) - + (static_cast<float>(mSurfaceFlingerPredictions.presentTime) - kThresh * static_cast<float>(mRenderRate.getPeriodNsecs())) && static_cast<float>(surfaceFrame->getPredictions().presentTime) >= - (static_cast<float>(previousActualPresentTime) - - kThresh * static_cast<float>(mRenderRate.getPeriodNsecs()))) { + (static_cast<float>(previousPredictionPresentTime) - + kThresh * static_cast<float>(mRenderRate.getPeriodNsecs())) && + // sf skipped frame is not considered if app is self janked + !surfaceFrame->isSelfJanky()) { skippedFrameStartTime = surfaceFrame->getPredictions().endTime; skippedFramePresentTime = surfaceFrame->getPredictions().presentTime; break; @@ -1215,18 +1234,18 @@ void FrameTimeline::DisplayFrame::traceActuals(pid_t surfaceFlingerPid, }); } -void FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset, - nsecs_t previousActualPresentTime) const { +nsecs_t FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset, + nsecs_t previousPredictionPresentTime) const { if (mSurfaceFrames.empty()) { // We don't want to trace display frames without any surface frames updates as this cannot // be janky - return; + return previousPredictionPresentTime; } if (mToken == FrameTimelineInfo::INVALID_VSYNC_ID) { // DisplayFrame should not have an invalid token. ALOGE("Cannot trace DisplayFrame with invalid token"); - return; + return previousPredictionPresentTime; } if (mPredictionState == PredictionState::Valid) { @@ -1241,8 +1260,9 @@ void FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBoo } if (FlagManager::getInstance().add_sf_skipped_frames_to_trace()) { - addSkippedFrame(surfaceFlingerPid, monoBootOffset, previousActualPresentTime); + addSkippedFrame(surfaceFlingerPid, monoBootOffset, previousPredictionPresentTime); } + return mSurfaceFlingerPredictions.presentTime; } float FrameTimeline::computeFps(const std::unordered_set<int32_t>& layerIds) { @@ -1333,8 +1353,9 @@ void FrameTimeline::flushPendingPresentFences() { const auto& pendingPresentFence = *mPendingPresentFences.begin(); const nsecs_t signalTime = Fence::SIGNAL_TIME_INVALID; auto& displayFrame = pendingPresentFence.second; - displayFrame->onPresent(signalTime, mPreviousPresentTime); - displayFrame->trace(mSurfaceFlingerPid, monoBootOffset, mPreviousPresentTime); + displayFrame->onPresent(signalTime, mPreviousActualPresentTime); + mPreviousPredictionPresentTime = displayFrame->trace(mSurfaceFlingerPid, monoBootOffset, + mPreviousPredictionPresentTime); mPendingPresentFences.erase(mPendingPresentFences.begin()); } @@ -1349,9 +1370,10 @@ void FrameTimeline::flushPendingPresentFences() { } auto& displayFrame = pendingPresentFence.second; - displayFrame->onPresent(signalTime, mPreviousPresentTime); - displayFrame->trace(mSurfaceFlingerPid, monoBootOffset, mPreviousPresentTime); - mPreviousPresentTime = signalTime; + displayFrame->onPresent(signalTime, mPreviousActualPresentTime); + mPreviousPredictionPresentTime = displayFrame->trace(mSurfaceFlingerPid, monoBootOffset, + mPreviousPredictionPresentTime); + mPreviousActualPresentTime = signalTime; mPendingPresentFences.erase(mPendingPresentFences.begin() + static_cast<int>(i)); --i; diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h index b5047a3467..a76f7d477a 100644 --- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h +++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h @@ -165,6 +165,8 @@ public: TraceCookieCounter* traceCookieCounter, bool isBuffer, GameMode); ~SurfaceFrame() = default; + bool isSelfJanky() const; + // Returns std::nullopt if the frame hasn't been classified yet. // Used by both SF and FrameTimeline. std::optional<int32_t> getJankType() const; @@ -381,8 +383,8 @@ public: // Emits a packet for perfetto tracing. The function body will be executed only if tracing // is enabled. monoBootOffset is the difference between SYSTEM_TIME_BOOTTIME // and SYSTEM_TIME_MONOTONIC. - void trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset, - nsecs_t previousActualPresentTime) const; + nsecs_t trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset, + nsecs_t previousPredictionPresentTime) const; // Sets the token, vsyncPeriod, predictions and SF start time. void onSfWakeUp(int64_t token, Fps refreshRate, Fps renderRate, std::optional<TimelineItem> predictions, nsecs_t wakeUpTime); @@ -508,7 +510,8 @@ private: uint32_t mMaxDisplayFrames; std::shared_ptr<TimeStats> mTimeStats; const pid_t mSurfaceFlingerPid; - nsecs_t mPreviousPresentTime = 0; + nsecs_t mPreviousActualPresentTime = 0; + nsecs_t mPreviousPredictionPresentTime = 0; const JankClassificationThresholds mJankClassificationThresholds; static constexpr uint32_t kDefaultMaxDisplayFrames = 64; // The initial container size for the vector<SurfaceFrames> inside display frame. Although diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp index 1e5a6fbd1e..821ac0cf88 100644 --- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp @@ -190,8 +190,12 @@ bool LayerHierarchy::hasRelZLoop(uint32_t& outInvalidRelativeRoot) const { return outInvalidRelativeRoot != UNASSIGNED_LAYER_ID; } -LayerHierarchyBuilder::LayerHierarchyBuilder( - const std::vector<std::unique_ptr<RequestedLayerState>>& layers) { +void LayerHierarchyBuilder::init(const std::vector<std::unique_ptr<RequestedLayerState>>& layers) { + mLayerIdToHierarchy.clear(); + mHierarchies.clear(); + mRoot = nullptr; + mOffscreenRoot = nullptr; + mHierarchies.reserve(layers.size()); mLayerIdToHierarchy.reserve(layers.size()); for (auto& layer : layers) { @@ -202,6 +206,7 @@ LayerHierarchyBuilder::LayerHierarchyBuilder( onLayerAdded(layer.get()); } detachHierarchyFromRelativeParent(&mOffscreenRoot); + mInitialized = true; } void LayerHierarchyBuilder::attachToParent(LayerHierarchy* hierarchy) { @@ -332,7 +337,7 @@ void LayerHierarchyBuilder::updateMirrorLayer(RequestedLayerState* layer) { } } -void LayerHierarchyBuilder::update( +void LayerHierarchyBuilder::doUpdate( const std::vector<std::unique_ptr<RequestedLayerState>>& layers, const std::vector<std::unique_ptr<RequestedLayerState>>& destroyedLayers) { // rebuild map @@ -381,6 +386,32 @@ void LayerHierarchyBuilder::update( attachHierarchyToRelativeParent(&mRoot); } +void LayerHierarchyBuilder::update(LayerLifecycleManager& layerLifecycleManager) { + if (!mInitialized) { + ATRACE_NAME("LayerHierarchyBuilder:init"); + init(layerLifecycleManager.getLayers()); + } else if (layerLifecycleManager.getGlobalChanges().test( + RequestedLayerState::Changes::Hierarchy)) { + ATRACE_NAME("LayerHierarchyBuilder:update"); + doUpdate(layerLifecycleManager.getLayers(), layerLifecycleManager.getDestroyedLayers()); + } else { + return; // nothing to do + } + + uint32_t invalidRelativeRoot; + bool hasRelZLoop = mRoot.hasRelZLoop(invalidRelativeRoot); + while (hasRelZLoop) { + ATRACE_NAME("FixRelZLoop"); + TransactionTraceWriter::getInstance().invoke("relz_loop_detected", + /*overwrite=*/false); + layerLifecycleManager.fixRelativeZLoop(invalidRelativeRoot); + // reinitialize the hierarchy with the updated layer data + init(layerLifecycleManager.getLayers()); + // check if we have any remaining loops + hasRelZLoop = mRoot.hasRelZLoop(invalidRelativeRoot); + } +} + const LayerHierarchy& LayerHierarchyBuilder::getHierarchy() const { return mRoot; } diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h index ba2e262baf..a1c73c38b0 100644 --- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h @@ -17,6 +17,7 @@ #pragma once #include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/LayerLifecycleManager.h" #include "RequestedLayerState.h" #include "ftl/small_vector.h" @@ -197,9 +198,8 @@ private: // hierarchy from a list of RequestedLayerState and associated change flags. class LayerHierarchyBuilder { public: - LayerHierarchyBuilder(const std::vector<std::unique_ptr<RequestedLayerState>>&); - void update(const std::vector<std::unique_ptr<RequestedLayerState>>& layers, - const std::vector<std::unique_ptr<RequestedLayerState>>& destroyedLayers); + LayerHierarchyBuilder() = default; + void update(LayerLifecycleManager& layerLifecycleManager); LayerHierarchy getPartialHierarchy(uint32_t, bool childrenOnly) const; const LayerHierarchy& getHierarchy() const; const LayerHierarchy& getOffscreenHierarchy() const; @@ -213,14 +213,18 @@ private: void detachFromRelativeParent(LayerHierarchy*); void attachHierarchyToRelativeParent(LayerHierarchy*); void detachHierarchyFromRelativeParent(LayerHierarchy*); - + void init(const std::vector<std::unique_ptr<RequestedLayerState>>&); + void doUpdate(const std::vector<std::unique_ptr<RequestedLayerState>>& layers, + const std::vector<std::unique_ptr<RequestedLayerState>>& destroyedLayers); void onLayerDestroyed(RequestedLayerState* layer); void updateMirrorLayer(RequestedLayerState* layer); LayerHierarchy* getHierarchyFromId(uint32_t layerId, bool crashOnFailure = true); + std::unordered_map<uint32_t, LayerHierarchy*> mLayerIdToHierarchy; std::vector<std::unique_ptr<LayerHierarchy>> mHierarchies; LayerHierarchy mRoot{nullptr}; LayerHierarchy mOffscreenRoot{nullptr}; + bool mInitialized = false; }; } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp index 38974a2f58..ea06cf6de6 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp @@ -315,6 +315,7 @@ std::ostream& operator<<(std::ostream& out, const LayerSnapshot& obj) { if (obj.hasInputInfo()) { out << "\n input{" << "(" << obj.inputInfo.inputConfig.string() << ")"; + if (obj.inputInfo.canOccludePresentation) out << " canOccludePresentation"; if (obj.touchCropId != UNASSIGNED_LAYER_ID) out << " touchCropId=" << obj.touchCropId; if (obj.inputInfo.replaceTouchableRegionWithCrop) out << " replaceTouchableRegionWithCrop"; auto touchableRegion = obj.inputInfo.touchableRegion.getBounds(); @@ -380,6 +381,9 @@ void LayerSnapshot::merge(const RequestedLayerState& requested, bool forceUpdate currentHdrSdrRatio = requested.currentHdrSdrRatio; desiredHdrSdrRatio = requested.desiredHdrSdrRatio; } + if (forceUpdate || requested.what & layer_state_t::eDesiredHdrHeadroomChanged) { + desiredHdrSdrRatio = requested.desiredHdrSdrRatio; + } if (forceUpdate || requested.what & layer_state_t::eCachingHintChanged) { cachingHint = requested.cachingHint; } diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index ad5e42be37..0966fe0496 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -587,8 +587,8 @@ LayerSnapshot* LayerSnapshotBuilder::createSnapshot(const LayerHierarchy::Traver bool LayerSnapshotBuilder::sortSnapshotsByZ(const Args& args) { if (!mResortSnapshots && args.forceUpdate == ForceUpdateFlags::NONE && !args.layerLifecycleManager.getGlobalChanges().any( - RequestedLayerState::Changes::Hierarchy | - RequestedLayerState::Changes::Visibility)) { + RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Visibility | + RequestedLayerState::Changes::Input)) { // We are not force updating and there are no hierarchy or visibility changes. Avoid sorting // the snapshots. return false; @@ -1044,6 +1044,8 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, snapshot.inputInfo.touchOcclusionMode = requested.hasInputInfo() ? requested.windowInfoHandle->getInfo()->touchOcclusionMode : parentSnapshot.inputInfo.touchOcclusionMode; + snapshot.inputInfo.canOccludePresentation = parentSnapshot.inputInfo.canOccludePresentation || + (requested.flags & layer_state_t::eCanOccludePresentation); if (requested.dropInputMode == gui::DropInputMode::ALL || parentSnapshot.dropInputMode == gui::DropInputMode::ALL) { snapshot.dropInputMode = gui::DropInputMode::ALL; @@ -1217,8 +1219,8 @@ void LayerSnapshotBuilder::updateTouchableRegionCrop(const Args& args) { Rect inputBoundsInDisplaySpace = getInputBoundsInDisplaySpace(*cropLayerSnapshot, inputBounds, displayInfo.transform); - snapshot->inputInfo.touchableRegion = snapshot->inputInfo.touchableRegion.intersect( - displayInfo.transform.transform(inputBoundsInDisplaySpace)); + snapshot->inputInfo.touchableRegion = + snapshot->inputInfo.touchableRegion.intersect(inputBoundsInDisplaySpace); } // If the layer is a clone, we need to crop the input region to cloned root to prevent diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index 209df79f8e..cb0e2a1938 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -98,7 +98,7 @@ RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args) z = 0; layerStack = ui::DEFAULT_LAYER_STACK; transformToDisplayInverse = false; - desiredHdrSdrRatio = 1.f; + desiredHdrSdrRatio = -1.f; currentHdrSdrRatio = 1.f; dataspaceRequested = false; hdrMetadata.validTypes = 0; @@ -170,6 +170,9 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta if ((oldFlags ^ flags) & layer_state_t::eIgnoreDestinationFrame) { changes |= RequestedLayerState::Changes::Geometry; } + if ((oldFlags ^ flags) & layer_state_t::eCanOccludePresentation) { + changes |= RequestedLayerState::Changes::Input; + } } if (clientState.what & layer_state_t::eBufferChanged) { @@ -603,7 +606,8 @@ bool RequestedLayerState::isSimpleBufferUpdate(const layer_state_t& s) const { layer_state_t::eShadowRadiusChanged | layer_state_t::eFixedTransformHintChanged | layer_state_t::eTrustedOverlayChanged | layer_state_t::eStretchChanged | layer_state_t::eBufferCropChanged | layer_state_t::eDestinationFrameChanged | - layer_state_t::eDimmingEnabledChanged | layer_state_t::eExtendedRangeBrightnessChanged; + layer_state_t::eDimmingEnabledChanged | layer_state_t::eExtendedRangeBrightnessChanged | + layer_state_t::eDesiredHdrHeadroomChanged; if (changedFlags & deniedChanges) { ATRACE_FORMAT_INSTANT("%s: false [has denied changes flags 0x%" PRIx64 "]", __func__, s.what & deniedChanges); diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index c8b1059b94..736fec6fb2 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -78,11 +78,13 @@ #include "SurfaceFlinger.h" #include "TimeStats/TimeStats.h" #include "TunnelModeEnabledReporter.h" +#include "Utils/FenceUtils.h" #define DEBUG_RESIZE 0 #define EARLY_RELEASE_ENABLED false namespace android { +using namespace std::chrono_literals; namespace { constexpr int kDumpTableRowLength = 159; @@ -2911,7 +2913,8 @@ void Layer::callReleaseBufferCallback(const sp<ITransactionCompletedListener>& l } void Layer::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult, - ui::LayerStack layerStack) { + ui::LayerStack layerStack, + std::function<FenceResult(FenceResult)>&& continuation) { // If we are displayed on multiple displays in a single composition cycle then we would // need to do careful tracking to enable the use of the mLastClientCompositionFence. // For example we can only use it if all the displays are client comp, and we need @@ -2946,11 +2949,41 @@ void Layer::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult, break; } } + + if (!FlagManager::getInstance().screenshot_fence_preservation() && continuation) { + futureFenceResult = ftl::Future(futureFenceResult).then(std::move(continuation)).share(); + } + if (ch != nullptr) { ch->previousReleaseCallbackId = mPreviousReleaseCallbackId; ch->previousReleaseFences.emplace_back(std::move(futureFenceResult)); ch->name = mName; + } else if (FlagManager::getInstance().screenshot_fence_preservation()) { + // If we didn't get a release callback yet, e.g. some scenarios when capturing screenshots + // asynchronously, then make sure we don't drop the fence. + mAdditionalPreviousReleaseFences.emplace_back(std::move(futureFenceResult), + std::move(continuation)); + std::vector<FenceAndContinuation> mergedFences; + sp<Fence> prevFence = nullptr; + // For a layer that's frequently screenshotted, try to merge fences to make sure we don't + // grow unbounded. + for (const auto& futureAndContinution : mAdditionalPreviousReleaseFences) { + auto result = futureAndContinution.future.wait_for(0s); + if (result != std::future_status::ready) { + mergedFences.emplace_back(futureAndContinution); + continue; + } + + mergeFence(getDebugName(), futureAndContinution.chain().get().value_or(Fence::NO_FENCE), + prevFence); + } + if (prevFence != nullptr) { + mergedFences.emplace_back(ftl::yield(FenceResult(std::move(prevFence))).share()); + } + + mAdditionalPreviousReleaseFences.swap(mergedFences); } + if (mBufferInfo.mBuffer) { mPreviouslyPresentedLayerStacks.push_back(layerStack); } @@ -3362,6 +3395,14 @@ bool Layer::setExtendedRangeBrightness(float currentBufferRatio, float desiredRa return true; } +bool Layer::setDesiredHdrHeadroom(float desiredRatio) { + if (mDrawingState.desiredHdrSdrRatio == desiredRatio) return false; + mDrawingState.desiredHdrSdrRatio = desiredRatio; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + bool Layer::setCachingHint(gui::CachingHint cachingHint) { if (mDrawingState.cachingHint == cachingHint) return false; mDrawingState.cachingHint = cachingHint; @@ -3440,6 +3481,15 @@ bool Layer::setTransactionCompletedListeners(const std::vector<sp<CallbackHandle handle->acquireTimeOrFence = mCallbackHandleAcquireTimeOrFence; handle->frameNumber = mDrawingState.frameNumber; handle->previousFrameNumber = mDrawingState.previousFrameNumber; + if (FlagManager::getInstance().screenshot_fence_preservation() && + mPreviousReleaseBufferEndpoint == handle->listener) { + // Add fences from previous screenshots now so that they can be dispatched to the + // client. + for (const auto& futureAndContinution : mAdditionalPreviousReleaseFences) { + handle->previousReleaseFences.emplace_back(futureAndContinution.chain()); + } + mAdditionalPreviousReleaseFences.clear(); + } // Store so latched time and release fence can be set mDrawingState.callbackHandles.push_back(handle); @@ -3949,6 +3999,13 @@ bool Layer::isSimpleBufferUpdate(const layer_state_t& s) const { } } + if (s.what & layer_state_t::eDesiredHdrHeadroomChanged) { + if (mDrawingState.desiredHdrSdrRatio != s.desiredHdrSdrRatio) { + ATRACE_FORMAT_INSTANT("%s: false [eDesiredHdrHeadroomChanged changed]", __func__); + return false; + } + } + return true; } diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index c772e0ea1b..0ceecec7ec 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -233,7 +233,7 @@ public: bool autoRefresh = false; bool dimmingEnabled = true; float currentHdrSdrRatio = 1.f; - float desiredHdrSdrRatio = 1.f; + float desiredHdrSdrRatio = -1.f; gui::CachingHint cachingHint = gui::CachingHint::Enabled; int64_t latchedVsyncId = 0; bool useVsyncIdForRefreshRateSelection = false; @@ -317,6 +317,7 @@ public: void setDesiredPresentTime(nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/); bool setDataspace(ui::Dataspace /*dataspace*/); bool setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio); + bool setDesiredHdrHeadroom(float desiredRatio); bool setCachingHint(gui::CachingHint cachingHint); bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/); bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/); @@ -546,7 +547,7 @@ public: sp<IBinder> mReleaseBufferEndpoint; bool mFrameLatencyNeeded{false}; - float mDesiredHdrSdrRatio = 1.f; + float mDesiredHdrSdrRatio = -1.f; }; BufferInfo mBufferInfo; @@ -555,7 +556,8 @@ public: const compositionengine::LayerFECompositionState* getCompositionState() const; bool fenceHasSignaled() const; void onPreComposition(nsecs_t refreshStartTime); - void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack layerStack); + void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack layerStack, + std::function<FenceResult(FenceResult)>&& continuation = nullptr); void setWasClientComposed(const sp<Fence>& fence) { mLastClientCompositionFence = fence; @@ -932,6 +934,19 @@ public: // the release fences from the correct displays when we release the last buffer // from the layer. std::vector<ui::LayerStack> mPreviouslyPresentedLayerStacks; + struct FenceAndContinuation { + ftl::SharedFuture<FenceResult> future; + std::function<FenceResult(FenceResult)> continuation; + + ftl::SharedFuture<FenceResult> chain() const { + if (continuation) { + return ftl::Future(future).then(continuation).share(); + } else { + return future; + } + } + }; + std::vector<FenceAndContinuation> mAdditionalPreviousReleaseFences; // Exposed so SurfaceFlinger can assert that it's held const sp<SurfaceFlinger> mFlinger; diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index 693a357de5..96eccf290f 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -100,6 +100,11 @@ std::string toString(const DisplayEventReceiver::Event& event) { case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE: return StringPrintf("ModeChanged{displayId=%s, modeId=%u}", to_string(event.header.displayId).c_str(), event.modeChange.modeId); + case DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE: + return StringPrintf("HdcpLevelsChange{displayId=%s, connectedLevel=%d, maxLevel=%d}", + to_string(event.header.displayId).c_str(), + event.hdcpLevelsChange.connectedLevel, + event.hdcpLevelsChange.maxLevel); default: return "Event{}"; } @@ -143,7 +148,7 @@ DisplayEventReceiver::Event makeModeChanged(const scheduler::FrameRateMode& mode DisplayEventReceiver::Event event; event.header = {DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE, mode.modePtr->getPhysicalDisplayId(), systemTime()}; - event.modeChange.modeId = mode.modePtr->getId().value(); + event.modeChange.modeId = ftl::to_underlying(mode.modePtr->getId()); event.modeChange.vsyncPeriod = mode.fps.getPeriodNsecs(); return event; } @@ -170,6 +175,20 @@ DisplayEventReceiver::Event makeFrameRateOverrideFlushEvent(PhysicalDisplayId di }}; } +DisplayEventReceiver::Event makeHdcpLevelsChange(PhysicalDisplayId displayId, + int32_t connectedLevel, int32_t maxLevel) { + return DisplayEventReceiver::Event{ + .header = + DisplayEventReceiver::Event::Header{ + .type = DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE, + .displayId = displayId, + .timestamp = systemTime(), + }, + .hdcpLevelsChange.connectedLevel = connectedLevel, + .hdcpLevelsChange.maxLevel = maxLevel, + }; +} + } // namespace EventThreadConnection::EventThreadConnection(EventThread* eventThread, uid_t callingUid, @@ -301,7 +320,7 @@ void EventThread::setDuration(std::chrono::nanoseconds workDuration, mVsyncRegistration.update({.workDuration = mWorkDuration.get().count(), .readyDuration = mReadyDuration.count(), - .earliestVsync = mLastVsyncCallbackTime.ns()}); + .lastVsync = mLastVsyncCallbackTime.ns()}); } sp<EventThreadConnection> EventThread::createEventConnection( @@ -385,6 +404,9 @@ VsyncEventData EventThread::getLatestVsyncEventData( }(); generateFrameTimeline(vsyncEventData, frameInterval.ns(), systemTime(SYSTEM_TIME_MONOTONIC), presentTime, deadline); + if (FlagManager::getInstance().vrr_config()) { + mCallback.onExpectedPresentTimePosted(TimePoint::fromNs(presentTime)); + } return vsyncEventData; } @@ -442,6 +464,14 @@ void EventThread::onFrameRateOverridesChanged(PhysicalDisplayId displayId, mCondition.notify_all(); } +void EventThread::onHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel, + int32_t maxLevel) { + std::lock_guard<std::mutex> lock(mMutex); + + mPendingEvents.push_back(makeHdcpLevelsChange(displayId, connectedLevel, maxLevel)); + mCondition.notify_all(); +} + void EventThread::threadMain(std::unique_lock<std::mutex>& lock) { DisplayEventConsumers consumers; @@ -501,7 +531,7 @@ void EventThread::threadMain(std::unique_lock<std::mutex>& lock) { const auto scheduleResult = mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(), .readyDuration = mReadyDuration.count(), - .earliestVsync = mLastVsyncCallbackTime.ns()}); + .lastVsync = mLastVsyncCallbackTime.ns()}); LOG_ALWAYS_FATAL_IF(!scheduleResult, "Error scheduling callback"); } else { mVsyncRegistration.cancel(); @@ -557,6 +587,9 @@ bool EventThread::shouldConsumeEvent(const DisplayEventReceiver::Event& event, case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG: return true; + case DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE: + return true; + case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE: { return connection->mEventRegistration.test( gui::ISurfaceComposer::EventRegistration::modeChanged); @@ -691,6 +724,11 @@ void EventThread::dispatchEvent(const DisplayEventReceiver::Event& event, removeDisplayEventConnectionLocked(consumer); } } + if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC && + FlagManager::getInstance().vrr_config()) { + mCallback.onExpectedPresentTimePosted( + TimePoint::fromNs(event.vsync.vsyncData.preferredExpectedPresentationTime())); + } } void EventThread::dump(std::string& result) const { @@ -757,7 +795,7 @@ scheduler::VSyncCallbackRegistration EventThread::onNewVsyncScheduleInternal( if (reschedule) { mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(), .readyDuration = mReadyDuration.count(), - .earliestVsync = mLastVsyncCallbackTime.ns()}); + .lastVsync = mLastVsyncCallbackTime.ns()}); } return oldRegistration; } diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h index 7842318e2e..90e61a984b 100644 --- a/services/surfaceflinger/Scheduler/EventThread.h +++ b/services/surfaceflinger/Scheduler/EventThread.h @@ -131,6 +131,9 @@ public: const sp<EventThreadConnection>& connection) const = 0; virtual void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) = 0; + + virtual void onHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel, + int32_t maxLevel) = 0; }; struct IEventThreadCallback { @@ -139,6 +142,7 @@ struct IEventThreadCallback { virtual bool throttleVsync(TimePoint, uid_t) = 0; virtual Period getVsyncPeriod(uid_t) = 0; virtual void resync() = 0; + virtual void onExpectedPresentTimePosted(TimePoint) = 0; }; namespace impl { @@ -177,6 +181,9 @@ public: void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) override EXCLUDES(mMutex); + void onHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel, + int32_t maxLevel) override; + private: friend EventThreadTest; diff --git a/services/surfaceflinger/Scheduler/ISchedulerCallback.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h index 3b61de7e83..9f4f5b6d39 100644 --- a/services/surfaceflinger/Scheduler/ISchedulerCallback.h +++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.h @@ -30,6 +30,8 @@ struct ISchedulerCallback { virtual void kernelTimerChanged(bool expired) = 0; virtual void triggerOnFrameRateOverridesChanged() = 0; virtual void onChoreographerAttached() = 0; + virtual void onExpectedPresentTimePosted(TimePoint, ftl::NonNull<DisplayModePtr>, + Fps renderRate) = 0; protected: ~ISchedulerCallback() = default; diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp index 5ce883ce39..dcb62546a6 100644 --- a/services/surfaceflinger/Scheduler/LayerHistory.cpp +++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp @@ -221,9 +221,8 @@ auto LayerHistory::summarize(const RefreshRateSelector& selector, nsecs_t now) - const std::string categoryString = vote.category == FrameRateCategory::Default ? "" : base::StringPrintf("category=%s", ftl::enum_string(vote.category).c_str()); - ATRACE_FORMAT_INSTANT("%s %s %s (%d%)", ftl::enum_string(vote.type).c_str(), - to_string(vote.fps).c_str(), categoryString.c_str(), - weight * 100); + ATRACE_FORMAT_INSTANT("%s %s %s (%.2f)", ftl::enum_string(vote.type).c_str(), + to_string(vote.fps).c_str(), categoryString.c_str(), weight); summary.push_back({info->getName(), info->getOwnerUid(), vote.type, vote.fps, vote.seamlessness, vote.category, vote.categorySmoothSwitchOnly, weight, layerFocused}); diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp index 97fca395b4..9745452e89 100644 --- a/services/surfaceflinger/Scheduler/LayerInfo.cpp +++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp @@ -62,6 +62,10 @@ void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUp mLastAnimationTime = std::max(lastPresentTime, now); break; case LayerUpdateType::SetFrameRate: + if (FlagManager::getInstance().vrr_config()) { + break; + } + FALLTHROUGH_INTENDED; case LayerUpdateType::Buffer: FrameTimeData frameTime = {.presentTime = lastPresentTime, .queueTime = mLastUpdatedTime, @@ -323,7 +327,8 @@ LayerInfo::RefreshRateVotes LayerInfo::getRefreshRateVote(const RefreshRateSelec mLayerVote.type != LayerHistory::LayerVoteType::ExplicitDefault) { ATRACE_FORMAT_INSTANT("Vote %s", ftl::enum_string(mLayerVote.type).c_str()); ALOGV("%s voted %d", mName.c_str(), static_cast<int>(mLayerVote.type)); - votes.push_back(mLayerVote); + votes.push_back({mLayerVote.type, mLayerVote.fps, mLayerVote.seamlessness, + FrameRateCategory::Default, mLayerVote.categorySmoothSwitchOnly}); } return votes; @@ -528,6 +533,8 @@ FrameRateCategory LayerInfo::FrameRate::convertCategory(int8_t category) { return FrameRateCategory::Low; case ANATIVEWINDOW_FRAME_RATE_CATEGORY_NORMAL: return FrameRateCategory::Normal; + case ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH_HINT: + return FrameRateCategory::HighHint; case ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH: return FrameRateCategory::High; default: diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp index 18c0a696d5..a145e59eb5 100644 --- a/services/surfaceflinger/Scheduler/MessageQueue.cpp +++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp @@ -75,9 +75,9 @@ void MessageQueue::vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, ns mHandler->dispatchFrame(vsyncId, expectedVsyncTime); } -void MessageQueue::initVsync(std::shared_ptr<scheduler::VSyncDispatch> dispatch, - frametimeline::TokenManager& tokenManager, - std::chrono::nanoseconds workDuration) { +void MessageQueue::initVsyncInternal(std::shared_ptr<scheduler::VSyncDispatch> dispatch, + frametimeline::TokenManager& tokenManager, + std::chrono::nanoseconds workDuration) { std::unique_ptr<scheduler::VSyncCallbackRegistration> oldRegistration; { std::lock_guard lock(mVsync.mutex); @@ -87,7 +87,7 @@ void MessageQueue::initVsync(std::shared_ptr<scheduler::VSyncDispatch> dispatch, } // See comments in onNewVsyncSchedule. Today, oldRegistration should be - // empty, but nothing prevents us from calling initVsync multiple times, so + // empty, but nothing prevents us from calling initVsyncInternal multiple times, so // go ahead and destruct it outside the lock for safety. oldRegistration.reset(); } @@ -125,7 +125,7 @@ std::unique_ptr<scheduler::VSyncCallbackRegistration> MessageQueue::onNewVsyncSc mVsync.scheduledFrameTime = mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(), .readyDuration = 0, - .earliestVsync = mVsync.lastCallbackTime.ns()}); + .lastVsync = mVsync.lastCallbackTime.ns()}); } return oldRegistration; } @@ -143,7 +143,7 @@ void MessageQueue::setDuration(std::chrono::nanoseconds workDuration) { mVsync.scheduledFrameTime = mVsync.registration->update({.workDuration = mVsync.workDuration.get().count(), .readyDuration = 0, - .earliestVsync = mVsync.lastCallbackTime.ns()}); + .lastVsync = mVsync.lastCallbackTime.ns()}); } void MessageQueue::waitMessage() { @@ -196,7 +196,7 @@ void MessageQueue::scheduleFrame() { mVsync.scheduledFrameTime = mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(), .readyDuration = 0, - .earliestVsync = mVsync.lastCallbackTime.ns()}); + .lastVsync = mVsync.lastCallbackTime.ns()}); } auto MessageQueue::getScheduledFrameTime() const -> std::optional<Clock::time_point> { diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h index a523147733..edb424b5b9 100644 --- a/services/surfaceflinger/Scheduler/MessageQueue.h +++ b/services/surfaceflinger/Scheduler/MessageQueue.h @@ -65,8 +65,9 @@ class MessageQueue { public: virtual ~MessageQueue() = default; - virtual void initVsync(std::shared_ptr<scheduler::VSyncDispatch>, frametimeline::TokenManager&, - std::chrono::nanoseconds workDuration) = 0; + virtual void initVsyncInternal(std::shared_ptr<scheduler::VSyncDispatch>, + frametimeline::TokenManager&, + std::chrono::nanoseconds workDuration) = 0; virtual void destroyVsync() = 0; virtual void setDuration(std::chrono::nanoseconds workDuration) = 0; virtual void waitMessage() = 0; @@ -137,8 +138,8 @@ private: public: explicit MessageQueue(ICompositor&); - void initVsync(std::shared_ptr<scheduler::VSyncDispatch>, frametimeline::TokenManager&, - std::chrono::nanoseconds workDuration) override; + void initVsyncInternal(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/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index e06221a43d..e696e8c358 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -286,7 +286,8 @@ struct RefreshRateSelector::RefreshRateScoreComparator { std::string RefreshRateSelector::Policy::toString() const { return base::StringPrintf("{defaultModeId=%d, allowGroupSwitching=%s" ", primaryRanges=%s, appRequestRanges=%s}", - defaultMode.value(), allowGroupSwitching ? "true" : "false", + ftl::to_underlying(defaultMode), + allowGroupSwitching ? "true" : "false", to_string(primaryRanges).c_str(), to_string(appRequestRanges).c_str()); } @@ -420,6 +421,11 @@ float RefreshRateSelector::calculateLayerScoreLocked(const LayerRequirement& lay const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty; if (layer.vote == LayerVoteType::ExplicitCategory) { + // HighHint is considered later for touch boost. + if (layer.frameRateCategory == FrameRateCategory::HighHint) { + return 0.f; + } + if (getFrameRateCategoryRange(layer.frameRateCategory).includes(refreshRate)) { return 1.f; } @@ -507,6 +513,7 @@ auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector<LayerRequi int explicitExact = 0; int explicitGteLayers = 0; int explicitCategoryVoteLayers = 0; + int interactiveLayers = 0; int seamedFocusedLayers = 0; int categorySmoothSwitchOnlyLayers = 0; @@ -534,7 +541,13 @@ auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector<LayerRequi explicitGteLayers++; break; case LayerVoteType::ExplicitCategory: - explicitCategoryVoteLayers++; + if (layer.frameRateCategory == FrameRateCategory::HighHint) { + // HighHint does not count as an explicit signal from an app. It may be + // be a touch signal. + interactiveLayers++; + } else { + explicitCategoryVoteLayers++; + } if (layer.frameRateCategory == FrameRateCategory::NoPreference) { // Count this layer for Min vote as well. The explicit vote avoids // touch boost and idle for choosing a category, while Min vote is for correct @@ -831,8 +844,13 @@ auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector<LayerRequi const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending); using fps_approx_ops::operator<; - if (signals.touch && explicitDefaultVoteLayers == 0 && explicitCategoryVoteLayers == 0 && - touchBoostForExplicitExact && + // A method for UI Toolkit to send the touch signal via "HighHint" category vote, + // which will touch boost when there are no ExplicitDefault layer votes. This is an + // incomplete solution but accounts for cases such as games that use `setFrameRate` with default + // compatibility to limit the frame rate, which should not have touch boost. + const bool hasInteraction = signals.touch || interactiveLayers > 0; + + if (hasInteraction && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact && scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) { ALOGV("Touch Boost"); ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])", @@ -1512,6 +1530,7 @@ FpsRange RefreshRateSelector::getFrameRateCategoryRange(FrameRateCategory catego return FpsRange{60_Hz, 90_Hz}; case FrameRateCategory::Low: return FpsRange{30_Hz, 30_Hz}; + case FrameRateCategory::HighHint: case FrameRateCategory::NoPreference: case FrameRateCategory::Default: LOG_ALWAYS_FATAL("Should not get fps range for frame rate category: %s", diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h index a1a7c289cf..6051e8935d 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h @@ -31,7 +31,6 @@ #include "DisplayHardware/DisplayMode.h" #include "Scheduler/OneShotTimer.h" -#include "Scheduler/StrongTyping.h" #include "ThreadContext.h" #include "Utils/Dumper.h" diff --git a/services/surfaceflinger/Scheduler/RefreshRateStats.h b/services/surfaceflinger/Scheduler/RefreshRateStats.h index 67e1b9c2ea..d51af9ac88 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateStats.h +++ b/services/surfaceflinger/Scheduler/RefreshRateStats.h @@ -49,10 +49,10 @@ class RefreshRateStats { public: // TODO(b/185535769): Inject clock to avoid sleeping in tests. - RefreshRateStats(TimeStats& timeStats, Fps currentRefreshRate, PowerMode currentPowerMode) + RefreshRateStats(TimeStats& timeStats, Fps currentRefreshRate) : mTimeStats(timeStats), mCurrentRefreshRate(currentRefreshRate), - mCurrentPowerMode(currentPowerMode) {} + mCurrentPowerMode(PowerMode::OFF) {} void setPowerMode(PowerMode mode) { if (mCurrentPowerMode == mode) { diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index ce59a04808..6979f0399b 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -51,8 +51,12 @@ #include "FrameRateOverrideMappings.h" #include "FrontEnd/LayerHandle.h" #include "OneShotTimer.h" +#include "RefreshRateStats.h" +#include "SurfaceFlingerFactory.h" #include "SurfaceFlingerProperties.h" +#include "TimeStats/TimeStats.h" #include "VSyncTracker.h" +#include "VsyncConfiguration.h" #include "VsyncController.h" #include "VsyncSchedule.h" @@ -67,12 +71,13 @@ namespace android::scheduler { Scheduler::Scheduler(ICompositor& compositor, ISchedulerCallback& callback, FeatureFlags features, - sp<VsyncModulator> modulatorPtr, IVsyncTrackerCallback& vsyncTrackerCallback) - : impl::MessageQueue(compositor), + surfaceflinger::Factory& factory, Fps activeRefreshRate, TimeStats& timeStats) + : android::impl::MessageQueue(compositor), mFeatures(features), - mVsyncModulator(std::move(modulatorPtr)), - mSchedulerCallback(callback), - mVsyncTrackerCallback(vsyncTrackerCallback) {} + mVsyncConfiguration(factory.createVsyncConfiguration(activeRefreshRate)), + mVsyncModulator(sp<VsyncModulator>::make(mVsyncConfiguration->getCurrentConfigs())), + mRefreshRateStats(std::make_unique<RefreshRateStats>(timeStats, activeRefreshRate)), + mSchedulerCallback(callback) {} Scheduler::~Scheduler() { // MessageQueue depends on VsyncSchedule, so first destroy it. @@ -88,6 +93,11 @@ Scheduler::~Scheduler() { demotePacesetterDisplay(); } +void Scheduler::initVsync(frametimeline::TokenManager& tokenManager, + std::chrono::nanoseconds workDuration) { + Impl::initVsyncInternal(getVsyncSchedule()->getDispatch(), tokenManager, workDuration); +} + void Scheduler::startTimers() { using namespace sysprop; using namespace std::string_literals; @@ -122,10 +132,11 @@ void Scheduler::setPacesetterDisplay(std::optional<PhysicalDisplayId> pacesetter } void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) { - auto schedulePtr = std::make_shared<VsyncSchedule>( - selectorPtr->getActiveMode().modePtr, mFeatures, - [this](PhysicalDisplayId id, bool enable) { onHardwareVsyncRequest(id, enable); }, - mVsyncTrackerCallback); + auto schedulePtr = + std::make_shared<VsyncSchedule>(selectorPtr->getActiveMode().modePtr, mFeatures, + [this](PhysicalDisplayId id, bool enable) { + onHardwareVsyncRequest(id, enable); + }); registerDisplayInternal(displayId, std::move(selectorPtr), std::move(schedulePtr)); } @@ -182,9 +193,9 @@ void Scheduler::onFrameSignal(ICompositor& compositor, VsyncId vsyncId, const FrameTargeter::BeginFrameArgs beginFrameArgs = {.frameBeginTime = SchedulerClock::now(), .vsyncId = vsyncId, - // TODO(b/255601557): Calculate per display. .expectedVsyncTime = expectedVsyncTime, - .sfWorkDuration = mVsyncModulator->getVsyncConfig().sfWorkDuration}; + .sfWorkDuration = mVsyncModulator->getVsyncConfig().sfWorkDuration, + .hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration}; ftl::NonNull<const Display*> pacesetterPtr = pacesetterPtrLocked(); pacesetterPtr->targeterPtr->beginFrame(beginFrameArgs, *pacesetterPtr->schedulePtr); @@ -193,15 +204,29 @@ void Scheduler::onFrameSignal(ICompositor& compositor, VsyncId vsyncId, FrameTargets targets; targets.try_emplace(pacesetterPtr->displayId, &pacesetterPtr->targeterPtr->target()); + // TODO (b/256196556): Followers should use the next VSYNC after the frontrunner, not the + // pacesetter. + // Update expectedVsyncTime, which may have been adjusted by beginFrame. + expectedVsyncTime = pacesetterPtr->targeterPtr->target().expectedPresentTime(); + for (const auto& [id, display] : mDisplays) { if (id == pacesetterPtr->displayId) continue; + auto followerBeginFrameArgs = beginFrameArgs; + followerBeginFrameArgs.expectedVsyncTime = + display.schedulePtr->vsyncDeadlineAfter(expectedVsyncTime); + FrameTargeter& targeter = *display.targeterPtr; - targeter.beginFrame(beginFrameArgs, *display.schedulePtr); + targeter.beginFrame(followerBeginFrameArgs, *display.schedulePtr); targets.try_emplace(id, &targeter.target()); } - if (!compositor.commit(pacesetterPtr->displayId, targets)) return; + if (!compositor.commit(pacesetterPtr->displayId, targets)) { + if (FlagManager::getInstance().vrr_config()) { + compositor.sendNotifyExpectedPresentHint(pacesetterPtr->displayId); + } + return; + } } // The pacesetter may have changed or been registered anew during commit. @@ -242,6 +267,9 @@ void Scheduler::onFrameSignal(ICompositor& compositor, VsyncId vsyncId, } const auto resultsPerDisplay = compositor.composite(pacesetterPtr->displayId, targeters); + if (FlagManager::getInstance().vrr_config()) { + compositor.sendNotifyExpectedPresentHint(pacesetterPtr->displayId); + } compositor.sample(); for (const auto& [id, targeter] : targeters) { @@ -302,14 +330,28 @@ Period Scheduler::getVsyncPeriod(uid_t uid) { // behaviour. return Period::fromNs(currentPeriod.ns() * divisor); } +void Scheduler::onExpectedPresentTimePosted(TimePoint expectedPresentTime) { + const auto frameRateMode = [this] { + std::scoped_lock lock(mDisplayLock); + const auto pacesetterOpt = pacesetterDisplayLocked(); + const Display& pacesetter = *pacesetterOpt; + return pacesetter.selectorPtr->getActiveMode(); + }(); + + if (frameRateMode.modePtr->getVrrConfig()) { + mSchedulerCallback.onExpectedPresentTimePosted(expectedPresentTime, frameRateMode.modePtr, + frameRateMode.fps); + } +} ConnectionHandle Scheduler::createEventThread(Cycle cycle, frametimeline::TokenManager* tokenManager, std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration) { - auto eventThread = std::make_unique<impl::EventThread>(cycle == Cycle::Render ? "app" : "appSf", - getVsyncSchedule(), tokenManager, *this, - workDuration, readyDuration); + auto eventThread = + std::make_unique<android::impl::EventThread>(cycle == Cycle::Render ? "app" : "appSf", + getVsyncSchedule(), tokenManager, *this, + workDuration, readyDuration); auto& handle = cycle == Cycle::Render ? mAppConnectionHandle : mSfConnectionHandle; handle = createConnection(std::move(eventThread)); @@ -412,6 +454,17 @@ void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDis thread->onFrameRateOverridesChanged(displayId, std::move(overrides)); } +void Scheduler::onHdcpLevelsChanged(ConnectionHandle handle, PhysicalDisplayId displayId, + int32_t connectedLevel, int32_t maxLevel) { + android::EventThread* thread; + { + std::lock_guard<std::mutex> lock(mConnectionsLock); + RETURN_IF_INVALID_HANDLE(handle); + thread = mConnections[handle].thread.get(); + } + thread->onHdcpLevelsChanged(displayId, connectedLevel, maxLevel); +} + void Scheduler::onPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode& mode) { { std::lock_guard<std::mutex> lock(mPolicyLock); @@ -484,8 +537,23 @@ void Scheduler::setDuration(ConnectionHandle handle, std::chrono::nanoseconds wo thread->setDuration(workDuration, readyDuration); } -void Scheduler::setVsyncConfigSet(const VsyncConfigSet& configs, Period vsyncPeriod) { - setVsyncConfig(mVsyncModulator->setVsyncConfigSet(configs), vsyncPeriod); +void Scheduler::updatePhaseConfiguration(Fps refreshRate) { + mRefreshRateStats->setRefreshRate(refreshRate); + mVsyncConfiguration->setRefreshRateFps(refreshRate); + setVsyncConfig(mVsyncModulator->setVsyncConfigSet(mVsyncConfiguration->getCurrentConfigs()), + refreshRate.getPeriod()); +} + +void Scheduler::resetPhaseConfiguration(Fps refreshRate) { + // Cancel the pending refresh rate change, if any, before updating the phase configuration. + mVsyncModulator->cancelRefreshRateChange(); + + mVsyncConfiguration->reset(); + updatePhaseConfiguration(refreshRate); +} + +void Scheduler::setActiveDisplayPowerModeForRefreshRateStats(hal::PowerMode powerMode) { + mRefreshRateStats->setPowerMode(powerMode); } void Scheduler::setVsyncConfig(const VsyncConfig& config, Period vsyncPeriod) { @@ -549,7 +617,7 @@ void Scheduler::onHardwareVsyncRequest(PhysicalDisplayId id, bool enabled) { // On main thread to serialize reads/writes of pending hardware VSYNC state. static_cast<void>( - schedule([=]() FTL_FAKE_GUARD(mDisplayLock) FTL_FAKE_GUARD(kMainThreadContext) { + schedule([=, this]() FTL_FAKE_GUARD(mDisplayLock) FTL_FAKE_GUARD(kMainThreadContext) { ATRACE_NAME(ftl::Concat(whence, ' ', id.value, ' ', enabled).c_str()); if (const auto displayOpt = mDisplays.get(id)) { @@ -598,11 +666,13 @@ Fps Scheduler::getNextFrameInterval(PhysicalDisplayId id, return Fps{}; } const Display& display = *displayOpt; - const nsecs_t threshold = - display.selectorPtr->getActiveMode().modePtr->getVsyncRate().getPeriodNsecs() / 2; - const nsecs_t nextVsyncTime = display.schedulePtr->getTracker().nextAnticipatedVSyncTimeFrom( - currentExpectedPresentTime.ns() + threshold); - return Fps::fromPeriodNsecs(nextVsyncTime - currentExpectedPresentTime.ns()); + const Duration threshold = + display.selectorPtr->getActiveMode().modePtr->getVsyncRate().getPeriod() / 2; + const TimePoint nextVsyncTime = + display.schedulePtr->vsyncDeadlineAfter(currentExpectedPresentTime + threshold, + currentExpectedPresentTime); + const Duration frameInterval = nextVsyncTime - currentExpectedPresentTime; + return Fps::fromPeriodNsecs(frameInterval.ns()); } void Scheduler::resync() { @@ -856,6 +926,12 @@ void Scheduler::dump(utils::Dumper& dumper) const { mFrameRateOverrideMappings.dump(dumper); dumper.eol(); + mVsyncConfiguration->dump(dumper.out()); + dumper.eol(); + + mRefreshRateStats->dump(dumper.out()); + dumper.eol(); + { utils::Dumper::Section section(dumper, "Frame Targeting"sv); diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index ce585c624a..9f29e9f696 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -88,23 +88,30 @@ struct hash<android::scheduler::ConnectionHandle> { namespace android { class FenceTime; +class TimeStats; namespace frametimeline { class TokenManager; } // namespace frametimeline +namespace surfaceflinger { +class Factory; +} // namespace surfaceflinger + namespace scheduler { using GlobalSignals = RefreshRateSelector::GlobalSignals; +class RefreshRateStats; +class VsyncConfiguration; class VsyncSchedule; class Scheduler : public IEventThreadCallback, android::impl::MessageQueue { using Impl = android::impl::MessageQueue; public: - Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags, sp<VsyncModulator>, - IVsyncTrackerCallback&); + Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags, surfaceflinger::Factory&, + Fps activeRefreshRate, TimeStats&); virtual ~Scheduler(); void startTimers(); @@ -124,7 +131,7 @@ public: void run(); - using Impl::initVsync; + void initVsync(frametimeline::TokenManager&, std::chrono::nanoseconds workDuration); using Impl::getScheduledFrameTime; using Impl::setDuration; @@ -173,6 +180,8 @@ public: void onFrameRateOverridesChanged(ConnectionHandle, PhysicalDisplayId) EXCLUDES(mConnectionsLock); + void onHdcpLevelsChanged(ConnectionHandle, PhysicalDisplayId, int32_t, int32_t); + // Modifies work duration in the event thread. void setDuration(ConnectionHandle, std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration); @@ -199,7 +208,10 @@ public: } } - void setVsyncConfigSet(const VsyncConfigSet&, Period vsyncPeriod); + void updatePhaseConfiguration(Fps); + void resetPhaseConfiguration(Fps) REQUIRES(kMainThreadContext); + + const VsyncConfiguration& getVsyncConfiguration() const { return *mVsyncConfiguration; } // Sets the render rate for the scheduler to run at. void setRenderRate(PhysicalDisplayId, Fps); @@ -247,8 +259,10 @@ public: // Indicates that touch interaction is taking place. void onTouchHint(); - void setDisplayPowerMode(PhysicalDisplayId, hal::PowerMode powerMode) - REQUIRES(kMainThreadContext); + void setDisplayPowerMode(PhysicalDisplayId, hal::PowerMode) REQUIRES(kMainThreadContext); + + // TODO(b/255635821): Track this per display. + void setActiveDisplayPowerModeForRefreshRateStats(hal::PowerMode) REQUIRES(kMainThreadContext); ConstVsyncSchedulePtr getVsyncSchedule(std::optional<PhysicalDisplayId> = std::nullopt) const EXCLUDES(mDisplayLock); @@ -444,6 +458,7 @@ private: bool throttleVsync(TimePoint, uid_t) override; Period getVsyncPeriod(uid_t) override EXCLUDES(mDisplayLock); void resync() override EXCLUDES(mDisplayLock); + void onExpectedPresentTimePosted(TimePoint expectedPresentTime) override EXCLUDES(mDisplayLock); // Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection. struct Connection { @@ -462,9 +477,14 @@ private: const FeatureFlags mFeatures; + // Stores phase offsets configured per refresh rate. + const std::unique_ptr<VsyncConfiguration> mVsyncConfiguration; + // Shifts the VSYNC phase during certain transactions and refresh rate changes. const sp<VsyncModulator> mVsyncModulator; + const std::unique_ptr<RefreshRateStats> mRefreshRateStats; + // Used to choose refresh rate if content detection is enabled. LayerHistory mLayerHistory; @@ -478,8 +498,6 @@ private: ISchedulerCallback& mSchedulerCallback; - IVsyncTrackerCallback& mVsyncTrackerCallback; - // mDisplayLock may be locked while under mPolicyLock. mutable std::mutex mPolicyLock; @@ -495,9 +513,7 @@ private: : displayId(displayId), selectorPtr(std::move(selectorPtr)), schedulePtr(std::move(schedulePtr)), - targeterPtr(std::make_unique< - FrameTargeter>(displayId, - features.test(Feature::kBackpressureGpuComposition))) {} + targeterPtr(std::make_unique<FrameTargeter>(displayId, features)) {} const PhysicalDisplayId displayId; diff --git a/services/surfaceflinger/Scheduler/StrongTyping.h b/services/surfaceflinger/Scheduler/StrongTyping.h deleted file mode 100644 index a05c123956..0000000000 --- a/services/surfaceflinger/Scheduler/StrongTyping.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -namespace android { - -template <typename T, template <typename> class AbilityType> -struct Ability { - T& base() { return static_cast<T&>(*this); } - T const& base() const { return static_cast<T const&>(*this); } -}; - -template <typename T> -struct Add : Ability<T, Add> { - inline T operator+(T const& other) const { return T(this->base().value() + other.value()); } - inline T& operator++() { - ++this->base().value(); - return this->base(); - }; - inline T operator++(int) { - T tmp(this->base()); - operator++(); - return tmp; - }; - inline T& operator+=(T const& other) { - this->base().value() += other.value(); - return this->base(); - }; -}; - -template <typename T> -struct Compare : Ability<T, Compare> { - inline bool operator==(T const& other) const { return this->base().value() == other.value(); }; - inline bool operator<(T const& other) const { return this->base().value() < other.value(); } - inline bool operator<=(T const& other) const { return (*this < other) || (*this == other); } - inline bool operator!=(T const& other) const { return !(*this == other); } - inline bool operator>=(T const& other) const { return !(*this < other); } - inline bool operator>(T const& other) const { return !(*this < other || *this == other); } -}; - -template <typename T> -struct Hash : Ability<T, Hash> { - [[nodiscard]] std::size_t hash() const { - return std::hash<typename std::remove_const< - typename std::remove_reference<decltype(this->base().value())>::type>::type>{}( - this->base().value()); - } -}; - -template <typename T, typename W, template <typename> class... Ability> -struct StrongTyping : Ability<StrongTyping<T, W, Ability...>>... { - constexpr StrongTyping() = default; - constexpr explicit StrongTyping(T const& value) : mValue(value) {} - StrongTyping(StrongTyping const&) = default; - StrongTyping& operator=(StrongTyping const&) = default; - explicit inline operator T() const { return mValue; } - T const& value() const { return mValue; } - T& value() { return mValue; } - - friend std::ostream& operator<<(std::ostream& os, const StrongTyping<T, W, Ability...>& value) { - return os << value.value(); - } - -private: - T mValue{0}; -}; -} // namespace android - -namespace std { -template <typename T, typename W, template <typename> class... Ability> -struct hash<android::StrongTyping<T, W, Ability...>> { - std::size_t operator()(android::StrongTyping<T, W, Ability...> const& k) const { - return k.hash(); - } -}; -} // namespace std diff --git a/services/surfaceflinger/Scheduler/VSyncDispatch.h b/services/surfaceflinger/Scheduler/VSyncDispatch.h index c3a952f689..ed8f8fef93 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatch.h +++ b/services/surfaceflinger/Scheduler/VSyncDispatch.h @@ -20,10 +20,9 @@ #include <optional> #include <string> +#include <ftl/mixins.h> #include <utils/Timers.h> -#include "StrongTyping.h" - namespace android::scheduler { using ScheduleResult = std::optional<nsecs_t>; @@ -35,7 +34,11 @@ enum class CancelResult { Cancelled, TooLate, Error }; */ class VSyncDispatch { public: - using CallbackToken = StrongTyping<size_t, class CallbackTokenTag, Compare, Hash>; + struct CallbackToken : ftl::DefaultConstructible<CallbackToken, size_t>, + ftl::Equatable<CallbackToken>, + ftl::Incrementable<CallbackToken> { + using DefaultConstructible::DefaultConstructible; + }; virtual ~VSyncDispatch(); @@ -84,8 +87,8 @@ public: * able to provide the ready-by time (deadline) on the callback. * For internal clients, we don't need to add additional padding, so * readyDuration will typically be 0. - * @earliestVsync: The targeted display time. This will be snapped to the closest - * predicted vsync time after earliestVsync. + * @lastVsync: The targeted display time. This will be snapped to the closest + * predicted vsync time after lastVsync. * * callback will be dispatched at 'workDuration + readyDuration' nanoseconds before a vsync * event. @@ -93,11 +96,11 @@ public: struct ScheduleTiming { nsecs_t workDuration = 0; nsecs_t readyDuration = 0; - nsecs_t earliestVsync = 0; + nsecs_t lastVsync = 0; bool operator==(const ScheduleTiming& other) const { return workDuration == other.workDuration && readyDuration == other.readyDuration && - earliestVsync == other.earliestVsync; + lastVsync == other.lastVsync; } bool operator!=(const ScheduleTiming& other) const { return !(*this == other); } @@ -109,12 +112,12 @@ public: * The callback will be dispatched at 'workDuration + readyDuration' nanoseconds before a vsync * event. * - * The caller designates the earliest vsync event that should be targeted by the earliestVsync + * The caller designates the earliest vsync event that should be targeted by the lastVsync * parameter. * The callback will be scheduled at (workDuration + readyDuration - predictedVsync), where - * predictedVsync is the first vsync event time where ( predictedVsync >= earliestVsync ). + * predictedVsync is the first vsync event time where ( predictedVsync >= lastVsync ). * - * If (workDuration + readyDuration - earliestVsync) is in the past, or if a callback has + * If (workDuration + readyDuration - lastVsync) is in the past, or if a callback has * already been dispatched for the predictedVsync, an error will be returned. * * It is valid to reschedule a callback to a different time. diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp index ef30887037..b92fa24670 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp @@ -43,13 +43,6 @@ nsecs_t getExpectedCallbackTime(nsecs_t nextVsyncTime, return nextVsyncTime - timing.readyDuration - timing.workDuration; } -nsecs_t getExpectedCallbackTime(VSyncTracker& tracker, nsecs_t now, - const VSyncDispatch::ScheduleTiming& timing) { - const auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom( - std::max(timing.earliestVsync, now + timing.workDuration + timing.readyDuration)); - return getExpectedCallbackTime(nextVsyncTime, timing); -} - } // namespace VSyncDispatch::~VSyncDispatch() = default; @@ -93,15 +86,18 @@ std::optional<nsecs_t> VSyncDispatchTimerQueueEntry::targetVsync() const { ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTiming timing, VSyncTracker& tracker, nsecs_t now) { - auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom( - std::max(timing.earliestVsync, now + timing.workDuration + timing.readyDuration)); + auto nextVsyncTime = + tracker.nextAnticipatedVSyncTimeFrom(std::max(timing.lastVsync, + now + timing.workDuration + + timing.readyDuration), + timing.lastVsync); auto nextWakeupTime = nextVsyncTime - timing.workDuration - timing.readyDuration; bool const wouldSkipAVsyncTarget = mArmedInfo && (nextVsyncTime > (mArmedInfo->mActualVsyncTime + mMinVsyncDistance)); bool const wouldSkipAWakeup = mArmedInfo && ((nextWakeupTime > (mArmedInfo->mActualWakeupTime + mMinVsyncDistance))); - if (FlagManager::getInstance().dont_skip_on_early()) { + if (FlagManager::getInstance().dont_skip_on_early_ro()) { if (wouldSkipAVsyncTarget || wouldSkipAWakeup) { nextVsyncTime = mArmedInfo->mActualVsyncTime; } else { @@ -122,8 +118,11 @@ ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTim return nextWakeupTime; } -void VSyncDispatchTimerQueueEntry::addPendingWorkloadUpdate(VSyncDispatch::ScheduleTiming timing) { +nsecs_t VSyncDispatchTimerQueueEntry::addPendingWorkloadUpdate( + VSyncTracker& tracker, nsecs_t now, VSyncDispatch::ScheduleTiming timing) { mWorkloadUpdateInfo = timing; + const auto armedInfo = update(tracker, now, timing, mArmedInfo); + return armedInfo.mActualWakeupTime; } bool VSyncDispatchTimerQueueEntry::hasPendingWorkloadUpdate() const { @@ -139,16 +138,43 @@ nsecs_t VSyncDispatchTimerQueueEntry::adjustVsyncIfNeeded(VSyncTracker& tracker, bool const nextVsyncTooClose = mLastDispatchTime && (nextVsyncTime - *mLastDispatchTime + mMinVsyncDistance) <= currentPeriod; if (alreadyDispatchedForVsync) { - return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance); + return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance, + *mLastDispatchTime); } if (nextVsyncTooClose) { - return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + currentPeriod); + return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + currentPeriod, + *mLastDispatchTime + currentPeriod); } return nextVsyncTime; } +auto VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now, + VSyncDispatch::ScheduleTiming timing, + std::optional<ArmingInfo> armedInfo) const -> ArmingInfo { + const auto earliestReadyBy = now + timing.workDuration + timing.readyDuration; + const auto earliestVsync = std::max(earliestReadyBy, timing.lastVsync); + + const auto nextVsyncTime = + adjustVsyncIfNeeded(tracker, /*nextVsyncTime*/ + tracker.nextAnticipatedVSyncTimeFrom(earliestVsync, + timing.lastVsync)); + const auto nextReadyTime = nextVsyncTime - timing.readyDuration; + const auto nextWakeupTime = nextReadyTime - timing.workDuration; + + bool const wouldSkipAVsyncTarget = + armedInfo && (nextVsyncTime > (armedInfo->mActualVsyncTime + mMinVsyncDistance)); + bool const wouldSkipAWakeup = + armedInfo && (nextWakeupTime > (armedInfo->mActualWakeupTime + mMinVsyncDistance)); + if (FlagManager::getInstance().dont_skip_on_early_ro() && + (wouldSkipAVsyncTarget || wouldSkipAWakeup)) { + return *armedInfo; + } + + return ArmingInfo{nextWakeupTime, nextVsyncTime, nextReadyTime}; +} + void VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now) { if (!mArmedInfo && !mWorkloadUpdateInfo) { return; @@ -159,16 +185,7 @@ void VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now) { mWorkloadUpdateInfo.reset(); } - const auto earliestReadyBy = now + mScheduleTiming.workDuration + mScheduleTiming.readyDuration; - const auto earliestVsync = std::max(earliestReadyBy, mScheduleTiming.earliestVsync); - - const auto nextVsyncTime = - adjustVsyncIfNeeded(tracker, /*nextVsyncTime*/ - tracker.nextAnticipatedVSyncTimeFrom(earliestVsync)); - const auto nextReadyTime = nextVsyncTime - mScheduleTiming.readyDuration; - const auto nextWakeupTime = nextReadyTime - mScheduleTiming.workDuration; - - mArmedInfo = {nextWakeupTime, nextVsyncTime, nextReadyTime}; + mArmedInfo = update(tracker, now, mScheduleTiming, mArmedInfo); } void VSyncDispatchTimerQueueEntry::disarm() { @@ -214,10 +231,10 @@ void VSyncDispatchTimerQueueEntry::dump(std::string& result) const { StringAppendF(&result, "\t\t%s: %s %s\n", mName.c_str(), mRunning ? "(in callback function)" : "", armedInfo.c_str()); StringAppendF(&result, - "\t\t\tworkDuration: %.2fms readyDuration: %.2fms earliestVsync: %.2fms relative " + "\t\t\tworkDuration: %.2fms readyDuration: %.2fms lastVsync: %.2fms relative " "to now\n", mScheduleTiming.workDuration / 1e6f, mScheduleTiming.readyDuration / 1e6f, - (mScheduleTiming.earliestVsync - systemTime()) / 1e6f); + (mScheduleTiming.lastVsync - systemTime()) / 1e6f); if (mLastDispatchTime) { StringAppendF(&result, "\t\t\tmLastDispatchTime: %.2fms ago\n", @@ -237,6 +254,7 @@ VSyncDispatchTimerQueue::VSyncDispatchTimerQueue(std::unique_ptr<TimeKeeper> tk, VSyncDispatchTimerQueue::~VSyncDispatchTimerQueue() { std::lock_guard lock(mMutex); + mRunning = false; cancelTimer(); for (auto& [_, entry] : mCallbacks) { ALOGE("Forgot to unregister a callback on VSyncDispatch!"); @@ -257,15 +275,15 @@ void VSyncDispatchTimerQueue::setTimer(nsecs_t targetTime, nsecs_t /*now*/) { } void VSyncDispatchTimerQueue::rearmTimer(nsecs_t now) { - rearmTimerSkippingUpdateFor(now, mCallbacks.end()); + rearmTimerSkippingUpdateFor(now, mCallbacks.cend()); } void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( - nsecs_t now, CallbackMap::iterator const& skipUpdateIt) { + nsecs_t now, CallbackMap::const_iterator skipUpdateIt) { std::optional<nsecs_t> min; std::optional<nsecs_t> targetVsync; std::optional<std::string_view> nextWakeupName; - for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) { + for (auto it = mCallbacks.cbegin(); it != mCallbacks.cend(); ++it) { auto& callback = it->second; if (!callback->wakeupTime() && !callback->hasPendingWorkloadUpdate()) { continue; @@ -305,6 +323,10 @@ void VSyncDispatchTimerQueue::timerCallback() { std::vector<Invocation> invocations; { std::lock_guard lock(mMutex); + if (!mRunning) { + ALOGD("TimerQueue is not running. Skipping callback."); + return; + } auto const now = mTimeKeeper->now(); mLastTimerCallback = now; for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) { @@ -337,13 +359,12 @@ void VSyncDispatchTimerQueue::timerCallback() { VSyncDispatchTimerQueue::CallbackToken VSyncDispatchTimerQueue::registerCallback( Callback callback, std::string callbackName) { std::lock_guard lock(mMutex); - return CallbackToken{ - mCallbacks - .emplace(++mCallbackToken, - std::make_shared<VSyncDispatchTimerQueueEntry>(std::move(callbackName), - std::move(callback), - mMinVsyncDistance)) - .first->first}; + return mCallbacks + .try_emplace(++mCallbackToken, + std::make_shared<VSyncDispatchTimerQueueEntry>(std::move(callbackName), + std::move(callback), + mMinVsyncDistance)) + .first->first; } void VSyncDispatchTimerQueue::unregisterCallback(CallbackToken token) { @@ -353,7 +374,7 @@ void VSyncDispatchTimerQueue::unregisterCallback(CallbackToken token) { auto it = mCallbacks.find(token); if (it != mCallbacks.end()) { entry = it->second; - mCallbacks.erase(it); + mCallbacks.erase(it->first); } } @@ -381,8 +402,7 @@ ScheduleResult VSyncDispatchTimerQueue::scheduleLocked(CallbackToken token, * timer recalculation to avoid cancelling a callback that is about to fire. */ auto const rearmImminent = now > mIntendedWakeupTime; if (CC_UNLIKELY(rearmImminent)) { - callback->addPendingWorkloadUpdate(scheduleTiming); - return getExpectedCallbackTime(*mTracker, now, scheduleTiming); + return callback->addPendingWorkloadUpdate(*mTracker, now, scheduleTiming); } const ScheduleResult result = callback->schedule(scheduleTiming, *mTracker, now); diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h index e0fb8f9f86..b5ddd25293 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h @@ -16,14 +16,13 @@ #pragma once -#include <functional> #include <memory> #include <mutex> #include <string> #include <string_view> -#include <unordered_map> #include <android-base/thread_annotations.h> +#include <ftl/small_map.h> #include "VSyncDispatch.h" #include "VsyncSchedule.h" @@ -70,7 +69,7 @@ public: // Adds a pending upload of the earliestVSync and workDuration that will be applied on the next // call to update() - void addPendingWorkloadUpdate(VSyncDispatch::ScheduleTiming); + nsecs_t addPendingWorkloadUpdate(VSyncTracker&, nsecs_t now, VSyncDispatch::ScheduleTiming); // Checks if there is a pending update to the workload, returning true if so. bool hasPendingWorkloadUpdate() const; @@ -84,7 +83,15 @@ public: void dump(std::string& result) const; private: + struct ArmingInfo { + nsecs_t mActualWakeupTime; + nsecs_t mActualVsyncTime; + nsecs_t mActualReadyTime; + }; + nsecs_t adjustVsyncIfNeeded(VSyncTracker& tracker, nsecs_t nextVsyncTime) const; + ArmingInfo update(VSyncTracker&, nsecs_t now, VSyncDispatch::ScheduleTiming, + std::optional<ArmingInfo>) const; const std::string mName; const VSyncDispatch::Callback mCallback; @@ -92,11 +99,6 @@ private: VSyncDispatch::ScheduleTiming mScheduleTiming; const nsecs_t mMinVsyncDistance; - struct ArmingInfo { - nsecs_t mActualWakeupTime; - nsecs_t mActualVsyncTime; - nsecs_t mActualReadyTime; - }; std::optional<ArmingInfo> mArmedInfo; std::optional<nsecs_t> mLastDispatchTime; @@ -135,26 +137,31 @@ private: VSyncDispatchTimerQueue(const VSyncDispatchTimerQueue&) = delete; VSyncDispatchTimerQueue& operator=(const VSyncDispatchTimerQueue&) = delete; + // The static capacity was chosen to exceed the expected number of callbacks. using CallbackMap = - std::unordered_map<CallbackToken, std::shared_ptr<VSyncDispatchTimerQueueEntry>>; + ftl::SmallMap<CallbackToken, std::shared_ptr<VSyncDispatchTimerQueueEntry>, 5>; void timerCallback(); void setTimer(nsecs_t, nsecs_t) REQUIRES(mMutex); void rearmTimer(nsecs_t now) REQUIRES(mMutex); - void rearmTimerSkippingUpdateFor(nsecs_t now, CallbackMap::iterator const& skipUpdate) + void rearmTimerSkippingUpdateFor(nsecs_t now, CallbackMap::const_iterator skipUpdate) REQUIRES(mMutex); void cancelTimer() REQUIRES(mMutex); ScheduleResult scheduleLocked(CallbackToken, ScheduleTiming) REQUIRES(mMutex); std::mutex mutable mMutex; + // During VSyncDispatchTimerQueue deconstruction, skip timerCallback to + // avoid crash + bool mRunning = true; + static constexpr nsecs_t kInvalidTime = std::numeric_limits<int64_t>::max(); std::unique_ptr<TimeKeeper> const mTimeKeeper; VsyncSchedule::TrackerPtr mTracker; nsecs_t const mTimerSlack; nsecs_t const mMinVsyncDistance; - size_t mCallbackToken GUARDED_BY(mMutex) = 0; + CallbackToken mCallbackToken GUARDED_BY(mMutex); CallbackMap mCallbacks GUARDED_BY(mMutex); nsecs_t mIntendedWakeupTime GUARDED_BY(mMutex) = kInvalidTime; diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 0d79a390a7..8697696915 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -48,14 +48,12 @@ static auto constexpr kMaxPercent = 100u; VSyncPredictor::~VSyncPredictor() = default; VSyncPredictor::VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize, - size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent, - IVsyncTrackerCallback& callback) + size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent) : mId(modePtr->getPhysicalDisplayId()), mTraceOn(property_get_bool("debug.sf.vsp_trace", false)), kHistorySize(historySize), kMinimumSamplesForPrediction(minimumSamplesForPrediction), kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)), - mVsyncTrackerCallback(callback), mDisplayModePtr(modePtr) { resetModel(); } @@ -253,7 +251,7 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { } auto VSyncPredictor::getVsyncSequenceLocked(nsecs_t timestamp) const -> VsyncSequence { - const auto vsync = nextAnticipatedVSyncTimeFromLocked(timestamp); + const auto vsync = snapToVsync(timestamp); if (!mLastVsyncSequence) return {vsync, 0}; const auto [slope, _] = getVSyncPredictionModelLocked(); @@ -263,7 +261,7 @@ auto VSyncPredictor::getVsyncSequenceLocked(nsecs_t timestamp) const -> VsyncSeq return {vsync, vsyncSequence}; } -nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const { +nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const { auto const [slope, intercept] = getVSyncPredictionModelLocked(); if (mTimestamps.empty()) { @@ -299,9 +297,23 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) co return prediction; } -nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const { +nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, + std::optional<nsecs_t> lastVsyncOpt) const { + ATRACE_CALL(); std::lock_guard lock(mMutex); + const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope; + const auto threshold = currentPeriod / 2; + const auto minFramePeriod = minFramePeriodLocked().ns(); + const auto lastFrameMissed = + lastVsyncOpt && std::abs(*lastVsyncOpt - mLastMissedVsync.ns()) < threshold; + const nsecs_t baseTime = + FlagManager::getInstance().vrr_config() && !lastFrameMissed && lastVsyncOpt + ? std::max(timePoint, *lastVsyncOpt + minFramePeriod - threshold) + : timePoint; + return snapToVsyncAlignedWithRenderRate(baseTime); +} +nsecs_t VSyncPredictor::snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const { // update the mLastVsyncSequence for reference point mLastVsyncSequence = getVsyncSequenceLocked(timePoint); @@ -325,30 +337,12 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const { }(); if (renderRatePhase == 0) { - const auto vsyncTime = mLastVsyncSequence->vsyncTime; - if (FlagManager::getInstance().vrr_config()) { - const auto vsyncTimePoint = TimePoint::fromNs(vsyncTime); - ATRACE_FORMAT("%s InPhase vsyncIn %.2fms", __func__, - ticks<std::milli, float>(vsyncTimePoint - TimePoint::now())); - const Fps renderRate = mRenderRateOpt ? *mRenderRateOpt : mDisplayModePtr->getPeakFps(); - mVsyncTrackerCallback.onVsyncGenerated(vsyncTimePoint, mDisplayModePtr, renderRate); - } - return vsyncTime; + return mLastVsyncSequence->vsyncTime; } auto const [slope, intercept] = getVSyncPredictionModelLocked(); const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase; - const auto nextAnticipatedVsyncTime = - nextAnticipatedVSyncTimeFromLocked(approximateNextVsync - slope / 2); - if (FlagManager::getInstance().vrr_config()) { - const auto nextAnticipatedVsyncTimePoint = TimePoint::fromNs(nextAnticipatedVsyncTime); - ATRACE_FORMAT("%s outOfPhase vsyncIn %.2fms", __func__, - ticks<std::milli, float>(nextAnticipatedVsyncTimePoint - TimePoint::now())); - const Fps renderRate = mRenderRateOpt ? *mRenderRateOpt : mDisplayModePtr->getPeakFps(); - mVsyncTrackerCallback.onVsyncGenerated(nextAnticipatedVsyncTimePoint, mDisplayModePtr, - renderRate); - } - return nextAnticipatedVsyncTime; + return snapToVsync(approximateNextVsync - slope / 2); } /* @@ -403,7 +397,7 @@ void VSyncPredictor::setDisplayModePtr(ftl::NonNull<DisplayModePtr> modePtr) { : std::nullopt; ALOGV("%s %s: DisplayMode %s notifyExpectedPresentTimeout %s", __func__, to_string(mId).c_str(), to_string(*modePtr).c_str(), - timeout ? std::to_string(timeout->notifyExpectedPresentTimeoutNs).c_str() : "N/A"); + timeout ? std::to_string(timeout->timeoutNs).c_str() : "N/A"); std::lock_guard lock(mMutex); mDisplayModePtr = modePtr; @@ -451,6 +445,7 @@ void VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime, if (mLastVsyncSequence) { mLastVsyncSequence->vsyncTime += phase.ns(); } + mPastExpectedPresentTimes.clear(); } } } @@ -468,23 +463,17 @@ void VSyncPredictor::onFrameBegin(TimePoint expectedPresentTime, lastConfirmedPresentTime.ns()) / 1e6f); } - mPastExpectedPresentTimes.push_back(expectedPresentTime); - const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope; const auto threshold = currentPeriod / 2; + mPastExpectedPresentTimes.push_back(expectedPresentTime); - const auto minFramePeriod = minFramePeriodLocked().ns(); while (!mPastExpectedPresentTimes.empty()) { const auto front = mPastExpectedPresentTimes.front().ns(); - const bool frontIsLastConfirmed = - std::abs(front - lastConfirmedPresentTime.ns()) < threshold; - const bool frontIsBeforeConfirmed = - front < lastConfirmedPresentTime.ns() - minFramePeriod + threshold; - if (frontIsLastConfirmed || frontIsBeforeConfirmed) { + const bool frontIsBeforeConfirmed = front < lastConfirmedPresentTime.ns() + threshold; + if (frontIsBeforeConfirmed) { if (CC_UNLIKELY(mTraceOn)) { ATRACE_FORMAT_INSTANT("Discarding old vsync - %.2f before last signaled fence", - static_cast<float>(lastConfirmedPresentTime.ns() - - mPastExpectedPresentTimes.front().ns()) / + static_cast<float>(lastConfirmedPresentTime.ns() - front) / 1e6f); } mPastExpectedPresentTimes.pop_front(); @@ -508,6 +497,7 @@ void VSyncPredictor::onFrameMissed(TimePoint expectedPresentTime) { TimePoint::fromNs(expectedPresentTime.ns() + currentPeriod); ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime); + mLastMissedVsync = expectedPresentTime; } VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const { diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index 72a343112a..8fd7e6046d 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -37,15 +37,15 @@ public: * \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. - * \param [in] IVsyncTrackerCallback The callback for the VSyncTracker. */ VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize, - size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent, - IVsyncTrackerCallback&); + size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent); ~VSyncPredictor(); bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex); - nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final EXCLUDES(mMutex); + nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, + std::optional<nsecs_t> lastVsyncOpt = {}) const final + EXCLUDES(mMutex); nsecs_t currentPeriod() const final EXCLUDES(mMutex); Period minFramePeriod() const final EXCLUDES(mMutex); void resetModel() final EXCLUDES(mMutex); @@ -87,7 +87,8 @@ private: 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); + nsecs_t snapToVsync(nsecs_t timePoint) const REQUIRES(mMutex); + nsecs_t snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const REQUIRES(mMutex); bool isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const REQUIRES(mMutex); Period minFramePeriodLocked() const REQUIRES(mMutex); void ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex); @@ -103,7 +104,6 @@ private: size_t const kHistorySize; size_t const kMinimumSamplesForPrediction; size_t const kOutlierTolerancePercent; - IVsyncTrackerCallback& mVsyncTrackerCallback; std::mutex mutable mMutex; std::optional<nsecs_t> mKnownTimestamp GUARDED_BY(mMutex); @@ -120,6 +120,8 @@ private: mutable std::optional<VsyncSequence> mLastVsyncSequence GUARDED_BY(mMutex); std::deque<TimePoint> mPastExpectedPresentTimes GUARDED_BY(mMutex); + + TimePoint mLastMissedVsync GUARDED_BY(mMutex); }; } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h index 1ed863cf7d..37bd4b4977 100644 --- a/services/surfaceflinger/Scheduler/VSyncTracker.h +++ b/services/surfaceflinger/Scheduler/VSyncTracker.h @@ -26,12 +26,6 @@ namespace android::scheduler { -struct IVsyncTrackerCallback { - virtual ~IVsyncTrackerCallback() = default; - virtual void onVsyncGenerated(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>, - Fps renderRate) = 0; -}; - /* * VSyncTracker is an interface for providing estimates on future Vsync signal times based on * historical vsync timing data. @@ -57,9 +51,13 @@ public: * is updated. * * \param [in] timePoint The point in time after which to estimate a vsync event. + * \param [in] lastVsyncOpt The last vsync time used by the client. If provided, the tracker + * should use that as a reference point when generating the new vsync + * and avoid crossing the minimal frame period of a VRR display. * \return A prediction of the timestamp of a vsync event. */ - virtual nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const = 0; + virtual nsecs_t nextAnticipatedVSyncTimeFrom( + nsecs_t timePoint, std::optional<nsecs_t> lastVsyncOpt = {}) const = 0; /* * The current period of the vsync signal. diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp index db6a1871a7..001938c756 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp @@ -57,11 +57,10 @@ private: }; VsyncSchedule::VsyncSchedule(ftl::NonNull<DisplayModePtr> modePtr, FeatureFlags features, - RequestHardwareVsync requestHardwareVsync, - IVsyncTrackerCallback& callback) + RequestHardwareVsync requestHardwareVsync) : mId(modePtr->getPhysicalDisplayId()), mRequestHardwareVsync(std::move(requestHardwareVsync)), - mTracker(createTracker(modePtr, callback)), + mTracker(createTracker(modePtr)), mDispatch(createDispatch(mTracker)), mController(createController(modePtr->getPhysicalDisplayId(), *mTracker, features)), mTracer(features.test(Feature::kTracePredictedVsync) @@ -89,8 +88,12 @@ Period VsyncSchedule::minFramePeriod() const { return period(); } -TimePoint VsyncSchedule::vsyncDeadlineAfter(TimePoint timePoint) const { - return TimePoint::fromNs(mTracker->nextAnticipatedVSyncTimeFrom(timePoint.ns())); +TimePoint VsyncSchedule::vsyncDeadlineAfter(TimePoint timePoint, + ftl::Optional<TimePoint> lastVsyncOpt) const { + return TimePoint::fromNs( + mTracker->nextAnticipatedVSyncTimeFrom(timePoint.ns(), + lastVsyncOpt.transform( + [](TimePoint t) { return t.ns(); }))); } void VsyncSchedule::dump(std::string& out) const { @@ -111,15 +114,14 @@ void VsyncSchedule::dump(std::string& out) const { mDispatch->dump(out); } -VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(ftl::NonNull<DisplayModePtr> modePtr, - IVsyncTrackerCallback& callback) { +VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(ftl::NonNull<DisplayModePtr> modePtr) { // TODO(b/144707443): Tune constants. constexpr size_t kHistorySize = 20; constexpr size_t kMinSamplesForPrediction = 6; constexpr uint32_t kDiscardOutlierPercent = 20; return std::make_unique<VSyncPredictor>(modePtr, kHistorySize, kMinSamplesForPrediction, - kDiscardOutlierPercent, callback); + kDiscardOutlierPercent); } VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(TrackerPtr tracker) { diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h index 722ea0b836..85cd3e7c31 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.h +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h @@ -57,13 +57,13 @@ class VsyncSchedule final : public IVsyncSource { public: using RequestHardwareVsync = std::function<void(PhysicalDisplayId, bool enabled)>; - VsyncSchedule(ftl::NonNull<DisplayModePtr> modePtr, FeatureFlags, RequestHardwareVsync, - IVsyncTrackerCallback&); + VsyncSchedule(ftl::NonNull<DisplayModePtr> modePtr, FeatureFlags, RequestHardwareVsync); ~VsyncSchedule(); // IVsyncSource overrides: Period period() const override; - TimePoint vsyncDeadlineAfter(TimePoint) const override; + TimePoint vsyncDeadlineAfter(TimePoint, + ftl::Optional<TimePoint> lastVsyncOpt = {}) const override; Period minFramePeriod() const override; // Inform the schedule that the display mode changed the schedule needs to recalibrate @@ -127,7 +127,7 @@ private: friend class android::VsyncScheduleTest; friend class android::fuzz::SchedulerFuzzer; - static TrackerPtr createTracker(ftl::NonNull<DisplayModePtr> modePtr, IVsyncTrackerCallback&); + static TrackerPtr createTracker(ftl::NonNull<DisplayModePtr> modePtr); static DispatchPtr createDispatch(TrackerPtr); static ControllerPtr createController(PhysicalDisplayId, VsyncTracker&, FeatureFlags); diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Features.h b/services/surfaceflinger/Scheduler/include/scheduler/Features.h index 7c72ac6afc..52485fb4e9 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/Features.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/Features.h @@ -29,6 +29,7 @@ enum class Feature : std::uint8_t { kTracePredictedVsync = 1 << 3, kBackpressureGpuComposition = 1 << 4, kSmallDirtyContentDetection = 1 << 5, + kExpectedPresentTime = 1 << 6, }; using FeatureFlags = ftl::Flags<Feature>; diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h index 2806450c5f..84ef89fb4a 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h @@ -88,6 +88,7 @@ enum class FrameRateCategory : int32_t { NoPreference, Low, Normal, + HighHint, High, ftl_last = High diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h index 70d4846070..a5bb6c216f 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h @@ -19,11 +19,13 @@ #include <array> #include <atomic> #include <memory> +#include <optional> #include <ui/DisplayId.h> #include <ui/Fence.h> #include <ui/FenceTime.h> +#include <scheduler/Features.h> #include <scheduler/Time.h> #include <scheduler/VsyncId.h> #include <scheduler/interface/CompositeResult.h> @@ -49,14 +51,11 @@ public: TimePoint expectedPresentTime() const { return mExpectedPresentTime; } + std::optional<TimePoint> earliestPresentTime() const { return mEarliestPresentTime; } + // The time of the VSYNC that preceded this frame. See `presentFenceForPastVsync` for details. TimePoint pastVsyncTime(Period minFramePeriod) const; - // Equivalent to `pastVsyncTime` unless running N VSYNCs ahead. - TimePoint previousFrameVsyncTime(Period minFramePeriod) const { - return mExpectedPresentTime - minFramePeriod; - } - // The present fence for the frame that had targeted the most recent VSYNC before this frame. // If the target VSYNC for any given frame is more than `vsyncPeriod` in the future, then the // VSYNC of at least one previous frame has not yet passed. In other words, this is NOT the @@ -69,8 +68,6 @@ public: return mPresentFences.front().fenceTime; } - bool wouldPresentEarly(Period minFramePeriod) const; - bool isFramePending() const { return mFramePending; } bool didMissFrame() const { return mFrameMissed; } bool didMissHwcFrame() const { return mHwcFrameMissed && !mGpuFrameMissed; } @@ -79,9 +76,17 @@ protected: explicit FrameTarget(const std::string& displayLabel); ~FrameTarget() = default; + bool wouldPresentEarly(Period minFramePeriod) const; + + // Equivalent to `pastVsyncTime` unless running N VSYNCs ahead. + TimePoint previousFrameVsyncTime(Period minFramePeriod) const { + return mExpectedPresentTime - minFramePeriod; + } + VsyncId mVsyncId; TimePoint mFrameBeginTime; TimePoint mExpectedPresentTime; + std::optional<TimePoint> mEarliestPresentTime; TracedOrdinal<bool> mFramePending; TracedOrdinal<bool> mFrameMissed; @@ -95,6 +100,8 @@ protected: std::array<FenceWithFenceTime, 2> mPresentFences; private: + friend class FrameTargeterTestBase; + template <int N> inline bool targetsVsyncsAhead(Period minFramePeriod) const { static_assert(N > 1); @@ -105,9 +112,10 @@ private: // Computes a display's per-frame metrics about past/upcoming targeting of present deadlines. class FrameTargeter final : private FrameTarget { public: - FrameTargeter(PhysicalDisplayId displayId, bool backpressureGpuComposition) + FrameTargeter(PhysicalDisplayId displayId, FeatureFlags flags) : FrameTarget(to_string(displayId)), - mBackpressureGpuComposition(backpressureGpuComposition) {} + mBackpressureGpuComposition(flags.test(Feature::kBackpressureGpuComposition)), + mSupportsExpectedPresentTime(flags.test(Feature::kExpectedPresentTime)) {} const FrameTarget& target() const { return *this; } @@ -116,10 +124,14 @@ public: VsyncId vsyncId; TimePoint expectedVsyncTime; Duration sfWorkDuration; + Duration hwcMinWorkDuration; }; void beginFrame(const BeginFrameArgs&, const IVsyncSource&); + std::optional<TimePoint> computeEarliestPresentTime(Period minFramePeriod, + Duration hwcMinWorkDuration); + // TODO(b/241285191): Merge with FrameTargeter::endFrame. FenceTimePtr setPresentFence(sp<Fence>); @@ -128,7 +140,7 @@ public: void dump(utils::Dumper&) const; private: - friend class FrameTargeterTest; + friend class FrameTargeterTestBase; // For tests. using IsFencePendingFuncPtr = bool (*)(const FenceTimePtr&, int graceTimeMs); @@ -138,6 +150,7 @@ private: static bool isFencePending(const FenceTimePtr&, int graceTimeMs); const bool mBackpressureGpuComposition; + const bool mSupportsExpectedPresentTime; TimePoint mScheduledPresentTime; CompositionCoverageFlags mCompositionCoverage; diff --git a/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h b/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h index 0154060f36..f0f7a87105 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h @@ -16,13 +16,14 @@ #pragma once +#include <ftl/optional.h> #include <scheduler/Time.h> namespace android::scheduler { struct IVsyncSource { virtual Period period() const = 0; - virtual TimePoint vsyncDeadlineAfter(TimePoint) const = 0; + virtual TimePoint vsyncDeadlineAfter(TimePoint, ftl::Optional<TimePoint> = {}) const = 0; virtual Period minFramePeriod() const = 0; protected: diff --git a/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h index 12ee36e084..8673a2251c 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h @@ -47,6 +47,9 @@ struct ICompositor { virtual CompositeResultsPerDisplay composite(PhysicalDisplayId pacesetterId, const scheduler::FrameTargeters&) = 0; + // Sends a hint about the expected present time + virtual void sendNotifyExpectedPresentHint(PhysicalDisplayId) = 0; + // Samples the composited frame via RegionSamplingThread. virtual void sample() = 0; diff --git a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp index e80372be31..68c277d499 100644 --- a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp +++ b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp @@ -82,6 +82,10 @@ void FrameTargeter::beginFrame(const BeginFrameArgs& args, const IVsyncSource& v } } + if (!mSupportsExpectedPresentTime) { + mEarliestPresentTime = computeEarliestPresentTime(minFramePeriod, args.hwcMinWorkDuration); + } + ATRACE_FORMAT("%s %" PRId64 " vsyncIn %.2fms%s", __func__, ftl::to_underlying(args.vsyncId), ticks<std::milli, float>(mExpectedPresentTime - TimePoint::now()), mExpectedPresentTime == args.expectedVsyncTime ? "" : " (adjusted)"); @@ -121,6 +125,14 @@ void FrameTargeter::beginFrame(const BeginFrameArgs& args, const IVsyncSource& v if (mGpuFrameMissed) mGpuFrameMissedCount++; } +std::optional<TimePoint> FrameTargeter::computeEarliestPresentTime(Period minFramePeriod, + Duration hwcMinWorkDuration) { + if (wouldPresentEarly(minFramePeriod)) { + return previousFrameVsyncTime(minFramePeriod) - hwcMinWorkDuration; + } + return {}; +} + void FrameTargeter::endFrame(const CompositeResult& result) { mCompositionCoverage = result.compositionCoverage; } diff --git a/services/surfaceflinger/Scheduler/src/Timer.cpp b/services/surfaceflinger/Scheduler/src/Timer.cpp index 09e8a1ebe0..eeb9c60d15 100644 --- a/services/surfaceflinger/Scheduler/src/Timer.cpp +++ b/services/surfaceflinger/Scheduler/src/Timer.cpp @@ -159,7 +159,7 @@ bool Timer::dispatch() { ALOGW("Failed to set SCHED_FIFO on dispatch thread"); } - if (pthread_setname_np(pthread_self(), "TimerDispatch")) { + if (pthread_setname_np(pthread_self(), "TimerDispatch") != 0) { ALOGW("Failed to set thread name on dispatch thread"); } diff --git a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp index c883385755..29711afdf9 100644 --- a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp +++ b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp @@ -38,18 +38,26 @@ struct VsyncSource final : IVsyncSource { const TimePoint vsyncDeadline; Period period() const override { return vsyncPeriod; } - TimePoint vsyncDeadlineAfter(TimePoint) const override { return vsyncDeadline; } + TimePoint vsyncDeadlineAfter(TimePoint, ftl::Optional<TimePoint> = {}) const override { + return vsyncDeadline; + } Period minFramePeriod() const override { return framePeriod; } }; } // namespace -class FrameTargeterTest : public testing::Test { +class FrameTargeterTestBase : public testing::Test { public: + FrameTargeterTestBase(FeatureFlags flags) : mTargeter(PhysicalDisplayId::fromPort(13), flags) {} + const auto& target() const { return mTargeter.target(); } + bool wouldPresentEarly(Period minFramePeriod) const { + return target().wouldPresentEarly(minFramePeriod); + } + struct Frame { - Frame(FrameTargeterTest* testPtr, VsyncId vsyncId, TimePoint& frameBeginTime, + Frame(FrameTargeterTestBase* testPtr, VsyncId vsyncId, TimePoint& frameBeginTime, Duration frameDuration, Fps refreshRate, Fps peakRefreshRate, FrameTargeter::IsFencePendingFuncPtr isFencePendingFuncPtr = Frame::fenceSignaled, const ftl::Optional<VsyncSource>& vsyncSourceOpt = std::nullopt) @@ -61,7 +69,8 @@ public: .vsyncId = vsyncId, .expectedVsyncTime = frameBeginTime + frameDuration, - .sfWorkDuration = 10ms}; + .sfWorkDuration = 10ms, + .hwcMinWorkDuration = kHwcMinWorkDuration}; testPtr->mTargeter.beginFrame(args, vsyncSourceOpt @@ -93,7 +102,7 @@ public: static bool fencePending(const FenceTimePtr&, int) { return true; } static bool fenceSignaled(const FenceTimePtr&, int) { return false; } - FrameTargeterTest* const testPtr; + FrameTargeterTestBase* const testPtr; TimePoint& frameBeginTime; const Period period; @@ -102,11 +111,24 @@ public: bool ended = false; }; + static constexpr Duration kHwcMinWorkDuration = std::chrono::nanoseconds(5ns); + private: FenceToFenceTimeMap mFenceMap; - static constexpr bool kBackpressureGpuComposition = true; - FrameTargeter mTargeter{PhysicalDisplayId::fromPort(13), kBackpressureGpuComposition}; + FrameTargeter mTargeter; +}; + +class FrameTargeterTest : public FrameTargeterTestBase { +public: + FrameTargeterTest() : FrameTargeterTestBase(Feature::kBackpressureGpuComposition) {} +}; + +class FrameTargeterWithExpectedPresentSupportTest : public FrameTargeterTestBase { +public: + FrameTargeterWithExpectedPresentSupportTest() + : FrameTargeterTestBase(FeatureFlags(Feature::kBackpressureGpuComposition) | + Feature::kExpectedPresentTime) {} }; TEST_F(FrameTargeterTest, targetsFrames) { @@ -208,7 +230,7 @@ TEST_F(FrameTargeterTest, recallsPastVsyncTwoVsyncsAheadVrr) { TEST_F(FrameTargeterTest, doesNotDetectEarlyPresentIfNoFence) { constexpr Period kPeriod = (60_Hz).getPeriod(); EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), FenceTime::NO_FENCE); - EXPECT_FALSE(target().wouldPresentEarly(kPeriod)); + EXPECT_FALSE(wouldPresentEarly(kPeriod)); } TEST_F(FrameTargeterTest, detectsEarlyPresent) { @@ -220,7 +242,8 @@ TEST_F(FrameTargeterTest, detectsEarlyPresent) { // The target is not early while past present fences are pending. for (int n = 3; n-- > 0;) { const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate); - EXPECT_FALSE(target().wouldPresentEarly(kPeriod)); + EXPECT_FALSE(wouldPresentEarly(kPeriod)); + EXPECT_FALSE(target().earliestPresentTime()); } // The target is early if the past present fence was signaled. @@ -228,7 +251,41 @@ TEST_F(FrameTargeterTest, detectsEarlyPresent) { const auto fence = frame.end(); fence->signalForTest(frameBeginTime.ns()); - EXPECT_TRUE(target().wouldPresentEarly(kPeriod)); + Frame finalFrame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate); + + // `finalFrame` would present early, so it has an earliest present time. + EXPECT_TRUE(wouldPresentEarly(kPeriod)); + ASSERT_NE(std::nullopt, target().earliestPresentTime()); + EXPECT_EQ(*target().earliestPresentTime(), + target().expectedPresentTime() - kPeriod - kHwcMinWorkDuration); +} + +// Same as `detectsEarlyPresent`, above, but verifies that we do not set an earliest present time +// when there is expected present time support. +TEST_F(FrameTargeterWithExpectedPresentSupportTest, detectsEarlyPresent) { + VsyncId vsyncId{333}; + TimePoint frameBeginTime(3000ms); + constexpr Fps kRefreshRate = 60_Hz; + constexpr Period kPeriod = kRefreshRate.getPeriod(); + + // The target is not early while past present fences are pending. + for (int n = 3; n-- > 0;) { + const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate); + EXPECT_FALSE(wouldPresentEarly(kPeriod)); + EXPECT_FALSE(target().earliestPresentTime()); + } + + // The target is early if the past present fence was signaled. + Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate); + const auto fence = frame.end(); + fence->signalForTest(frameBeginTime.ns()); + + Frame finalFrame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate); + + // `finalFrame` would present early, but we have expected present time support, so it has no + // earliest present time. + EXPECT_TRUE(wouldPresentEarly(kPeriod)); + ASSERT_EQ(std::nullopt, target().earliestPresentTime()); } TEST_F(FrameTargeterTest, detectsEarlyPresentTwoVsyncsAhead) { @@ -240,7 +297,8 @@ TEST_F(FrameTargeterTest, detectsEarlyPresentTwoVsyncsAhead) { // The target is not early while past present fences are pending. for (int n = 3; n-- > 0;) { const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate); - EXPECT_FALSE(target().wouldPresentEarly(kPeriod)); + EXPECT_FALSE(wouldPresentEarly(kPeriod)); + EXPECT_FALSE(target().earliestPresentTime()); } Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate); @@ -248,12 +306,18 @@ TEST_F(FrameTargeterTest, detectsEarlyPresentTwoVsyncsAhead) { fence->signalForTest(frameBeginTime.ns()); // The target is two VSYNCs ahead, so the past present fence is still pending. - EXPECT_FALSE(target().wouldPresentEarly(kPeriod)); + EXPECT_FALSE(wouldPresentEarly(kPeriod)); + EXPECT_FALSE(target().earliestPresentTime()); { const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate); } + Frame finalFrame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate); + // The target is early if the past present fence was signaled. - EXPECT_TRUE(target().wouldPresentEarly(kPeriod)); + EXPECT_TRUE(wouldPresentEarly(kPeriod)); + ASSERT_NE(std::nullopt, target().earliestPresentTime()); + EXPECT_EQ(*target().earliestPresentTime(), + target().expectedPresentTime() - kPeriod - kHwcMinWorkDuration); } TEST_F(FrameTargeterTest, detectsEarlyPresentThreeVsyncsAhead) { @@ -264,7 +328,10 @@ TEST_F(FrameTargeterTest, detectsEarlyPresentThreeVsyncsAhead) { const Frame frame(this, VsyncId{555}, frameBeginTime, 16ms, kRefreshRate, kRefreshRate); // The target is more than two VSYNCs ahead, but present fences are not tracked that far back. - EXPECT_TRUE(target().wouldPresentEarly(kPeriod)); + EXPECT_TRUE(wouldPresentEarly(kPeriod)); + EXPECT_TRUE(target().earliestPresentTime()); + EXPECT_EQ(*target().earliestPresentTime(), + target().expectedPresentTime() - kPeriod - kHwcMinWorkDuration); } TEST_F(FrameTargeterTest, detectsMissedFrames) { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index c56dc83412..5bb550834e 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -116,6 +116,7 @@ #include <common/FlagManager.h> #include <gui/LayerStatePermissions.h> #include <gui/SchedulingPolicy.h> +#include <gui/SyncScreenCaptureListener.h> #include <ui/DisplayIdentification.h> #include "BackgroundExecutor.h" #include "Client.h" @@ -492,14 +493,6 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI mSupportsBlur = supportsBlurs; ALOGI_IF(!mSupportsBlur, "Disabling blur effects, they are not supported."); - const size_t defaultListSize = MAX_LAYERS; - auto listSize = property_get_int32("debug.sf.max_igbp_list_size", int32_t(defaultListSize)); - mMaxGraphicBufferProducerListSize = (listSize > 0) ? size_t(listSize) : defaultListSize; - mGraphicBufferProducerListSizeLogThreshold = - std::max(static_cast<int>(0.95 * - static_cast<double>(mMaxGraphicBufferProducerListSize)), - 1); - property_get("debug.sf.luma_sampling", value, "1"); mLumaSampling = atoi(value); @@ -551,6 +544,12 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI base::GetBoolProperty("persist.debug.sf.enable_layer_lifecycle_manager"s, true); mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled || base::GetBoolProperty("persist.debug.sf.enable_legacy_frontend"s, false); + + // These are set by the HWC implementation to indicate that they will use the workarounds. + mIsHotplugErrViaNegVsync = + base::GetBoolProperty("debug.sf.hwc_hotplug_error_via_neg_vsync"s, false); + + mIsHdcpViaNegVsync = base::GetBoolProperty("debug.sf.hwc_hdcp_via_neg_vsync"s, false); } LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() { @@ -763,7 +762,7 @@ void SurfaceFlinger::bootFinished() { sp<IBinder> input(defaultServiceManager()->waitForService(String16("inputflinger"))); - static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) { + static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) { if (input == nullptr) { ALOGE("Failed to link to input service"); } else { @@ -800,22 +799,26 @@ void SurfaceFlinger::bootFinished() { })); } -static std::optional<renderengine::RenderEngine::RenderEngineType> -chooseRenderEngineTypeViaSysProp() { +void chooseRenderEngineType(renderengine::RenderEngineCreationArgs::Builder& builder) { char prop[PROPERTY_VALUE_MAX]; - property_get(PROPERTY_DEBUG_RENDERENGINE_BACKEND, prop, "skiaglthreaded"); + property_get(PROPERTY_DEBUG_RENDERENGINE_BACKEND, prop, ""); if (strcmp(prop, "skiagl") == 0) { - return renderengine::RenderEngine::RenderEngineType::SKIA_GL; + builder.setThreaded(renderengine::RenderEngine::Threaded::NO) + .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::GL); } else if (strcmp(prop, "skiaglthreaded") == 0) { - return renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED; + builder.setThreaded(renderengine::RenderEngine::Threaded::YES) + .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::GL); } else if (strcmp(prop, "skiavk") == 0) { - return renderengine::RenderEngine::RenderEngineType::SKIA_VK; + builder.setThreaded(renderengine::RenderEngine::Threaded::NO) + .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::VK); } else if (strcmp(prop, "skiavkthreaded") == 0) { - return renderengine::RenderEngine::RenderEngineType::SKIA_VK_THREADED; + builder.setThreaded(renderengine::RenderEngine::Threaded::YES) + .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::VK); } else { - ALOGE("Unrecognized RenderEngineType %s; ignoring!", prop); - return {}; + builder.setGraphicsApi(FlagManager::getInstance().vulkan_renderengine() + ? renderengine::RenderEngine::GraphicsApi::VK + : renderengine::RenderEngine::GraphicsApi::GL); } } @@ -841,9 +844,7 @@ void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { useContextPriority ? renderengine::RenderEngine::ContextPriority::REALTIME : renderengine::RenderEngine::ContextPriority::MEDIUM); - if (auto type = chooseRenderEngineTypeViaSysProp()) { - builder.setRenderEngineType(type.value()); - } + chooseRenderEngineType(builder); mRenderEngine = renderengine::RenderEngine::create(builder.build()); mCompositionEngine->setRenderEngine(mRenderEngine.get()); mMaxRenderTargetSize = @@ -1082,7 +1083,7 @@ void SurfaceFlinger::getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo*& info for (const auto& [id, mode] : displayModes) { ui::DisplayMode outMode; - outMode.id = static_cast<int32_t>(id.value()); + outMode.id = ftl::to_underlying(id); auto [width, height] = mode->getResolution(); auto [xDpi, yDpi] = mode->getDpi(); @@ -1102,7 +1103,7 @@ void SurfaceFlinger::getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo*& info outMode.peakRefreshRate = peakFps.getValue(); outMode.vsyncRate = mode->getVsyncRate().getValue(); - const auto vsyncConfigSet = mVsyncConfiguration->getConfigsForRefreshRate( + const auto vsyncConfigSet = mScheduler->getVsyncConfiguration().getConfigsForRefreshRate( Fps::fromValue(outMode.peakRefreshRate)); outMode.appVsyncOffset = vsyncConfigSet.late.appOffset; outMode.sfVsyncOffset = vsyncConfigSet.late.sfOffset; @@ -1131,7 +1132,7 @@ void SurfaceFlinger::getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo*& info const PhysicalDisplayId displayId = snapshot.displayId(); const auto mode = display->refreshRateSelector().getActiveMode(); - info->activeDisplayModeId = mode.modePtr->getId().value(); + info->activeDisplayModeId = ftl::to_underlying(mode.modePtr->getId()); info->renderFrameRate = mode.fps.getValue(); info->activeColorMode = display->getCompositionDisplay()->getState().colorMode; info->hdrCapabilities = filterOut4k30(display->getHdrCapabilities()); @@ -1147,7 +1148,7 @@ void SurfaceFlinger::getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo*& info if (getHwComposer().hasCapability(Capability::BOOT_DISPLAY_CONFIG)) { if (const auto hwcId = getHwComposer().getPreferredBootDisplayMode(displayId)) { if (const auto modeId = snapshot.translateModeId(*hwcId)) { - info->preferredBootDisplayMode = modeId->value(); + info->preferredBootDisplayMode = ftl::to_underlying(*modeId); } } } @@ -1229,8 +1230,10 @@ status_t SurfaceFlinger::getDisplayStats(const sp<IBinder>& displayToken, return NO_ERROR; } -void SurfaceFlinger::setDesiredMode(display::DisplayModeRequest&& request, bool force) { - const auto displayId = request.mode.modePtr->getPhysicalDisplayId(); +void SurfaceFlinger::setDesiredMode(display::DisplayModeRequest&& desiredMode) { + const auto mode = desiredMode.mode; + const auto displayId = mode.modePtr->getPhysicalDisplayId(); + ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str()); const auto display = getDisplayDeviceLocked(displayId); @@ -1239,10 +1242,9 @@ void SurfaceFlinger::setDesiredMode(display::DisplayModeRequest&& request, bool return; } - const auto mode = request.mode; - const bool emitEvent = request.emitEvent; + const bool emitEvent = desiredMode.emitEvent; - switch (display->setDesiredMode(std::move(request), force)) { + switch (display->setDesiredMode(std::move(desiredMode))) { case DisplayDevice::DesiredModeAction::InitiateDisplayModeSwitch: // DisplayDevice::setDesiredMode updated the render rate, so inform Scheduler. mScheduler->setRenderRate(displayId, @@ -1261,7 +1263,7 @@ void SurfaceFlinger::setDesiredMode(display::DisplayModeRequest&& request, bool mScheduler->modulateVsync(displayId, &VsyncModulator::onRefreshRateChangeInitiated); if (displayId == mActiveDisplayId) { - updatePhaseConfiguration(mode.fps); + mScheduler->updatePhaseConfiguration(mode.fps); } mScheduler->setModeChangePending(true); @@ -1270,8 +1272,7 @@ void SurfaceFlinger::setDesiredMode(display::DisplayModeRequest&& request, bool mScheduler->setRenderRate(displayId, mode.fps); if (displayId == mActiveDisplayId) { - updatePhaseConfiguration(mode.fps); - mRefreshRateStats->setRefreshRate(mode.fps); + mScheduler->updatePhaseConfiguration(mode.fps); } if (emitEvent) { @@ -1292,7 +1293,7 @@ status_t SurfaceFlinger::setActiveModeFromBackdoor(const sp<display::DisplayToke } const char* const whence = __func__; - auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t { + auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t { const auto displayOpt = FTL_FAKE_GUARD(mStateLock, ftl::find_if(mPhysicalDisplays, @@ -1311,7 +1312,7 @@ status_t SurfaceFlinger::setActiveModeFromBackdoor(const sp<display::DisplayToke [](const DisplayModePtr& mode) { return mode->getPeakFps(); }); if (!fpsOpt) { - ALOGE("%s: Invalid mode %d for display %s", whence, modeId.value(), + ALOGE("%s: Invalid mode %d for display %s", whence, ftl::to_underlying(modeId), to_string(snapshot.displayId()).c_str()); return BAD_VALUE; } @@ -1363,8 +1364,7 @@ void SurfaceFlinger::finalizeDisplayModeChange(DisplayDevice& display) { activeMode.fps); if (displayId == mActiveDisplayId) { - mRefreshRateStats->setRefreshRate(activeMode.fps); - updatePhaseConfiguration(activeMode.fps); + mScheduler->updatePhaseConfiguration(activeMode.fps); } if (pendingModeOpt->emitEvent) { @@ -1393,7 +1393,7 @@ void SurfaceFlinger::applyActiveMode(const sp<DisplayDevice>& display) { mScheduler->setRenderRate(displayId, renderFps); if (displayId == mActiveDisplayId) { - updatePhaseConfiguration(renderFps); + mScheduler->updatePhaseConfiguration(renderFps); } } @@ -1421,16 +1421,17 @@ void SurfaceFlinger::initiateDisplayModeChanges() { if (!displayModePtrOpt) { ALOGW("Desired display mode is no longer supported. Mode ID = %d", - desiredModeId.value()); - dropModeRequest(display); + ftl::to_underlying(desiredModeId)); continue; } - ALOGV("%s changing active mode to %d(%s) for display %s", __func__, desiredModeId.value(), + ALOGV("%s changing active mode to %d(%s) for display %s", __func__, + ftl::to_underlying(desiredModeId), to_string(displayModePtrOpt->get()->getVsyncRate()).c_str(), to_string(display->getId()).c_str()); - if (display->getActiveMode() == desiredModeOpt->mode) { + if ((!FlagManager::getInstance().connected_display() || !desiredModeOpt->force) && + display->getActiveMode() == desiredModeOpt->mode) { applyActiveMode(display); continue; } @@ -1479,7 +1480,7 @@ void SurfaceFlinger::initiateDisplayModeChanges() { void SurfaceFlinger::disableExpensiveRendering() { const char* const whence = __func__; - auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) { + auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) { ATRACE_NAME(whence); if (mPowerAdvisor->isUsingExpensiveRendering()) { for (const auto& [_, display] : mDisplays) { @@ -1521,7 +1522,7 @@ status_t SurfaceFlinger::setActiveColorMode(const sp<IBinder>& displayToken, ui: } const char* const whence = __func__; - auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t { + auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) -> status_t { const auto displayOpt = ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken)) .transform(&ftl::to_mapped_ref<PhysicalDisplays>) @@ -1602,7 +1603,7 @@ status_t SurfaceFlinger::getOverlaySupport(gui::OverlayProperties* outProperties status_t SurfaceFlinger::setBootDisplayMode(const sp<display::DisplayToken>& displayToken, DisplayModeId modeId) { const char* const whence = __func__; - auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t { + auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) -> status_t { const auto snapshotOpt = ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken)) .transform(&ftl::to_mapped_ref<PhysicalDisplays>) @@ -1618,7 +1619,7 @@ status_t SurfaceFlinger::setBootDisplayMode(const sp<display::DisplayToken>& dis [](const DisplayModePtr& mode) { return mode->getHwcId(); }); if (!hwcIdOpt) { - ALOGE("%s: Invalid mode %d for display %s", whence, modeId.value(), + ALOGE("%s: Invalid mode %d for display %s", whence, ftl::to_underlying(modeId), to_string(snapshot.displayId()).c_str()); return BAD_VALUE; } @@ -1630,7 +1631,7 @@ status_t SurfaceFlinger::setBootDisplayMode(const sp<display::DisplayToken>& dis status_t SurfaceFlinger::clearBootDisplayMode(const sp<IBinder>& displayToken) { const char* const whence = __func__; - auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t { + auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) -> status_t { if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) { return getHwComposer().clearBootDisplayMode(*displayId); } else { @@ -1669,7 +1670,7 @@ status_t SurfaceFlinger::setHdrConversionStrategy( ALOGE("hdrOutputConversion is not supported by this device."); return INVALID_OPERATION; } - auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) mutable -> status_t { + auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) mutable -> status_t { using AidlHdrConversionStrategy = aidl::android::hardware::graphics::common::HdrConversionStrategy; using GuiHdrConversionStrategyTag = gui::HdrConversionStrategy::Tag; @@ -1729,7 +1730,7 @@ status_t SurfaceFlinger::getHdrOutputConversionSupport(bool* outSupport) const { void SurfaceFlinger::setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on) { const char* const whence = __func__; - static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) { + static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) { if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) { getHwComposer().setAutoLowLatencyMode(*displayId, on); } else { @@ -1740,7 +1741,7 @@ void SurfaceFlinger::setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool void SurfaceFlinger::setGameContentType(const sp<IBinder>& displayToken, bool on) { const char* const whence = __func__; - static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) { + static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) { if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) { const auto type = on ? hal::ContentType::GAME : hal::ContentType::NONE; getHwComposer().setContentType(*displayId, type); @@ -1794,7 +1795,7 @@ status_t SurfaceFlinger::setDisplayContentSamplingEnabled(const sp<IBinder>& dis bool enable, uint8_t componentMask, uint64_t maxFrames) { const char* const whence = __func__; - auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t { + auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) -> status_t { if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) { return getHwComposer().setDisplayContentSamplingEnabled(*displayId, enable, componentMask, maxFrames); @@ -1847,7 +1848,7 @@ status_t SurfaceFlinger::isWideColorDisplay(const sp<IBinder>& displayToken, status_t SurfaceFlinger::getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers) { outLayers->clear(); - auto future = mScheduler->schedule([=] { + auto future = mScheduler->schedule([=, this] { const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()); mDrawingState.traverseInZOrder([&](Layer* layer) { outLayers->push_back(layer->getLayerDebugInfo(display.get())); @@ -1953,7 +1954,8 @@ status_t SurfaceFlinger::setDisplayBrightness(const sp<IBinder>& displayToken, } const char* const whence = __func__; - return ftl::Future(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) { + return ftl::Future(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) { + // TODO(b/241285876): Validate that the display is physical instead of failing later. if (const auto display = getDisplayDeviceLocked(displayToken)) { const bool supportsDisplayBrightnessCommand = getHwComposer().getComposer()->isSupported( @@ -2003,7 +2005,6 @@ status_t SurfaceFlinger::setDisplayBrightness(const sp<IBinder>& displayToken, Hwc2::Composer::DisplayBrightnessOptions{ .applyImmediately = true}); } - } else { ALOGE("%s: Invalid display token %p", whence, displayToken.get()); return ftl::yield<status_t>(NAME_NOT_FOUND); @@ -2130,15 +2131,28 @@ nsecs_t SurfaceFlinger::getVsyncPeriodFromHWC() const { void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t timestamp, std::optional<hal::VsyncPeriodNanos> vsyncPeriod) { - if (FlagManager::getInstance().connected_display()) { + if (FlagManager::getInstance().connected_display() && timestamp < 0 && + vsyncPeriod.has_value()) { // use ~0 instead of -1 as AidlComposerHal.cpp passes the param as unsigned int32 - if (mIsHotplugErrViaNegVsync && timestamp < 0 && vsyncPeriod.has_value() && - vsyncPeriod.value() == ~0) { - int hotplugErrorCode = static_cast<int32_t>(-timestamp); - ALOGD("SurfaceFlinger got hotplugErrorCode=%d", hotplugErrorCode); + if (mIsHotplugErrViaNegVsync && vsyncPeriod.value() == ~0) { + const int32_t hotplugErrorCode = static_cast<int32_t>(-timestamp); + ALOGD("SurfaceFlinger got hotplugErrorCode=%d for display %" PRIu64, hotplugErrorCode, + hwcDisplayId); mScheduler->onHotplugConnectionError(mAppConnectionHandle, hotplugErrorCode); return; } + + if (mIsHdcpViaNegVsync && vsyncPeriod.value() == ~1) { + const int32_t value = static_cast<int32_t>(-timestamp); + // one byte is good enough to encode android.hardware.drm.HdcpLevel + const int32_t maxLevel = (value >> 8) & 0xFF; + const int32_t connectedLevel = value & 0xFF; + ALOGD("SurfaceFlinger got HDCP level changed: connected=%d, max=%d for " + "display=%" PRIu64, + connectedLevel, maxLevel, hwcDisplayId); + updateHdcpLevels(hwcDisplayId, connectedLevel, maxLevel); + return; + } } ATRACE_NAME(vsyncPeriod @@ -2208,7 +2222,7 @@ void SurfaceFlinger::onRefreshRateChangedDebug(const RefreshRateChangedDebugData ATRACE_CALL(); if (const auto displayId = getHwComposer().toPhysicalDisplayId(data.display); displayId) { const char* const whence = __func__; - static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) { + static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) { const Fps fps = Fps::fromPeriodNsecs(getHwComposer().getComposer()->isVrrSupported() ? data.refreshPeriodNanos : data.vsyncPeriodNanos); @@ -2372,11 +2386,7 @@ bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, mLegacyLayers[layer->sequence] = layer; } } - if (mLayerLifecycleManager.getGlobalChanges().test(Changes::Hierarchy)) { - ATRACE_NAME("LayerHierarchyBuilder:update"); - mLayerHierarchyBuilder.update(mLayerLifecycleManager.getLayers(), - mLayerLifecycleManager.getDestroyedLayers()); - } + mLayerHierarchyBuilder.update(mLayerLifecycleManager); } bool mustComposite = false; @@ -2436,6 +2446,13 @@ bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, const bool willReleaseBufferOnLatch = layer->willReleaseBufferOnLatch(); auto it = mLegacyLayers.find(layer->id); + if (it == mLegacyLayers.end() && + layer->changes.test(frontend::RequestedLayerState::Changes::Destroyed)) { + // Layer handle was created and immediately destroyed. It was destroyed before it + // was added to the map. + continue; + } + LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s", layer->getDebugString().c_str()); @@ -2668,6 +2685,8 @@ CompositeResultsPerDisplay SurfaceFlinger::composite( if (const auto display = getCompositionDisplayLocked(id)) { refreshArgs.outputs.push_back(display); } + + refreshArgs.frameTargets.try_emplace(id, &targeter->target()); } std::vector<DisplayId> displayIds; @@ -2736,36 +2755,11 @@ CompositeResultsPerDisplay SurfaceFlinger::composite( refreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::milliseconds(mDebugFlashDelay); } - const Period minFramePeriod = mScheduler->getVsyncSchedule()->minFramePeriod(); - - if (!getHwComposer().getComposer()->isSupported( - Hwc2::Composer::OptionalFeature::ExpectedPresentTime) && - pacesetterTarget.wouldPresentEarly(minFramePeriod)) { - const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration; - - // TODO(b/255601557): Calculate and pass per-display values for each FrameTarget. - refreshArgs.earliestPresentTime = - pacesetterTarget.previousFrameVsyncTime(minFramePeriod) - hwcMinWorkDuration; - } - - const TimePoint expectedPresentTime = pacesetterTarget.expectedPresentTime(); // TODO(b/255601557) Update frameInterval per display - refreshArgs.frameInterval = mScheduler->getNextFrameInterval(pacesetterId, expectedPresentTime); + refreshArgs.frameInterval = + mScheduler->getNextFrameInterval(pacesetterId, pacesetterTarget.expectedPresentTime()); refreshArgs.scheduledFrameTime = mScheduler->getScheduledFrameTime(); - refreshArgs.expectedPresentTime = expectedPresentTime.ns(); refreshArgs.hasTrustedPresentationListener = mNumTrustedPresentationListeners > 0; - { - auto& notifyExpectedPresentData = mNotifyExpectedPresentMap[pacesetterId]; - auto lastExpectedPresentTimestamp = TimePoint::fromNs( - notifyExpectedPresentData.lastExpectedPresentTimestamp.load().ns()); - if (expectedPresentTime > lastExpectedPresentTimestamp) { - // If the values are not same, then hint is sent with newer value. - // And because composition always follows the notifyExpectedPresentIfRequired, we can - // skip updating the lastExpectedPresentTimestamp in this case. - notifyExpectedPresentData.lastExpectedPresentTimestamp - .compare_exchange_weak(lastExpectedPresentTimestamp, expectedPresentTime); - } - } // Store the present time just before calling to the composition engine so we could notify // the scheduler. const auto presentTime = systemTime(); @@ -2829,6 +2823,7 @@ CompositeResultsPerDisplay SurfaceFlinger::composite( scheduleComposite(FrameHint::kNone); } + mNotifyExpectedPresentMap[pacesetterId].hintStatus = NotifyExpectedPresentHintStatus::Start; onCompositionPresented(pacesetterId, frameTargeters, presentTime); const bool hadGpuComposited = @@ -3049,7 +3044,8 @@ void SurfaceFlinger::onCompositionPresented(PhysicalDisplayId pacesetterId, 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 nsecs_t vsyncPhase = + mScheduler->getVsyncConfiguration().getCurrentConfigs().late.sfOffset; const CompositorTiming compositorTiming(vsyncDeadline.ns(), vsyncPeriod.ns(), vsyncPhase, presentLatency.ns()); @@ -3093,7 +3089,7 @@ void SurfaceFlinger::onCompositionPresented(PhysicalDisplayId pacesetterId, { Mutex::Autolock lock(mStateLock); if (mFpsReporter) { - mFpsReporter->dispatchLayerFps(); + mFpsReporter->dispatchLayerFps(mLayerHierarchyBuilder.getHierarchy()); } if (mTunnelModeEnabledReporter) { @@ -3115,6 +3111,7 @@ void SurfaceFlinger::onCompositionPresented(PhysicalDisplayId pacesetterId, for (auto& [compositionDisplay, listener] : hdrInfoListeners) { HdrLayerInfoReporter::HdrLayerInfo info; int32_t maxArea = 0; + auto updateInfoFn = [&](const std::shared_ptr<compositionengine::Display>& compositionDisplay, const frontend::LayerSnapshot& snapshot, const sp<LayerFE>& layerFe) { @@ -3125,7 +3122,7 @@ void SurfaceFlinger::onCompositionPresented(PhysicalDisplayId pacesetterId, compositionDisplay->getOutputLayerForLayer(layerFe); if (outputLayer) { const float desiredHdrSdrRatio = - snapshot.desiredHdrSdrRatio <= 1.f + snapshot.desiredHdrSdrRatio < 1.f ? std::numeric_limits<float>::infinity() : snapshot.desiredHdrSdrRatio; info.mergeDesiredRatio(desiredHdrSdrRatio); @@ -3288,7 +3285,11 @@ void SurfaceFlinger::commitTransactions() { std::pair<DisplayModes, DisplayModePtr> SurfaceFlinger::loadDisplayModes( PhysicalDisplayId displayId) const { std::vector<HWComposer::HWCDisplayMode> hwcModes; - std::optional<hal::HWDisplayId> activeModeHwcId; + std::optional<hal::HWConfigId> activeModeHwcIdOpt; + + const bool isExternalDisplay = FlagManager::getInstance().connected_display() && + getHwComposer().getDisplayConnectionType(displayId) == + ui::DisplayConnectionType::External; int attempt = 0; constexpr int kMaxAttempts = 3; @@ -3296,10 +3297,81 @@ std::pair<DisplayModes, DisplayModePtr> SurfaceFlinger::loadDisplayModes( hwcModes = getHwComposer().getModes(displayId, scheduler::RefreshRateSelector::kMinSupportedFrameRate .getPeriodNsecs()); - activeModeHwcId = getHwComposer().getActiveMode(displayId); + const auto activeModeHwcIdExp = getHwComposer().getActiveMode(displayId); + activeModeHwcIdOpt = activeModeHwcIdExp.value_opt(); + + if (isExternalDisplay && + activeModeHwcIdExp.has_error([](status_t error) { return error == NO_INIT; })) { + constexpr nsecs_t k59HzVsyncPeriod = 16949153; + constexpr nsecs_t k60HzVsyncPeriod = 16666667; + + // DM sets the initial mode for an external display to 1080p@60, but + // this comes after SF creates its own state (including the + // DisplayDevice). For now, pick the same mode in order to avoid + // inconsistent state and unnecessary mode switching. + // TODO (b/318534874): Let DM decide the initial mode. + // + // Try to find 1920x1080 @ 60 Hz + if (const auto iter = std::find_if(hwcModes.begin(), hwcModes.end(), + [](const auto& mode) { + return mode.width == 1920 && + mode.height == 1080 && + mode.vsyncPeriod == k60HzVsyncPeriod; + }); + iter != hwcModes.end()) { + activeModeHwcIdOpt = iter->hwcId; + break; + } - const auto isActiveMode = [activeModeHwcId](const HWComposer::HWCDisplayMode& mode) { - return mode.hwcId == activeModeHwcId; + // Try to find 1920x1080 @ 59-60 Hz + if (const auto iter = std::find_if(hwcModes.begin(), hwcModes.end(), + [](const auto& mode) { + return mode.width == 1920 && + mode.height == 1080 && + mode.vsyncPeriod >= k60HzVsyncPeriod && + mode.vsyncPeriod <= k59HzVsyncPeriod; + }); + iter != hwcModes.end()) { + activeModeHwcIdOpt = iter->hwcId; + break; + } + + // The display does not support 1080p@60, and this is the last attempt to pick a display + // mode. Prefer 60 Hz if available, with the closest resolution to 1080p. + if (attempt + 1 == kMaxAttempts) { + std::vector<HWComposer::HWCDisplayMode> hwcModeOpts; + + for (const auto& mode : hwcModes) { + if (mode.width <= 1920 && mode.height <= 1080 && + mode.vsyncPeriod >= k60HzVsyncPeriod && + mode.vsyncPeriod <= k59HzVsyncPeriod) { + hwcModeOpts.push_back(mode); + } + } + + if (const auto iter = std::max_element(hwcModeOpts.begin(), hwcModeOpts.end(), + [](const auto& a, const auto& b) { + const auto aSize = a.width * a.height; + const auto bSize = b.width * b.height; + if (aSize < bSize) + return true; + else if (aSize == bSize) + return a.vsyncPeriod > b.vsyncPeriod; + else + return false; + }); + iter != hwcModeOpts.end()) { + activeModeHwcIdOpt = iter->hwcId; + break; + } + + // hwcModeOpts was empty, use hwcModes[0] as the last resort + activeModeHwcIdOpt = hwcModes[0].hwcId; + } + } + + const auto isActiveMode = [activeModeHwcIdOpt](const HWComposer::HWCDisplayMode& mode) { + return mode.hwcId == activeModeHwcIdOpt; }; if (std::any_of(hwcModes.begin(), hwcModes.end(), isActiveMode)) { @@ -3309,7 +3381,7 @@ std::pair<DisplayModes, DisplayModePtr> SurfaceFlinger::loadDisplayModes( if (attempt == kMaxAttempts) { const std::string activeMode = - activeModeHwcId ? std::to_string(*activeModeHwcId) : "unknown"s; + activeModeHwcIdOpt ? std::to_string(*activeModeHwcIdOpt) : "unknown"s; ALOGE("HWC failed to report an active mode that is supported: activeModeHwcId=%s, " "hwcModes={%s}", activeMode.c_str(), base::Join(hwcModes, ", ").c_str()); @@ -3322,15 +3394,15 @@ std::pair<DisplayModes, DisplayModePtr> SurfaceFlinger::loadDisplayModes( }) .value_or(DisplayModes{}); - ui::DisplayModeId nextModeId = 1 + - std::accumulate(oldModes.begin(), oldModes.end(), static_cast<ui::DisplayModeId>(-1), - [](ui::DisplayModeId max, const auto& pair) { - return std::max(max, pair.first.value()); - }); + DisplayModeId nextModeId = std::accumulate(oldModes.begin(), oldModes.end(), DisplayModeId(-1), + [](DisplayModeId max, const auto& pair) { + return std::max(max, pair.first); + }); + ++nextModeId; DisplayModes newModes; for (const auto& hwcMode : hwcModes) { - const DisplayModeId id{nextModeId++}; + const auto id = nextModeId++; newModes.try_emplace(id, DisplayMode::Builder(hwcMode.hwcId) .setId(id) @@ -3353,10 +3425,14 @@ std::pair<DisplayModes, DisplayModePtr> SurfaceFlinger::loadDisplayModes( // Keep IDs if modes have not changed. const auto& modes = sameModes ? oldModes : newModes; const DisplayModePtr activeMode = - std::find_if(modes.begin(), modes.end(), [activeModeHwcId](const auto& pair) { - return pair.second->getHwcId() == activeModeHwcId; + std::find_if(modes.begin(), modes.end(), [activeModeHwcIdOpt](const auto& pair) { + return pair.second->getHwcId() == activeModeHwcIdOpt; })->second; + if (isExternalDisplay) { + ALOGI("External display %s initial mode: {%s}", to_string(displayId).c_str(), + to_string(*activeMode).c_str()); + } return {modes, activeMode}; } @@ -3401,8 +3477,12 @@ const char* SurfaceFlinger::processHotplug(PhysicalDisplayId displayId, auto [displayModes, activeMode] = loadDisplayModes(displayId); if (!activeMode) { - // TODO(b/241286153): Report hotplug failure to the framework. ALOGE("Failed to hotplug display %s", to_string(displayId).c_str()); + if (FlagManager::getInstance().hotplug2()) { + mScheduler->onHotplugConnectionError(mAppConnectionHandle, + static_cast<int32_t>( + DisplayHotplugEvent::ERROR_UNKNOWN)); + } getHwComposer().disconnectDisplay(displayId); return nullptr; } @@ -3432,9 +3512,10 @@ const char* SurfaceFlinger::processHotplug(PhysicalDisplayId displayId, } const sp<IBinder> token = sp<BBinder>::make(); + const ui::DisplayConnectionType connectionType = + getHwComposer().getDisplayConnectionType(displayId); - mPhysicalDisplays.try_emplace(displayId, token, displayId, - getHwComposer().getDisplayConnectionType(displayId), + mPhysicalDisplays.try_emplace(displayId, token, displayId, connectionType, std::move(displayModes), std::move(colorModes), std::move(info.deviceProductInfo)); @@ -3442,7 +3523,7 @@ const char* SurfaceFlinger::processHotplug(PhysicalDisplayId displayId, state.physical = {.id = displayId, .hwcDisplayId = hwcDisplayId, .activeMode = std::move(activeMode)}; - state.isSecure = true; // All physical displays are currently considered secure. + state.isSecure = connectionType == ui::DisplayConnectionType::Internal; state.isProtected = true; state.displayName = std::move(info.name); @@ -3541,9 +3622,7 @@ sp<DisplayDevice> SurfaceFlinger::setupNewDisplayDeviceInternal( getPhysicalDisplayOrientation(compositionDisplay->getId(), creationArgs.isPrimary); ALOGV("Display Orientation: %s", toCString(creationArgs.physicalOrientation)); - // virtual displays are always considered enabled - creationArgs.initialPowerMode = - state.isVirtual() ? std::make_optional(hal::PowerMode::ON) : std::nullopt; + creationArgs.initialPowerMode = state.isVirtual() ? hal::PowerMode::ON : hal::PowerMode::OFF; creationArgs.requestedRefreshRate = state.requestedRefreshRate; @@ -3662,6 +3741,27 @@ void SurfaceFlinger::processDisplayAdded(const wp<IBinder>& displayToken, } mDisplays.try_emplace(displayToken, std::move(display)); + + // For an external display, loadDisplayModes already attempted to select the same mode + // as DM, but SF still needs to be updated to match. + // TODO (b/318534874): Let DM decide the initial mode. + if (const auto& physical = state.physical; + mScheduler && physical && FlagManager::getInstance().connected_display()) { + const bool isInternalDisplay = mPhysicalDisplays.get(physical->id) + .transform(&PhysicalDisplay::isInternal) + .value_or(false); + + if (!isInternalDisplay) { + auto activeModePtr = physical->activeMode; + const auto fps = activeModePtr->getPeakFps(); + + setDesiredMode( + {.mode = scheduler::FrameRateMode{fps, + ftl::as_non_null(std::move(activeModePtr))}, + .emitEvent = false, + .force = true}); + } + } } void SurfaceFlinger::processDisplayRemoved(const wp<IBinder>& displayToken) { @@ -3725,7 +3825,7 @@ void SurfaceFlinger::processDisplayChanged(const wp<IBinder>& displayToken, // TODO(b/175678251) Call a listener instead. if (currentState.physical->hwcDisplayId == getHwComposer().getPrimaryHwcDisplayId()) { - resetPhaseConfiguration(display->getActiveMode().fps); + mScheduler->resetPhaseConfiguration(display->getActiveMode().fps); } } return; @@ -3761,15 +3861,6 @@ void SurfaceFlinger::processDisplayChanged(const wp<IBinder>& displayToken, } } -void SurfaceFlinger::resetPhaseConfiguration(Fps refreshRate) { - // Cancel the pending refresh rate change, if any, before updating the phase configuration. - mScheduler->vsyncModulator().cancelRefreshRateChange(); - - mVsyncConfiguration->reset(); - updatePhaseConfiguration(refreshRate); - mRefreshRateStats->setRefreshRate(refreshRate); -} - void SurfaceFlinger::processDisplayChangesLocked() { // here we take advantage of Vector's copy-on-write semantics to // improve performance by skipping the transaction entirely when @@ -3780,6 +3871,9 @@ void SurfaceFlinger::processDisplayChangesLocked() { mVisibleRegionsDirty = true; mUpdateInputInfo = true; + // Apply the current color matrix to any added or changed display. + mCurrentState.colorMatrixChanged = true; + // find the displays that were removed // (ie: in drawing state but not in current state) // also handle displays that changed @@ -4089,8 +4183,8 @@ void SurfaceFlinger::requestDisplayModes(std::vector<display::DisplayModeRequest if (display->refreshRateSelector().isModeAllowed(request.mode)) { setDesiredMode(std::move(request)); } else { - ALOGV("%s: Mode %d is disallowed for display %s", __func__, modePtr->getId().value(), - to_string(displayId).c_str()); + ALOGV("%s: Mode %d is disallowed for display %s", __func__, + ftl::to_underlying(modePtr->getId()), to_string(displayId).c_str()); } } } @@ -4116,8 +4210,9 @@ void SurfaceFlinger::onChoreographerAttached() { } } -void SurfaceFlinger::onVsyncGenerated(TimePoint expectedPresentTime, - ftl::NonNull<DisplayModePtr> modePtr, Fps renderRate) { +void SurfaceFlinger::onExpectedPresentTimePosted(TimePoint expectedPresentTime, + ftl::NonNull<DisplayModePtr> modePtr, + Fps renderRate) { const auto vsyncPeriod = modePtr->getVsyncRate().getPeriod(); const auto timeoutOpt = [&]() -> std::optional<Period> { const auto vrrConfig = modePtr->getVrrConfig(); @@ -4126,7 +4221,7 @@ void SurfaceFlinger::onVsyncGenerated(TimePoint expectedPresentTime, const auto notifyExpectedPresentConfig = modePtr->getVrrConfig()->notifyExpectedPresentConfig; if (!notifyExpectedPresentConfig) return std::nullopt; - return Period::fromNs(notifyExpectedPresentConfig->notifyExpectedPresentTimeoutNs); + return Period::fromNs(notifyExpectedPresentConfig->timeoutNs); }(); notifyExpectedPresentIfRequired(modePtr->getPhysicalDisplayId(), vsyncPeriod, @@ -4138,45 +4233,91 @@ void SurfaceFlinger::notifyExpectedPresentIfRequired(PhysicalDisplayId displayId TimePoint expectedPresentTime, Fps frameInterval, std::optional<Period> timeoutOpt) { - { - auto& data = mNotifyExpectedPresentMap[displayId]; - const auto lastExpectedPresentTimestamp = data.lastExpectedPresentTimestamp.load(); - const auto lastFrameInterval = data.lastFrameInterval; - data.lastFrameInterval = frameInterval; - const auto threshold = Duration::fromNs(vsyncPeriod.ns() / 2); - - const constexpr nsecs_t kOneSecondNs = - std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count(); - const auto timeout = Period::fromNs(timeoutOpt && timeoutOpt->ns() > 0 ? timeoutOpt->ns() - : kOneSecondNs); - const bool frameIntervalIsOnCadence = - isFrameIntervalOnCadence(expectedPresentTime, lastExpectedPresentTimestamp, - lastFrameInterval, timeout, threshold); - - const bool expectedPresentWithinTimeout = - isExpectedPresentWithinTimeout(expectedPresentTime, lastExpectedPresentTimestamp, - timeoutOpt, threshold); - - using fps_approx_ops::operator!=; - if (frameIntervalIsOnCadence && frameInterval != lastFrameInterval) { - data.lastExpectedPresentTimestamp = expectedPresentTime; - } - - if (expectedPresentWithinTimeout && frameIntervalIsOnCadence) { + auto& data = mNotifyExpectedPresentMap[displayId]; + const auto lastExpectedPresentTimestamp = data.lastExpectedPresentTimestamp; + const auto lastFrameInterval = data.lastFrameInterval; + data.lastFrameInterval = frameInterval; + data.lastExpectedPresentTimestamp = expectedPresentTime; + const auto threshold = Duration::fromNs(vsyncPeriod.ns() / 2); + + const constexpr nsecs_t kOneSecondNs = + std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count(); + const auto timeout = + Period::fromNs(timeoutOpt && timeoutOpt->ns() > 0 ? timeoutOpt->ns() : kOneSecondNs); + const bool frameIntervalIsOnCadence = + isFrameIntervalOnCadence(expectedPresentTime, lastExpectedPresentTimestamp, + lastFrameInterval, timeout, threshold); + + const bool expectedPresentWithinTimeout = + isExpectedPresentWithinTimeout(expectedPresentTime, lastExpectedPresentTimestamp, + timeoutOpt, threshold); + if (expectedPresentWithinTimeout && frameIntervalIsOnCadence) { + return; + } + + auto hintStatus = data.hintStatus.load(); + if (!expectedPresentWithinTimeout) { + if (hintStatus != NotifyExpectedPresentHintStatus::Sent || + (timeoutOpt && timeoutOpt->ns() == 0)) { + // Send the hint immediately if timeout, as the hint gets + // delayed otherwise, as the frame is scheduled close + // to the actual present. + if (data.hintStatus + .compare_exchange_strong(hintStatus, + NotifyExpectedPresentHintStatus::ScheduleOnTx)) { + scheduleNotifyExpectedPresentHint(displayId); + } return; } - data.lastExpectedPresentTimestamp = expectedPresentTime; + } + + if (hintStatus != NotifyExpectedPresentHintStatus::Start) { + return; + } + data.hintStatus.store(NotifyExpectedPresentHintStatus::ScheduleOnPresent); + mScheduler->scheduleFrame(); +} + +void SurfaceFlinger::scheduleNotifyExpectedPresentHint(PhysicalDisplayId displayId) { + auto itr = mNotifyExpectedPresentMap.find(displayId); + if (itr == mNotifyExpectedPresentMap.end()) { + return; } const char* const whence = __func__; - static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) { - const auto status = getHwComposer().notifyExpectedPresent(displayId, expectedPresentTime, - frameInterval); + const auto sendHint = [=, this]() { + auto& data = mNotifyExpectedPresentMap.at(displayId); + data.hintStatus.store(NotifyExpectedPresentHintStatus::Sent); + const auto status = + getHwComposer().notifyExpectedPresent(displayId, data.lastExpectedPresentTimestamp, + data.lastFrameInterval); if (status != NO_ERROR) { ALOGE("%s failed to notifyExpectedPresentHint for display %" PRId64, whence, displayId.value); } - })); + }; + + if (itr->second.hintStatus == NotifyExpectedPresentHintStatus::ScheduleOnTx) { + return static_cast<void>(mScheduler->schedule([=, + this]() FTL_FAKE_GUARD(kMainThreadContext) { + auto& data = mNotifyExpectedPresentMap.at(displayId); + auto scheduleHintOnTx = NotifyExpectedPresentHintStatus::ScheduleOnTx; + if (data.hintStatus.compare_exchange_strong(scheduleHintOnTx, + NotifyExpectedPresentHintStatus::Sent)) { + sendHint(); + } + })); + } + sendHint(); +} + +void SurfaceFlinger::sendNotifyExpectedPresentHint(PhysicalDisplayId displayId) { + if (auto itr = mNotifyExpectedPresentMap.find(displayId); + itr == mNotifyExpectedPresentMap.end() || + itr->second.hintStatus != NotifyExpectedPresentHintStatus::ScheduleOnPresent) { + return; + } + scheduleNotifyExpectedPresentHint(displayId); } void SurfaceFlinger::initScheduler(const sp<const DisplayDevice>& display) { @@ -4186,10 +4327,6 @@ void SurfaceFlinger::initScheduler(const sp<const DisplayDevice>& display) { const auto activeMode = display->refreshRateSelector().getActiveMode(); const Fps activeRefreshRate = activeMode.fps; - mRefreshRateStats = - std::make_unique<RefreshRateStats>(*mTimeStats, activeRefreshRate, hal::PowerMode::OFF); - - mVsyncConfiguration = getFactory().createVsyncConfiguration(activeRefreshRate); FeatureFlags features; @@ -4215,20 +4352,21 @@ void SurfaceFlinger::initScheduler(const sp<const DisplayDevice>& display) { if (mBackpressureGpuComposition) { features |= Feature::kBackpressureGpuComposition; } - - auto modulatorPtr = sp<VsyncModulator>::make(mVsyncConfiguration->getCurrentConfigs()); + if (getHwComposer().getComposer()->isSupported( + Hwc2::Composer::OptionalFeature::ExpectedPresentTime)) { + features |= Feature::kExpectedPresentTime; + } mScheduler = std::make_unique<Scheduler>(static_cast<ICompositor&>(*this), static_cast<ISchedulerCallback&>(*this), features, - std::move(modulatorPtr), - static_cast<IVsyncTrackerCallback&>(*this)); + getFactory(), activeRefreshRate, *mTimeStats); mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector()); if (FlagManager::getInstance().vrr_config()) { mScheduler->setRenderRate(display->getPhysicalId(), activeMode.fps); } mScheduler->startTimers(); - const auto configs = mVsyncConfiguration->getCurrentConfigs(); + const auto configs = mScheduler->getVsyncConfiguration().getCurrentConfigs(); mAppConnectionHandle = mScheduler->createEventThread(Scheduler::Cycle::Render, @@ -4241,22 +4379,12 @@ void SurfaceFlinger::initScheduler(const sp<const DisplayDevice>& display) { /* workDuration */ activeRefreshRate.getPeriod(), /* readyDuration */ configs.late.sfWorkDuration); - mScheduler->initVsync(mScheduler->getVsyncSchedule()->getDispatch(), - *mFrameTimeline->getTokenManager(), configs.late.sfWorkDuration); + mScheduler->initVsync(*mFrameTimeline->getTokenManager(), configs.late.sfWorkDuration); mRegionSamplingThread = sp<RegionSamplingThread>::make(*this, RegionSamplingThread::EnvironmentTimingTunables()); - mFpsReporter = sp<FpsReporter>::make(*mFrameTimeline, *this); - - mIsHotplugErrViaNegVsync = - base::GetBoolProperty("debug.sf.hwc_hotplug_error_via_neg_vsync"s, false); -} - -void SurfaceFlinger::updatePhaseConfiguration(Fps refreshRate) { - mVsyncConfiguration->setRefreshRateFps(refreshRate); - mScheduler->setVsyncConfigSet(mVsyncConfiguration->getCurrentConfigs(), - refreshRate.getPeriod()); + mFpsReporter = sp<FpsReporter>::make(*mFrameTimeline); } void SurfaceFlinger::doCommitTransactions() { @@ -4290,7 +4418,6 @@ void SurfaceFlinger::doCommitTransactions() { } mDrawingState = mCurrentState; - // clear the "changed" flags in current state mCurrentState.colorMatrixChanged = false; if (mVisibleRegionsDirty) { @@ -4438,7 +4565,7 @@ status_t SurfaceFlinger::addClientLayer(LayerCreationArgs& args, const sp<IBinde if (mNumLayers >= MAX_LAYERS) { ALOGE("AddClientLayer failed, mNumLayers (%zu) >= MAX_LAYERS (%zu)", mNumLayers.load(), MAX_LAYERS); - static_cast<void>(mScheduler->schedule([=] { + static_cast<void>(mScheduler->schedule([=, this] { ALOGE("Dumping layer keeping > 20 children alive:"); bool leakingParentLayerFound = false; mDrawingState.traverse([&](Layer* layer) { @@ -5433,6 +5560,11 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime flags |= eTraversalNeeded; } } + if (what & layer_state_t::eDesiredHdrHeadroomChanged) { + if (layer->setDesiredHdrHeadroom(s.desiredHdrSdrRatio)) { + flags |= eTraversalNeeded; + } + } if (what & layer_state_t::eCachingHintChanged) { if (layer->setCachingHint(s.cachingHint)) { flags |= eTraversalNeeded; @@ -5618,6 +5750,11 @@ uint32_t SurfaceFlinger::updateLayerCallbacksAndStats(const FrameTimelineInfo& f flags |= eTraversalNeeded; } } + if (what & layer_state_t::eDesiredHdrHeadroomChanged) { + if (layer->setDesiredHdrHeadroom(s.desiredHdrSdrRatio)) { + flags |= eTraversalNeeded; + } + } if (what & layer_state_t::eBufferChanged) { std::optional<ui::Transform::RotationFlags> transformHint = std::nullopt; frontend::LayerSnapshot* snapshot = mLayerSnapshotBuilder.getSnapshot(layer->sequence); @@ -5750,7 +5887,7 @@ status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, gui::CreateSurface case ISurfaceComposerClient::eFXSurfaceContainer: case ISurfaceComposerClient::eFXSurfaceBufferState: args.flags |= ISurfaceComposerClient::eNoColorFill; - FMT_FALLTHROUGH; + [[fallthrough]]; case ISurfaceComposerClient::eFXSurfaceEffect: { result = createBufferStateLayer(args, &outResult.handle, &layer); std::atomic<int32_t>* pendingBufferCounter = layer->getPendingBufferCounter(); @@ -5828,12 +5965,6 @@ void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32 } void SurfaceFlinger::initializeDisplays() { - const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()); - if (!display) return; - - const sp<IBinder> token = display->getDisplayToken().promote(); - LOG_ALWAYS_FATAL_IF(token == nullptr); - TransactionState state; state.inputWindowCommands = mInputWindowCommands; const nsecs_t now = systemTime(); @@ -5844,18 +5975,10 @@ void SurfaceFlinger::initializeDisplays() { const uint64_t transactionId = (static_cast<uint64_t>(mPid) << 32) | mUniqueTransactionId++; state.id = transactionId; - // reset screen orientation and use primary layer stack - DisplayState d; - d.what = DisplayState::eDisplayProjectionChanged | - DisplayState::eLayerStackChanged; - d.token = token; - d.layerStack = ui::DEFAULT_LAYER_STACK; - d.orientation = ui::ROTATION_0; - d.orientedDisplaySpaceRect.makeInvalid(); - d.layerStackSpaceRect.makeInvalid(); - d.width = 0; - d.height = 0; - state.displays.add(d); + auto layerStack = ui::DEFAULT_LAYER_STACK.id; + for (const auto& [id, display] : FTL_FAKE_GUARD(mStateLock, mPhysicalDisplays)) { + state.displays.push(DisplayState(display.token(), ui::LayerStack::fromValue(layerStack++))); + } std::vector<TransactionState> transactions; transactions.emplace_back(state); @@ -5868,12 +5991,25 @@ void SurfaceFlinger::initializeDisplays() { { ftl::FakeGuard guard(mStateLock); - setPowerModeInternal(display, hal::PowerMode::ON); + + // In case of a restart, ensure all displays are off. + for (const auto& [id, display] : mPhysicalDisplays) { + setPowerModeInternal(getDisplayDeviceLocked(id), hal::PowerMode::OFF); + } + + // Power on all displays. The primary display is first, so becomes the active display. Also, + // the DisplayCapability set of a display is populated on its first powering on. Do this now + // before responding to any Binder query from DisplayManager about display capabilities. + for (const auto& [id, display] : mPhysicalDisplays) { + setPowerModeInternal(getDisplayDeviceLocked(id), hal::PowerMode::ON); + } } } void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal::PowerMode mode) { if (display->isVirtual()) { + // TODO(b/241285876): This code path should not be reachable, so enforce this at compile + // time. ALOGE("%s: Invalid operation on virtual display", __func__); return; } @@ -5881,8 +6017,8 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal: const auto displayId = display->getPhysicalId(); ALOGD("Setting power mode %d on display %s", mode, to_string(displayId).c_str()); - const auto currentModeOpt = display->getPowerMode(); - if (currentModeOpt == mode) { + const auto currentMode = display->getPowerMode(); + if (currentMode == mode) { return; } @@ -5899,7 +6035,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal: display->setPowerMode(mode); const auto activeMode = display->refreshRateSelector().getActiveMode().modePtr; - if (!currentModeOpt || *currentModeOpt == hal::PowerMode::OFF) { + if (currentMode == hal::PowerMode::OFF) { // Turn on the display // Activate the display (which involves a modeset to the active mode) when the inner or @@ -5944,7 +6080,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal: mVisibleRegionsDirty = true; scheduleComposite(FrameHint::kActive); } else if (mode == hal::PowerMode::OFF) { - const bool currentModeNotDozeSuspend = (*currentModeOpt != hal::PowerMode::DOZE_SUSPEND); + const bool currentModeNotDozeSuspend = (currentMode != hal::PowerMode::DOZE_SUSPEND); // Turn off the display if (displayId == mActiveDisplayId) { if (const auto display = getActivatableDisplay()) { @@ -5985,7 +6121,7 @@ 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 (*currentModeOpt == hal::PowerMode::DOZE_SUSPEND && + if (currentMode == hal::PowerMode::DOZE_SUSPEND && (displayId == mActiveDisplayId || FlagManager::getInstance().multithreaded_present())) { if (displayId == mActiveDisplayId) { ALOGI("Force repainting for DOZE_SUSPEND -> DOZE or ON."); @@ -6013,7 +6149,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal: if (displayId == mActiveDisplayId) { mTimeStats->setPowerMode(mode); - mRefreshRateStats->setPowerMode(mode); + mScheduler->setActiveDisplayPowerModeForRefreshRateStats(mode); } mScheduler->setDisplayPowerMode(displayId, mode); @@ -6022,7 +6158,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal: } void SurfaceFlinger::setPowerMode(const sp<IBinder>& displayToken, int mode) { - auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD( + auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD( kMainThreadContext) { const auto display = getDisplayDeviceLocked(displayToken); if (!display) { @@ -6178,10 +6314,6 @@ void SurfaceFlinger::dumpScheduler(std::string& result) const { dumper.dump("debugDisplayModeSetByBackdoor"sv, mDebugDisplayModeSetByBackdoor); dumper.eol(); - mRefreshRateStats->dump(result); - dumper.eol(); - - mVsyncConfiguration->dump(result); StringAppendF(&result, " present offset: %9" PRId64 " ns\t VSYNC period: %9" PRId64 " ns\n\n", @@ -6471,7 +6603,7 @@ void SurfaceFlinger::dumpOffscreenLayersProto(perfetto::protos::LayersProto& lay } perfetto::protos::LayersProto SurfaceFlinger::dumpProtoFromMainThread(uint32_t traceFlags) { - return mScheduler->schedule([=] { return dumpDrawingStateProto(traceFlags); }).get(); + return mScheduler->schedule([=, this] { return dumpDrawingStateProto(traceFlags); }).get(); } void SurfaceFlinger::dumpOffscreenLayers(std::string& result) { @@ -7392,7 +7524,7 @@ void SurfaceFlinger::kernelTimerChanged(bool expired) { // Update the overlay on the main thread to avoid race conditions with // RefreshRateSelector::getActiveMode - static_cast<void>(mScheduler->schedule([=] { + static_cast<void>(mScheduler->schedule([=, this] { const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()); if (!display) { ALOGW("%s: default display is null", __func__); @@ -7762,6 +7894,12 @@ void SurfaceFlinger::captureDisplay(DisplayId displayId, const CaptureArgs& args kAllowProtected, kGrayscale, captureListener); } +ScreenCaptureResults SurfaceFlinger::captureLayersSync(const LayerCaptureArgs& args) { + sp<SyncScreenCaptureListener> captureListener = sp<SyncScreenCaptureListener>::make(); + captureLayers(args, captureListener); + return captureListener->waitForResults(); +} + void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, const sp<IScreenCaptureListener>& captureListener) { ATRACE_CALL(); @@ -7833,7 +7971,7 @@ void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, } bool childrenOnly = args.childrenOnly; - RenderAreaFuture renderAreaFuture = ftl::defer([=]() -> std::unique_ptr<RenderArea> { + RenderAreaFuture renderAreaFuture = ftl::defer([=, this]() -> std::unique_ptr<RenderArea> { ui::Transform layerTransform; Rect layerBufferSize; if (mLayerLifecycleManagerEnabled) { @@ -7972,7 +8110,7 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenCommon( ATRACE_CALL(); auto future = mScheduler->schedule( - [=, renderAreaFuture = std::move(renderAreaFuture)]() FTL_FAKE_GUARD( + [=, this, renderAreaFuture = std::move(renderAreaFuture)]() FTL_FAKE_GUARD( kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> { ScreenCaptureResults captureResults; std::shared_ptr<RenderArea> renderArea = renderAreaFuture.get(); @@ -8171,23 +8309,27 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl( // // TODO(b/196334700) Once we use RenderEngineThreaded everywhere we can always defer the call // to CompositionEngine::present. - const bool renderEngineIsThreaded = [&]() { - using Type = renderengine::RenderEngine::RenderEngineType; - const auto type = mRenderEngine->getRenderEngineType(); - return type == Type::SKIA_GL_THREADED; - }(); - auto presentFuture = renderEngineIsThreaded ? ftl::defer(std::move(present)).share() - : ftl::yield(present()).share(); + auto presentFuture = mRenderEngine->isThreaded() ? ftl::defer(std::move(present)).share() + : ftl::yield(present()).share(); for (auto& [layer, layerFE] : layers) { - layer->onLayerDisplayed(ftl::Future(presentFuture) - .then([layerFE = std::move(layerFE)](FenceResult) { - return layerFE->stealCompositionResult() - .releaseFences.back() - .first.get(); - }) - .share(), - ui::INVALID_LAYER_STACK); + layer->onLayerDisplayed(presentFuture, ui::INVALID_LAYER_STACK, + [layerFE = std::move(layerFE)](FenceResult) { + if (FlagManager::getInstance() + .screenshot_fence_preservation()) { + const auto compositionResult = + layerFE->stealCompositionResult(); + const auto& fences = compositionResult.releaseFences; + // CompositionEngine may choose to cull layers that + // aren't visible, so pass a non-fence. + return fences.empty() ? Fence::NO_FENCE + : fences.back().first.get(); + } else { + return layerFE->stealCompositionResult() + .releaseFences.back() + .first.get(); + } + }); } return presentFuture; @@ -8346,15 +8488,15 @@ status_t SurfaceFlinger::applyRefreshRateSelectorPolicy( const auto preferredModeId = preferredMode.modePtr->getId(); const Fps preferredFps = preferredMode.fps; - ALOGV("Switching to Scheduler preferred mode %d (%s)", preferredModeId.value(), + ALOGV("Switching to Scheduler preferred mode %d (%s)", ftl::to_underlying(preferredModeId), to_string(preferredFps).c_str()); if (!selector.isModeAllowed(preferredMode)) { - ALOGE("%s: Preferred mode %d is disallowed", __func__, preferredModeId.value()); + ALOGE("%s: Preferred mode %d is disallowed", __func__, ftl::to_underlying(preferredModeId)); return INVALID_OPERATION; } - setDesiredMode({std::move(preferredMode), .emitEvent = true}, force); + setDesiredMode({std::move(preferredMode), .emitEvent = true, .force = force}); // Update the frameRateOverride list as the display render rate might have changed if (mScheduler->updateFrameRateOverrides(scheduler::GlobalSignals{}, preferredFps)) { @@ -8397,7 +8539,7 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecs(const sp<IBinder>& displayTo return BAD_VALUE; } - auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t { + auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t { const auto display = FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(displayToken)); if (!display) { ALOGE("Attempt to set desired display modes for invalid display token %p", @@ -8438,7 +8580,7 @@ status_t SurfaceFlinger::getDesiredDisplayModeSpecs(const sp<IBinder>& displayTo scheduler::RefreshRateSelector::Policy policy = display->refreshRateSelector().getDisplayManagerPolicy(); - outSpecs->defaultMode = policy.defaultMode.value(); + outSpecs->defaultMode = ftl::to_underlying(policy.defaultMode); outSpecs->allowGroupSwitching = policy.allowGroupSwitching; outSpecs->primaryRanges = translate(policy.primaryRanges); outSpecs->appRequestRanges = translate(policy.appRequestRanges); @@ -8621,7 +8763,8 @@ uint32_t SurfaceFlinger::getMaxAcquiredBufferCountForCurrentRefreshRate(uid_t ui } int SurfaceFlinger::getMaxAcquiredBufferCountForRefreshRate(Fps refreshRate) const { - const auto vsyncConfig = mVsyncConfiguration->getConfigsForRefreshRate(refreshRate).late; + const auto vsyncConfig = + mScheduler->getVsyncConfiguration().getConfigsForRefreshRate(refreshRate).late; const auto presentLatency = vsyncConfig.appWorkDuration + vsyncConfig.sfWorkDuration; return calculateMaxAcquiredBufferCount(refreshRate, presentLatency); } @@ -8714,7 +8857,7 @@ void SurfaceFlinger::onActiveDisplayChangedLocked(const DisplayDevice* inactiveD mActiveDisplayId = activeDisplay.getPhysicalId(); activeDisplay.getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true); - resetPhaseConfiguration(activeDisplay.getActiveMode().fps); + mScheduler->resetPhaseConfiguration(activeDisplay.getActiveMode().fps); // TODO(b/255635711): Check for pending mode changes on other displays. mScheduler->setModeChangePending(false); @@ -8752,6 +8895,40 @@ status_t SurfaceFlinger::getStalledTransactionInfo( return NO_ERROR; } +void SurfaceFlinger::updateHdcpLevels(hal::HWDisplayId hwcDisplayId, int32_t connectedLevel, + int32_t maxLevel) { + if (!FlagManager::getInstance().connected_display()) { + return; + } + + Mutex::Autolock lock(mStateLock); + + const auto idOpt = getHwComposer().toPhysicalDisplayId(hwcDisplayId); + if (!idOpt) { + ALOGE("No display found for HDCP level changed event: connected=%d, max=%d for " + "display=%" PRIu64, + connectedLevel, maxLevel, hwcDisplayId); + return; + } + + const bool isInternalDisplay = + mPhysicalDisplays.get(*idOpt).transform(&PhysicalDisplay::isInternal).value_or(false); + if (isInternalDisplay) { + ALOGW("Unexpected HDCP level changed for internal display: connected=%d, max=%d for " + "display=%" PRIu64, + connectedLevel, maxLevel, hwcDisplayId); + return; + } + + static_cast<void>(mScheduler->schedule([this, displayId = *idOpt, connectedLevel, maxLevel]() { + if (const auto display = FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(displayId))) { + Mutex::Autolock lock(mStateLock); + display->setSecure(connectedLevel >= 2 /* HDCP_V1 */); + } + mScheduler->onHdcpLevelsChanged(mAppConnectionHandle, displayId, connectedLevel, maxLevel); + })); +} + std::shared_ptr<renderengine::ExternalTexture> SurfaceFlinger::getExternalTextureFromBufferData( BufferData& bufferData, const char* layerName, uint64_t transactionId) { if (bufferData.buffer && @@ -9571,6 +9748,12 @@ binder::Status SurfaceComposerAIDL::captureDisplayById( return binderStatusFromStatusT(NO_ERROR); } +binder::Status SurfaceComposerAIDL::captureLayersSync(const LayerCaptureArgs& args, + ScreenCaptureResults* outResults) { + *outResults = mFlinger->captureLayersSync(args); + return binderStatusFromStatusT(NO_ERROR); +} + binder::Status SurfaceComposerAIDL::captureLayers( const LayerCaptureArgs& args, const sp<IScreenCaptureListener>& captureListener) { mFlinger->captureLayers(args, captureListener); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 6b4440193b..be057979f9 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -82,7 +82,6 @@ #include "MutexUtils.h" #include "Scheduler/ISchedulerCallback.h" #include "Scheduler/RefreshRateSelector.h" -#include "Scheduler/RefreshRateStats.h" #include "Scheduler/Scheduler.h" #include "SurfaceFlingerFactory.h" #include "ThreadContext.h" @@ -200,8 +199,7 @@ class SurfaceFlinger : public BnSurfaceComposer, private HWC2::ComposerCallback, private ICompositor, private scheduler::ISchedulerCallback, - private compositionengine::ICEPowerCallback, - private scheduler::IVsyncTrackerCallback { + private compositionengine::ICEPowerCallback { public: struct SkipInitializationTag {}; @@ -561,6 +559,7 @@ private: void captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&); void captureDisplay(DisplayId, const CaptureArgs&, const sp<IScreenCaptureListener>&); + ScreenCaptureResults captureLayersSync(const LayerCaptureArgs&); void captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&); status_t getDisplayStats(const sp<IBinder>& displayToken, DisplayStatInfo* stats); @@ -654,6 +653,8 @@ private: status_t getStalledTransactionInfo( int pid, std::optional<TransactionHandler::StalledTransactionInfo>& result); + void updateHdcpLevels(hal::HWDisplayId hwcDisplayId, int32_t connectedLevel, int32_t maxLevel); + // Implements IBinder::DeathRecipient. void binderDied(const wp<IBinder>& who) override; @@ -684,14 +685,12 @@ private: void kernelTimerChanged(bool expired) override; void triggerOnFrameRateOverridesChanged() override; void onChoreographerAttached() override; + void onExpectedPresentTimePosted(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>, + Fps renderRate) override; // ICEPowerCallback overrides: void notifyCpuLoadUp() override; - // IVsyncTrackerCallback overrides - void onVsyncGenerated(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>, - Fps renderRate) override; - // Toggles the kernel idle timer on or off depending the policy decisions around refresh rates. void toggleKernelIdleTimer() REQUIRES(mStateLock); @@ -717,7 +716,7 @@ private: // Show hdr sdr ratio overlay bool mHdrSdrRatioOverlay = false; - void setDesiredMode(display::DisplayModeRequest&&, bool force = false) REQUIRES(mStateLock); + void setDesiredMode(display::DisplayModeRequest&&) REQUIRES(mStateLock); status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId, Fps minFps, Fps maxFps); @@ -783,9 +782,6 @@ private: void initScheduler(const sp<const DisplayDevice>&) REQUIRES(kMainThreadContext, mStateLock); - void resetPhaseConfiguration(Fps) REQUIRES(mStateLock, kMainThreadContext); - void updatePhaseConfiguration(Fps) REQUIRES(mStateLock); - /* * Transactions */ @@ -905,7 +901,8 @@ private: * Display and layer stack management */ - // Called during boot, and restart after system_server death. + // Called during boot and restart after system_server death, setting the stage for bootanimation + // before DisplayManager takes over. void initializeDisplays() REQUIRES(kMainThreadContext); sp<const DisplayDevice> getDisplayDeviceLocked(const wp<IBinder>& displayToken) const @@ -1214,12 +1211,6 @@ private: float mGlobalSaturationFactor = 1.0f; mat4 mClientColorMatrix; - size_t mMaxGraphicBufferProducerListSize = MAX_LAYERS; - // If there are more GraphicBufferProducers tracked by SurfaceFlinger than - // this threshold, then begin logging. - size_t mGraphicBufferProducerListSizeLogThreshold = - static_cast<size_t>(0.95 * static_cast<double>(MAX_LAYERS)); - // protected by mStateLock (but we could use another lock) bool mLayersRemoved = false; bool mLayersAdded = false; @@ -1276,6 +1267,7 @@ private: hal::Connection connection = hal::Connection::INVALID; }; + bool mIsHdcpViaNegVsync = false; bool mIsHotplugErrViaNegVsync = false; std::mutex mHotplugMutex; @@ -1371,10 +1363,6 @@ private: scheduler::ConnectionHandle mAppConnectionHandle; scheduler::ConnectionHandle mSfConnectionHandle; - // Stores phase offsets configured per refresh rate. - std::unique_ptr<scheduler::VsyncConfiguration> mVsyncConfiguration; - - std::unique_ptr<scheduler::RefreshRateStats> mRefreshRateStats; scheduler::PresentLatencyTracker mPresentLatencyTracker GUARDED_BY(kMainThreadContext); bool mLumaSampling = true; @@ -1469,7 +1457,7 @@ private: bool mLegacyFrontEndEnabled = true; frontend::LayerLifecycleManager mLayerLifecycleManager; - frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}}; + frontend::LayerHierarchyBuilder mLayerHierarchyBuilder; frontend::LayerSnapshotBuilder mLayerSnapshotBuilder; std::vector<std::pair<uint32_t, std::string>> mDestroyedHandles; @@ -1491,14 +1479,28 @@ private: ftl::SmallMap<int64_t, sp<SurfaceControl>, 3> mMirrorMapForDebug; // NotifyExpectedPresentHint + enum class NotifyExpectedPresentHintStatus { + // Represents that framework can start sending hint if required. + Start, + // Represents that the hint is already sent. + Sent, + // Represents that the hint will be scheduled with a new frame. + ScheduleOnPresent, + // Represents that a hint will be sent instantly by scheduling on the main thread. + ScheduleOnTx + }; struct NotifyExpectedPresentData { - // lastExpectedPresentTimestamp is read and write from multiple threads such as - // main thread, EventThread, MessageQueue. And is atomic for that reason. - std::atomic<TimePoint> lastExpectedPresentTimestamp{}; + TimePoint lastExpectedPresentTimestamp{}; Fps lastFrameInterval{}; + // hintStatus is read and write from multiple threads such as + // main thread, EventThread. And is atomic for that reason. + std::atomic<NotifyExpectedPresentHintStatus> hintStatus = + NotifyExpectedPresentHintStatus::Start; }; std::unordered_map<PhysicalDisplayId, NotifyExpectedPresentData> mNotifyExpectedPresentMap; - + void sendNotifyExpectedPresentHint(PhysicalDisplayId displayId) override + REQUIRES(kMainThreadContext); + void scheduleNotifyExpectedPresentHint(PhysicalDisplayId displayId); void notifyExpectedPresentIfRequired(PhysicalDisplayId, Period vsyncPeriod, TimePoint expectedPresentTime, Fps frameInterval, std::optional<Period> timeoutOpt); @@ -1557,6 +1559,7 @@ public: const sp<IScreenCaptureListener>&) override; binder::Status captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&) override; + binder::Status captureLayersSync(const LayerCaptureArgs&, ScreenCaptureResults* results); // TODO(b/239076119): Remove deprecated AIDL. [[deprecated]] binder::Status clearAnimationFrameStats() override { diff --git a/services/surfaceflinger/Tracing/TransactionTracing.h b/services/surfaceflinger/Tracing/TransactionTracing.h index ddbf3e4873..6a66fff75f 100644 --- a/services/surfaceflinger/Tracing/TransactionTracing.h +++ b/services/surfaceflinger/Tracing/TransactionTracing.h @@ -218,6 +218,13 @@ class TransactionTraceWriter : public Singleton<TransactionTraceWriter> { friend class Singleton<TransactionTracing>; std::function<void(const std::string& prefix, bool overwrite)> mWriterFunction = [](const std::string&, bool) {}; + std::atomic<bool> mEnabled{true}; + + void doInvoke(const std::string& filename, bool overwrite) { + if (mEnabled) { + mWriterFunction(filename, overwrite); + } + }; public: void setWriterFunction( @@ -225,12 +232,15 @@ public: mWriterFunction = std::move(function); } void invoke(const std::string& prefix, bool overwrite) { - mWriterFunction(TransactionTracing::getFilePath(prefix), overwrite); + doInvoke(TransactionTracing::getFilePath(prefix), overwrite); } /* pass in a complete file path for testing */ void invokeForTest(const std::string& filename, bool overwrite) { - mWriterFunction(filename, overwrite); + doInvoke(filename, overwrite); } + /* hacky way to avoid generating traces when converting transaction trace to layers trace. */ + void disable() { mEnabled.store(false); } + void enable() { mEnabled.store(true); } }; } // namespace android diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index c2d1954ee5..617ea2c566 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -40,9 +40,24 @@ namespace android { using namespace ftl::flag_operators; +namespace { +class ScopedTraceDisabler { +public: + ScopedTraceDisabler() { TransactionTraceWriter::getInstance().disable(); } + ~ScopedTraceDisabler() { TransactionTraceWriter::getInstance().enable(); } +}; +} // namespace + bool LayerTraceGenerator::generate(const perfetto::protos::TransactionTraceFile& traceFile, std::uint32_t traceFlags, LayerTracing& layerTracing, bool onlyLastEntry) { + // We are generating the layers trace by replaying back a set of transactions. If the + // transactions have unexpected states, we may generate a transaction trace to debug + // the unexpected state. This is silly. So we disable it by poking the + // TransactionTraceWriter. This is really a hack since we should manage our depenecies a + // little better. + ScopedTraceDisabler fatalErrorTraceDisabler; + if (traceFile.entry_size() == 0) { ALOGD("Trace file is empty"); return false; @@ -52,7 +67,7 @@ bool LayerTraceGenerator::generate(const perfetto::protos::TransactionTraceFile& // frontend frontend::LayerLifecycleManager lifecycleManager; - frontend::LayerHierarchyBuilder hierarchyBuilder{{}}; + frontend::LayerHierarchyBuilder hierarchyBuilder; frontend::LayerSnapshotBuilder snapshotBuilder; ui::DisplayMap<ui::LayerStack, frontend::DisplayInfo> displayInfos; @@ -119,12 +134,10 @@ bool LayerTraceGenerator::generate(const perfetto::protos::TransactionTraceFile& lifecycleManager.applyTransactions(transactions, /*ignoreUnknownHandles=*/true); lifecycleManager.onHandlesDestroyed(destroyedHandles, /*ignoreUnknownHandles=*/true); - if (lifecycleManager.getGlobalChanges().test( - frontend::RequestedLayerState::Changes::Hierarchy)) { - hierarchyBuilder.update(lifecycleManager.getLayers(), - lifecycleManager.getDestroyedLayers()); - } + // update hierarchy + hierarchyBuilder.update(lifecycleManager); + // update snapshots frontend::LayerSnapshotBuilder::Args args{.root = hierarchyBuilder.getHierarchy(), .layerLifecycleManager = lifecycleManager, .displays = displayInfos, diff --git a/services/surfaceflinger/TransactionCallbackInvoker.cpp b/services/surfaceflinger/TransactionCallbackInvoker.cpp index 6a155c17df..7b5298c82e 100644 --- a/services/surfaceflinger/TransactionCallbackInvoker.cpp +++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp @@ -25,6 +25,7 @@ #include "TransactionCallbackInvoker.h" #include "BackgroundExecutor.h" +#include "Utils/FenceUtils.h" #include <cinttypes> @@ -127,33 +128,8 @@ status_t TransactionCallbackInvoker::addCallbackHandle(const sp<CallbackHandle>& sp<IBinder> surfaceControl = handle->surfaceControl.promote(); if (surfaceControl) { sp<Fence> prevFence = nullptr; - for (const auto& future : handle->previousReleaseFences) { - sp<Fence> currentFence = future.get().value_or(Fence::NO_FENCE); - if (prevFence == nullptr && currentFence->getStatus() != Fence::Status::Invalid) { - prevFence = std::move(currentFence); - } else if (prevFence != nullptr) { - // If both fences are signaled or both are unsignaled, we need to merge - // them to get an accurate timestamp. - if (prevFence->getStatus() != Fence::Status::Invalid && - prevFence->getStatus() == currentFence->getStatus()) { - char fenceName[32] = {}; - snprintf(fenceName, 32, "%.28s", handle->name.c_str()); - sp<Fence> mergedFence = Fence::merge(fenceName, prevFence, currentFence); - if (mergedFence->isValid()) { - prevFence = std::move(mergedFence); - } - } else if (currentFence->getStatus() == Fence::Status::Unsignaled) { - // If one fence has signaled and the other hasn't, the unsignaled - // fence will approximately correspond with the correct timestamp. - // There's a small race if both fences signal at about the same time - // and their statuses are retrieved with unfortunate timing. However, - // 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. - prevFence = std::move(currentFence); - } - } + mergeFence(handle->name.c_str(), future.get().value_or(Fence::NO_FENCE), prevFence); } handle->previousReleaseFence = prevFence; handle->previousReleaseFences.clear(); diff --git a/services/surfaceflinger/Utils/Dumper.h b/services/surfaceflinger/Utils/Dumper.h index ee942177e5..62d2ebb76d 100644 --- a/services/surfaceflinger/Utils/Dumper.h +++ b/services/surfaceflinger/Utils/Dumper.h @@ -35,6 +35,8 @@ public: void eol() { mOut += '\n'; } + std::string& out() { return mOut; } + void dump(std::string_view name, std::string_view value = {}) { using namespace std::string_view_literals; diff --git a/services/surfaceflinger/Utils/FenceUtils.h b/services/surfaceflinger/Utils/FenceUtils.h new file mode 100644 index 0000000000..f6f70062df --- /dev/null +++ b/services/surfaceflinger/Utils/FenceUtils.h @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2024 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 <ui/Fence.h> + +namespace android { + +// TODO: measure if Fence::merge is cheaper +inline void mergeFence(const char* debugName, sp<Fence>&& incomingFence, sp<Fence>& prevFence) { + if (prevFence == nullptr && incomingFence->getStatus() != Fence::Status::Invalid) { + prevFence = std::move(incomingFence); + } else if (prevFence != nullptr) { + // If both fences are signaled or both are unsignaled, we need to merge + // them to get an accurate timestamp. + if (prevFence->getStatus() != Fence::Status::Invalid && + prevFence->getStatus() == incomingFence->getStatus()) { + char fenceName[32] = {}; + snprintf(fenceName, 32, "%.28s", debugName); + sp<Fence> mergedFence = Fence::merge(fenceName, prevFence, incomingFence); + if (mergedFence->isValid()) { + prevFence = std::move(mergedFence); + } + } else if (incomingFence->getStatus() == Fence::Status::Unsignaled) { + // If one fence has signaled and the other hasn't, the unsignaled + // fence will approximately correspond with the correct timestamp. + // There's a small race if both fences signal at about the same time + // and their statuses are retrieved with unfortunate timing. However, + // 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. + prevFence = std::move(incomingFence); + } + } +} + +} // namespace android diff --git a/services/surfaceflinger/common/Android.bp b/services/surfaceflinger/common/Android.bp index 5ef22b590b..e125bbefe5 100644 --- a/services/surfaceflinger/common/Android.bp +++ b/services/surfaceflinger/common/Android.bp @@ -18,7 +18,7 @@ cc_defaults { "server_configurable_flags", ], static_libs: [ - "librenderengine", + "librenderengine_includes", ], srcs: [ "FlagManager.cpp", diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp index cb1faee6b9..f7adc0e5d6 100644 --- a/services/surfaceflinger/common/FlagManager.cpp +++ b/services/surfaceflinger/common/FlagManager.cpp @@ -108,8 +108,6 @@ void FlagManager::dump(std::string& result) const { DUMP_SERVER_FLAG(use_skia_tracing); /// Trunk stable server flags /// - DUMP_SERVER_FLAG(late_boot_misc2); - DUMP_SERVER_FLAG(dont_skip_on_early); DUMP_SERVER_FLAG(refresh_rate_overlay_on_external_display); /// Trunk stable readonly flags /// @@ -122,12 +120,17 @@ void FlagManager::dump(std::string& result) const { DUMP_READ_ONLY_FLAG(multithreaded_present); DUMP_READ_ONLY_FLAG(add_sf_skipped_frames_to_trace); DUMP_READ_ONLY_FLAG(use_known_refresh_rate_for_fps_consistency); - DUMP_READ_ONLY_FLAG(cache_if_source_crop_layer_only_moved); + DUMP_READ_ONLY_FLAG(cache_when_source_crop_layer_only_moved); DUMP_READ_ONLY_FLAG(enable_fro_dependent_features); DUMP_READ_ONLY_FLAG(display_protected); DUMP_READ_ONLY_FLAG(fp16_client_target); DUMP_READ_ONLY_FLAG(game_default_frame_rate); DUMP_READ_ONLY_FLAG(enable_layer_command_batching); + DUMP_READ_ONLY_FLAG(screenshot_fence_preservation); + DUMP_READ_ONLY_FLAG(vulkan_renderengine); + DUMP_READ_ONLY_FLAG(renderable_buffer_usage); + DUMP_READ_ONLY_FLAG(restore_blur_step); + DUMP_READ_ONLY_FLAG(dont_skip_on_early_ro); #undef DUMP_READ_ONLY_FLAG #undef DUMP_SERVER_FLAG #undef DUMP_FLAG_INTERVAL @@ -195,23 +198,20 @@ FLAG_MANAGER_READ_ONLY_FLAG(hdcp_level_hal, "") FLAG_MANAGER_READ_ONLY_FLAG(multithreaded_present, "debug.sf.multithreaded_present") FLAG_MANAGER_READ_ONLY_FLAG(add_sf_skipped_frames_to_trace, "") FLAG_MANAGER_READ_ONLY_FLAG(use_known_refresh_rate_for_fps_consistency, "") -FLAG_MANAGER_READ_ONLY_FLAG(cache_if_source_crop_layer_only_moved, +FLAG_MANAGER_READ_ONLY_FLAG(cache_when_source_crop_layer_only_moved, "debug.sf.cache_source_crop_only_moved") FLAG_MANAGER_READ_ONLY_FLAG(enable_fro_dependent_features, "") FLAG_MANAGER_READ_ONLY_FLAG(display_protected, "") FLAG_MANAGER_READ_ONLY_FLAG(fp16_client_target, "debug.sf.fp16_client_target") FLAG_MANAGER_READ_ONLY_FLAG(game_default_frame_rate, "") FLAG_MANAGER_READ_ONLY_FLAG(enable_layer_command_batching, "") +FLAG_MANAGER_READ_ONLY_FLAG(screenshot_fence_preservation, "debug.sf.screenshot_fence_preservation") +FLAG_MANAGER_READ_ONLY_FLAG(vulkan_renderengine, "debug.renderengine.vulkan") +FLAG_MANAGER_READ_ONLY_FLAG(renderable_buffer_usage, "") +FLAG_MANAGER_READ_ONLY_FLAG(restore_blur_step, "debug.renderengine.restore_blur_step") +FLAG_MANAGER_READ_ONLY_FLAG(dont_skip_on_early_ro, "") /// Trunk stable server flags /// -FLAG_MANAGER_SERVER_FLAG(late_boot_misc2, "") FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "") -/// Exceptions /// -bool FlagManager::dont_skip_on_early() const { - // Even though this is a server writable flag, we do call it before boot completed, but that's - // fine since the decision is done per frame. We can't do caching though. - return flags::dont_skip_on_early(); -} - } // namespace android diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h index 2e1d6aee4d..18f623faff 100644 --- a/services/surfaceflinger/common/include/common/FlagManager.h +++ b/services/surfaceflinger/common/include/common/FlagManager.h @@ -17,6 +17,7 @@ #pragma once #include <cstdint> +#include <functional> #include <mutex> #include <optional> #include <string> @@ -47,8 +48,6 @@ public: bool use_skia_tracing() const; /// Trunk stable server flags /// - bool late_boot_misc2() const; - bool dont_skip_on_early() const; bool refresh_rate_overlay_on_external_display() const; /// Trunk stable readonly flags /// @@ -61,12 +60,17 @@ public: bool multithreaded_present() const; bool add_sf_skipped_frames_to_trace() const; bool use_known_refresh_rate_for_fps_consistency() const; - bool cache_if_source_crop_layer_only_moved() const; + bool cache_when_source_crop_layer_only_moved() const; bool enable_fro_dependent_features() const; bool display_protected() const; bool fp16_client_target() const; bool game_default_frame_rate() const; bool enable_layer_command_batching() const; + bool screenshot_fence_preservation() const; + bool vulkan_renderengine() const; + bool renderable_buffer_usage() const; + bool restore_blur_step() const; + bool dont_skip_on_early_ro() const; protected: // overridden for unit tests diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp index 68237c8dd6..682079f979 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp @@ -143,7 +143,6 @@ private: void invokeComposerHal2_2(Hwc2::AidlComposer*, Display, Hwc2::V2_4::hal::Layer); void invokeComposerHal2_3(Hwc2::AidlComposer*, Display, Hwc2::V2_4::hal::Layer); void invokeComposerHal2_4(Hwc2::AidlComposer*, Display, Hwc2::V2_4::hal::Layer); - void getDisplayVsyncPeriod(); void setActiveModeWithConstraints(); void getDisplayIdentificationData(); void dumpHwc(); @@ -202,11 +201,6 @@ Display DisplayHardwareFuzzer::createVirtualDisplay(Hwc2::AidlComposer* composer return display; } -void DisplayHardwareFuzzer::getDisplayVsyncPeriod() { - nsecs_t outVsyncPeriod; - mHwc.getDisplayVsyncPeriod(mPhysicalDisplayId, &outVsyncPeriod); -} - void DisplayHardwareFuzzer::setActiveModeWithConstraints() { hal::VsyncPeriodChangeTimeline outTimeline; mHwc.setActiveModeWithConstraints(mPhysicalDisplayId, kActiveConfig, {} /*constraints*/, @@ -617,8 +611,7 @@ void DisplayHardwareFuzzer::invokeComposer() { mHwc.getDisplayConnectionType(mPhysicalDisplayId); mHwc.isVsyncPeriodSwitchSupported(mPhysicalDisplayId); - - getDisplayVsyncPeriod(); + mHwc.getDisplayVsyncPeriod(mPhysicalDisplayId); setActiveModeWithConstraints(); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 4fc39cc912..0d15f6264f 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -224,20 +224,13 @@ namespace scheduler { class TestableScheduler : public Scheduler, private ICompositor { public: - TestableScheduler(const std::shared_ptr<scheduler::RefreshRateSelector>& selectorPtr, - sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback, - IVsyncTrackerCallback& vsyncTrackerCallback) - : TestableScheduler(std::make_unique<android::mock::VsyncController>(), - std::make_shared<android::mock::VSyncTracker>(), selectorPtr, - std::move(modulatorPtr), callback, vsyncTrackerCallback) {} - TestableScheduler(std::unique_ptr<VsyncController> controller, VsyncSchedule::TrackerPtr tracker, std::shared_ptr<RefreshRateSelector> selectorPtr, - sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback, - IVsyncTrackerCallback& vsyncTrackerCallback) - : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr), - vsyncTrackerCallback) { + surfaceflinger::Factory& factory, TimeStats& timeStats, + ISchedulerCallback& callback) + : Scheduler(*this, callback, Feature::kContentDetection, factory, + selectorPtr->getActiveMode().fps, timeStats) { const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId(); registerDisplayInternal(displayId, std::move(selectorPtr), std::shared_ptr<VsyncSchedule>( @@ -296,6 +289,8 @@ private: } void sample() override {} + void sendNotifyExpectedPresentHint(PhysicalDisplayId) override {} + // MessageQueue overrides: void scheduleFrame() override {} void postMessage(sp<MessageHandler>&& handler) override { handler->handleMessage(Message()); } @@ -403,8 +398,7 @@ public: } // namespace surfaceflinger::test // TODO(b/189053744) : Create a common test/mock library for surfaceflinger -class TestableSurfaceFlinger final : private scheduler::ISchedulerCallback, - private scheduler::IVsyncTrackerCallback { +class TestableSurfaceFlinger final : private scheduler::ISchedulerCallback { public: using HotplugEvent = SurfaceFlinger::HotplugEvent; @@ -613,7 +607,14 @@ public: mFlinger->commitTransactions(); mFlinger->flushTransactionQueues(getFuzzedVsyncId(mFdp)); - scheduler::FrameTargeter frameTargeter(displayId, mFdp.ConsumeBool()); + scheduler::FeatureFlags flags; + if (mFdp.ConsumeBool()) { + flags |= scheduler::Feature::kBackpressureGpuComposition; + } + if (mFdp.ConsumeBool()) { + flags |= scheduler::Feature::kExpectedPresentTime; + } + scheduler::FrameTargeter frameTargeter(displayId, flags); mFlinger->onCompositionPresented(displayId, ftl::init::map(displayId, &frameTargeter), mFdp.ConsumeIntegral<nsecs_t>()); } @@ -660,7 +661,6 @@ public: std::unique_ptr<EventThread> appEventThread, std::unique_ptr<EventThread> sfEventThread, scheduler::ISchedulerCallback* callback = nullptr, - scheduler::IVsyncTrackerCallback* vsyncTrackerCallback = nullptr, bool hasMultipleModes = false) { constexpr DisplayModeId kModeId60{0}; DisplayModes modes = makeModes(mock::createDisplayMode(kModeId60, 60_Hz)); @@ -671,20 +671,11 @@ public: } mRefreshRateSelector = std::make_shared<scheduler::RefreshRateSelector>(modes, kModeId60); - const auto fps = mRefreshRateSelector->getActiveMode().modePtr->getVsyncRate(); - mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps); - - mFlinger->mRefreshRateStats = - std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mTimeStats, fps, - hal::PowerMode::OFF); - - auto modulatorPtr = sp<scheduler::VsyncModulator>::make( - mFlinger->mVsyncConfiguration->getCurrentConfigs()); mScheduler = new scheduler::TestableScheduler(std::move(vsyncController), std::move(vsyncTracker), mRefreshRateSelector, - std::move(modulatorPtr), *(callback ?: this), - *(vsyncTrackerCallback ?: this)); + mFactory, *mFlinger->mTimeStats, + *(callback ?: this)); mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread)); mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread)); @@ -804,9 +795,7 @@ private: void kernelTimerChanged(bool) override {} void triggerOnFrameRateOverridesChanged() override {} void onChoreographerAttached() override {} - - // IVsyncTrackerCallback overrides - void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {} + void onExpectedPresentTimePosted(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {} surfaceflinger::test::Factory mFactory; sp<SurfaceFlinger> mFlinger = diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index b690d8d98e..ff2ee7e0f4 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -24,6 +24,7 @@ #include "Scheduler/OneShotTimer.h" #include "Scheduler/RefreshRateSelector.h" +#include "Scheduler/RefreshRateStats.h" #include "Scheduler/VSyncDispatchTimerQueue.h" #include "Scheduler/VSyncPredictor.h" #include "Scheduler/VSyncReactor.h" @@ -102,6 +103,7 @@ struct EventThreadCallback : public IEventThreadCallback { bool throttleVsync(TimePoint, uid_t) override { return false; } Period getVsyncPeriod(uid_t) override { return kSyncPeriod; } void resync() override {} + void onExpectedPresentTimePosted(TimePoint) override {} }; void SchedulerFuzzer::fuzzEventThread() { @@ -134,13 +136,13 @@ void SchedulerFuzzer::fuzzCallbackToken(scheduler::VSyncDispatchTimerQueue* disp dispatch->schedule(tmp, {.workDuration = mFdp.ConsumeIntegral<nsecs_t>(), .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(), - .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()}); + .lastVsync = mFdp.ConsumeIntegral<nsecs_t>()}); }, "o.o"); dispatch->schedule(tmp, {.workDuration = mFdp.ConsumeIntegral<nsecs_t>(), .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(), - .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()}); + .lastVsync = mFdp.ConsumeIntegral<nsecs_t>()}); dispatch->unregisterCallback(tmp); dispatch->cancel(tmp); } @@ -162,38 +164,33 @@ void SchedulerFuzzer::fuzzVSyncDispatchTimerQueue() { entry.update(*stubTracker, 0); entry.schedule({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(), .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(), - .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()}, + .lastVsync = mFdp.ConsumeIntegral<nsecs_t>()}, *stubTracker, 0); entry.disarm(); entry.ensureNotRunning(); entry.schedule({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(), .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(), - .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()}, + .lastVsync = mFdp.ConsumeIntegral<nsecs_t>()}, *stubTracker, 0); auto const wakeup = entry.wakeupTime(); auto const ready = entry.readyTime(); entry.callback(entry.executing(), *wakeup, *ready); - entry.addPendingWorkloadUpdate({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(), + entry.addPendingWorkloadUpdate(*stubTracker, 0, + {.workDuration = mFdp.ConsumeIntegral<nsecs_t>(), .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(), - .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()}); + .lastVsync = mFdp.ConsumeIntegral<nsecs_t>()}); dump<scheduler::VSyncDispatchTimerQueueEntry>(&entry, &mFdp); } -struct VsyncTrackerCallback : public scheduler::IVsyncTrackerCallback { - void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {} -}; - 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); nsecs_t idealPeriod = mFdp.ConsumeIntegralInRange<nsecs_t>(1, UINT32_MAX); - VsyncTrackerCallback callback; const auto mode = ftl::as_non_null( mock::createDisplayMode(DisplayModeId(0), Fps::fromPeriodNsecs(idealPeriod))); scheduler::VSyncPredictor tracker{mode, historySize, minimumSamplesForPrediction, - mFdp.ConsumeIntegral<uint32_t>() /*outlierTolerancePercent*/, - callback}; + mFdp.ConsumeIntegral<uint32_t>() /*outlierTolerancePercent*/}; uint16_t period = mFdp.ConsumeIntegral<uint16_t>(); tracker.setDisplayModePtr(ftl::as_non_null( mock::createDisplayMode(DisplayModeId(0), Fps::fromPeriodNsecs(period)))); @@ -404,8 +401,8 @@ void SchedulerFuzzer::fuzzRefreshRateSelector() { Fps::fromValue(mFdp.ConsumeFloatingPoint<float>())); android::mock::TimeStats timeStats; - RefreshRateStats refreshRateStats(timeStats, Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()), - PowerMode::OFF); + RefreshRateStats refreshRateStats(timeStats, + Fps::fromValue(mFdp.ConsumeFloatingPoint<float>())); const auto fpsOpt = displayModes.get(modeId).transform( [](const DisplayModePtr& mode) { return mode->getVsyncRate(); }); @@ -425,14 +422,24 @@ void SchedulerFuzzer::fuzzPresentLatencyTracker() { } void SchedulerFuzzer::fuzzFrameTargeter() { - scheduler::FrameTargeter frameTargeter(kDisplayId, mFdp.ConsumeBool()); + scheduler::FeatureFlags flags; + if (mFdp.ConsumeBool()) { + flags |= scheduler::Feature::kBackpressureGpuComposition; + } + if (mFdp.ConsumeBool()) { + flags |= scheduler::Feature::kExpectedPresentTime; + } + + scheduler::FrameTargeter frameTargeter(kDisplayId, flags); const struct VsyncSource final : scheduler::IVsyncSource { explicit VsyncSource(FuzzedDataProvider& fuzzer) : fuzzer(fuzzer) {} FuzzedDataProvider& fuzzer; Period period() const { return getFuzzedDuration(fuzzer); } - TimePoint vsyncDeadlineAfter(TimePoint) const { return getFuzzedTimePoint(fuzzer); } + TimePoint vsyncDeadlineAfter(TimePoint, ftl::Optional<TimePoint> = {}) const { + return getFuzzedTimePoint(fuzzer); + } Period minFramePeriod() const { return period(); } } vsyncSource{mFdp}; @@ -441,7 +448,8 @@ void SchedulerFuzzer::fuzzFrameTargeter() { frameTargeter.beginFrame({.frameBeginTime = getFuzzedTimePoint(mFdp), .vsyncId = getFuzzedVsyncId(mFdp), .expectedVsyncTime = getFuzzedTimePoint(mFdp), - .sfWorkDuration = getFuzzedDuration(mFdp)}, + .sfWorkDuration = getFuzzedDuration(mFdp), + .hwcMinWorkDuration = getFuzzedDuration(mFdp)}, vsyncSource); frameTargeter.setPresentFence(makeFakeFence()); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h index fa307e9bb4..114f3b0e7a 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h @@ -86,7 +86,10 @@ public: bool addVsyncTimestamp(nsecs_t /* timestamp */) override { return true; } - nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t /* timePoint */) const override { return 1; } + nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t /* timePoint */, + std::optional<nsecs_t>) const override { + return 1; + } nsecs_t currentPeriod() const override { return 1; } Period minFramePeriod() const override { return Period::fromNs(currentPeriod()); } diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig index 1a28b81483..f5ec1eeaf2 100644 --- a/services/surfaceflinger/surfaceflinger_flags.aconfig +++ b/services/surfaceflinger/surfaceflinger_flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.graphics.surfaceflinger.flags" +container: "system" flag { name: "misc1" @@ -16,13 +17,6 @@ flag { is_fixed_read_only: true } -flag{ - name: "late_boot_misc2" - namespace: "core_graphics" - description: "This flag controls minor miscellaneous SurfaceFlinger changes. Cannot be read before boot finished!" - bug: "297389311" -} - flag { name: "vrr_config" namespace: "core_graphics" @@ -40,13 +34,6 @@ flag { } flag { - name: "dont_skip_on_early" - namespace: "core_graphics" - description: "This flag is guarding the behaviour where SurfaceFlinger is trying to opportunistically present a frame when the configuration change from late to early" - bug: "273702768" -} - -flag { name: "multithreaded_present" namespace: "core_graphics" description: "Controls whether to offload present calls to another thread" @@ -98,10 +85,21 @@ flag { namespace: "core_graphics" description: "Whether to use the closest known refresh rate to determine the fps consistency." bug: "299201319" + is_fixed_read_only: true } +# This flag is broken. +# See alternative one: cache_when_source_crop_layer_only_moved +# flag { +# name: "cache_if_source_crop_layer_only_moved" +# namespace: "core_graphics" +# description: "do not flatten layers if source crop is only moved" +# bug: "305718400" +# is_fixed_read_only: true +# } + flag { - name: "cache_if_source_crop_layer_only_moved" + name: "cache_when_source_crop_layer_only_moved" namespace: "core_graphics" description: "do not flatten layers if source crop is only moved" bug: "305718400" @@ -139,3 +137,60 @@ flag { bug: "286084594" is_fixed_read_only: true } + +flag { + name: "vulkan_renderengine" + namespace: "core_graphics" + description: "Use Vulkan backend in RenderEngine prior to switching to Graphite." + bug: "293371537" + is_fixed_read_only: true +} + +flag { + name: "graphite_renderengine" + namespace: "core_graphics" + description: "Use Skia's Graphite Vulkan backend in RenderEngine." + bug: "293371537" + is_fixed_read_only: true +} + +flag { + name: "screenshot_fence_preservation" + namespace: "core_graphics" + description: "Bug fix around screenshot fences" + bug: "302703346" + is_fixed_read_only: true +} + +flag { + name: "renderable_buffer_usage" + namespace: "core_graphics" + description: "Decide whether an ExternalTexture isRenderable based on its buffer's usage." + bug: "305445199" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "restore_blur_step" + namespace: "core_graphics" + description: "Restore drawing the blur input prior to drawing blurred content." + bug: "255921628" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "dont_skip_on_early_ro" + namespace: "core_graphics" + description: "This flag is guarding the behaviour where SurfaceFlinger is trying to opportunistically present a frame when the configuration change from late to early" + bug: "273702768" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/surfaceflinger/tests/LayerTransaction_test.cpp b/services/surfaceflinger/tests/LayerTransaction_test.cpp index 03de8d0b6d..ea141f3257 100644 --- a/services/surfaceflinger/tests/LayerTransaction_test.cpp +++ b/services/surfaceflinger/tests/LayerTransaction_test.cpp @@ -213,6 +213,15 @@ TEST_F(LayerTransactionTest, CommitCallbackCalledOnce) { ASSERT_EQ(callCount, 1); } +TEST_F(LayerTransactionTest, AddRemoveLayers) { + for (int i = 0; i < 100; i++) { + sp<SurfaceControl> layer; + ASSERT_NO_FATAL_FAILURE( + layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); + layer.clear(); + } +} + } // namespace android // TODO(b/129481165): remove the #pragma below and fix conversion issues diff --git a/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp index f1bb231f26..b17b529793 100644 --- a/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp +++ b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp @@ -17,7 +17,7 @@ #undef LOG_TAG #define LOG_TAG "LibSurfaceFlingerUnittests" -#include "DisplayTransactionTestHelpers.h" +#include "DualDisplayTransactionTest.h" #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -25,31 +25,10 @@ namespace android { namespace { -struct ActiveDisplayRotationFlagsTest : DisplayTransactionTest { - static constexpr bool kWithMockScheduler = false; - ActiveDisplayRotationFlagsTest() : DisplayTransactionTest(kWithMockScheduler) {} - +struct ActiveDisplayRotationFlagsTest + : DualDisplayTransactionTest<hal::PowerMode::ON, hal::PowerMode::OFF> { void SetUp() override { - injectMockScheduler(kInnerDisplayId); - - // Inject inner and outer displays with uninitialized power modes. - constexpr bool kInitPowerMode = false; - { - InnerDisplayVariant::injectHwcDisplay<kInitPowerMode>(this); - auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this); - injector.setPowerMode(std::nullopt); - injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector()); - mInnerDisplay = injector.inject(); - } - { - OuterDisplayVariant::injectHwcDisplay<kInitPowerMode>(this); - auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this); - injector.setPowerMode(std::nullopt); - mOuterDisplay = injector.inject(); - } - - mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON); - mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::ON); + DualDisplayTransactionTest::SetUp(); // The flags are a static variable, so by modifying them in the test, we // are modifying the real ones used by SurfaceFlinger. Save the original @@ -64,10 +43,6 @@ struct ActiveDisplayRotationFlagsTest : DisplayTransactionTest { void TearDown() override { mFlinger.mutableActiveDisplayRotationFlags() = mOldRotationFlags; } - static inline PhysicalDisplayId kInnerDisplayId = InnerDisplayVariant::DISPLAY_ID::get(); - static inline PhysicalDisplayId kOuterDisplayId = OuterDisplayVariant::DISPLAY_ID::get(); - - sp<DisplayDevice> mInnerDisplay, mOuterDisplay; ui::Transform::RotationFlags mOldRotationFlags; }; diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index c75f90222a..da4e47fdec 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -95,6 +95,7 @@ cc_test { "MessageQueueTest.cpp", "PowerAdvisorTest.cpp", "SmallAreaDetectionAllowMappingsTest.cpp", + "SurfaceFlinger_ColorMatrixTest.cpp", "SurfaceFlinger_CreateDisplayTest.cpp", "SurfaceFlinger_DestroyDisplayTest.cpp", "SurfaceFlinger_DisplayModeSwitching.cpp", @@ -118,6 +119,7 @@ cc_test { "RefreshRateSelectorTest.cpp", "RefreshRateStatsTest.cpp", "RegionSamplingTest.cpp", + "TestableScheduler.cpp", "TimeStatsTest.cpp", "FrameTracerTest.cpp", "TransactionApplicationTest.cpp", @@ -127,7 +129,6 @@ cc_test { "TransactionTraceWriterTest.cpp", "TransactionTracingTest.cpp", "TunnelModeEnabledReporterTest.cpp", - "StrongTypingTest.cpp", "VSyncCallbackRegistrationTest.cpp", "VSyncDispatchTimerQueueTest.cpp", "VSyncDispatchRealtimeTest.cpp", diff --git a/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h b/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h new file mode 100644 index 0000000000..34e4ba5e78 --- /dev/null +++ b/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h @@ -0,0 +1,86 @@ +/* + * Copyright 2024 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 <gtest/gtest.h> + +#include <compositionengine/Display.h> +#include <compositionengine/mock/DisplaySurface.h> +#include <renderengine/mock/RenderEngine.h> + +#include "TestableSurfaceFlinger.h" +#include "mock/DisplayHardware/MockComposer.h" +#include "mock/DisplayHardware/MockPowerAdvisor.h" +#include "mock/MockTimeStats.h" +#include "mock/system/window/MockNativeWindow.h" + +namespace android { + +// Minimal setup to use TestableSurfaceFlinger::commitAndComposite. +struct CommitAndCompositeTest : testing::Test { + void SetUp() override { + mFlinger.setupMockScheduler({.displayId = DEFAULT_DISPLAY_ID}); + mComposer = new Hwc2::mock::Composer(); + mPowerAdvisor = new Hwc2::mock::PowerAdvisor(); + mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine)); + mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats)); + mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer)); + mFlinger.setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor>(mPowerAdvisor)); + + constexpr bool kIsPrimary = true; + FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, hal::DisplayType::PHYSICAL, kIsPrimary) + .setPowerMode(hal::PowerMode::ON) + .inject(&mFlinger, mComposer); + auto compostionEngineDisplayArgs = + compositionengine::DisplayCreationArgsBuilder() + .setId(DEFAULT_DISPLAY_ID) + .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT}) + .setPowerAdvisor(mPowerAdvisor) + .setName("Internal display") + .build(); + auto compositionDisplay = + compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(), + std::move(compostionEngineDisplayArgs)); + mDisplay = FakeDisplayDeviceInjector(mFlinger, compositionDisplay, + ui::DisplayConnectionType::Internal, HWC_DISPLAY, + kIsPrimary) + .setDisplaySurface(mDisplaySurface) + .setNativeWindow(mNativeWindow) + .setPowerMode(hal::PowerMode::ON) + .setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector()) + .skipRegisterDisplay() + .inject(); + } + + using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; + using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector; + + static constexpr hal::HWDisplayId HWC_DISPLAY = FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID; + static constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u); + static constexpr int DEFAULT_DISPLAY_WIDTH = 1920; + static constexpr int DEFAULT_DISPLAY_HEIGHT = 1024; + + TestableSurfaceFlinger mFlinger; + renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine(); + sp<DisplayDevice> mDisplay; + sp<compositionengine::mock::DisplaySurface> mDisplaySurface = + sp<compositionengine::mock::DisplaySurface>::make(); + sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make(); + mock::TimeStats* mTimeStats = new mock::TimeStats(); + Hwc2::mock::PowerAdvisor* mPowerAdvisor = nullptr; + Hwc2::mock::Composer* mComposer = nullptr; +}; + +} // namespace android diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index beb2147c98..7d8a30a727 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -25,6 +25,7 @@ #include <compositionengine/Display.h> #include <compositionengine/mock/DisplaySurface.h> +#include <ftl/future.h> #include <gmock/gmock.h> #include <gtest/gtest.h> #include <gui/IProducerListener.h> @@ -227,14 +228,6 @@ void CompositionTest::captureScreenComposition() { LayerCase::cleanup(this); } -template <class T> -std::future<T> futureOf(T obj) { - std::promise<T> resultPromise; - std::future<T> resultFuture = resultPromise.get_future(); - resultPromise.set_value(std::move(obj)); - return resultFuture; -} - /* ------------------------------------------------------------------------ * Variants for each display configuration which can be tested */ @@ -327,13 +320,13 @@ struct BaseDisplayVariant { .WillRepeatedly([&](const renderengine::DisplaySettings& displaySettings, const std::vector<renderengine::LayerSettings>&, const std::shared_ptr<renderengine::ExternalTexture>&, - base::unique_fd&&) -> std::future<FenceResult> { + base::unique_fd&&) -> ftl::Future<FenceResult> { EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance); EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT), displaySettings.physicalDisplay); EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT), displaySettings.clip); - return futureOf<FenceResult>(Fence::NO_FENCE); + return ftl::yield<FenceResult>(Fence::NO_FENCE); }); } @@ -378,14 +371,14 @@ struct BaseDisplayVariant { .WillRepeatedly([&](const renderengine::DisplaySettings& displaySettings, const std::vector<renderengine::LayerSettings>&, const std::shared_ptr<renderengine::ExternalTexture>&, - base::unique_fd&&) -> std::future<FenceResult> { + base::unique_fd&&) -> ftl::Future<FenceResult> { EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance); EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT), displaySettings.physicalDisplay); EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT), displaySettings.clip); EXPECT_EQ(ui::Dataspace::UNKNOWN, displaySettings.outputDataspace); - return futureOf<FenceResult>(Fence::NO_FENCE); + return ftl::yield<FenceResult>(Fence::NO_FENCE); }); } @@ -578,7 +571,7 @@ struct BaseLayerProperties { .WillOnce([&](const renderengine::DisplaySettings& displaySettings, const std::vector<renderengine::LayerSettings>& layerSettings, const std::shared_ptr<renderengine::ExternalTexture>&, - base::unique_fd&&) -> std::future<FenceResult> { + base::unique_fd&&) -> ftl::Future<FenceResult> { EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance); EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT), displaySettings.physicalDisplay); @@ -586,7 +579,8 @@ struct BaseLayerProperties { displaySettings.clip); // screen capture adds an additional color layer as an alpha // prefill, so gtet the back layer. - std::future<FenceResult> resultFuture = futureOf<FenceResult>(Fence::NO_FENCE); + ftl::Future<FenceResult> resultFuture = + ftl::yield<FenceResult>(Fence::NO_FENCE); if (layerSettings.empty()) { ADD_FAILURE() << "layerSettings was not expected to be empty in " "setupREBufferCompositionCommonCallExpectations " @@ -627,7 +621,7 @@ struct BaseLayerProperties { .WillOnce([&](const renderengine::DisplaySettings& displaySettings, const std::vector<renderengine::LayerSettings>& layerSettings, const std::shared_ptr<renderengine::ExternalTexture>&, - base::unique_fd&&) -> std::future<FenceResult> { + base::unique_fd&&) -> ftl::Future<FenceResult> { EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance); EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT), displaySettings.physicalDisplay); @@ -635,7 +629,8 @@ struct BaseLayerProperties { displaySettings.clip); // screen capture adds an additional color layer as an alpha // prefill, so get the back layer. - std::future<FenceResult> resultFuture = futureOf<FenceResult>(Fence::NO_FENCE); + ftl::Future<FenceResult> resultFuture = + ftl::yield<FenceResult>(Fence::NO_FENCE); if (layerSettings.empty()) { ADD_FAILURE() << "layerSettings was not expected to be empty in " @@ -709,7 +704,7 @@ struct CommonSecureLayerProperties : public BaseLayerProperties<LayerProperties> .WillOnce([&](const renderengine::DisplaySettings& displaySettings, const std::vector<renderengine::LayerSettings>& layerSettings, const std::shared_ptr<renderengine::ExternalTexture>&, - base::unique_fd&&) -> std::future<FenceResult> { + base::unique_fd&&) -> ftl::Future<FenceResult> { EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance); EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT), displaySettings.physicalDisplay); @@ -717,7 +712,8 @@ struct CommonSecureLayerProperties : public BaseLayerProperties<LayerProperties> displaySettings.clip); // screen capture adds an additional color layer as an alpha // prefill, so get the back layer. - std::future<FenceResult> resultFuture = futureOf<FenceResult>(Fence::NO_FENCE); + ftl::Future<FenceResult> resultFuture = + ftl::yield<FenceResult>(Fence::NO_FENCE); if (layerSettings.empty()) { ADD_FAILURE() << "layerSettings was not expected to be empty in " "setupInsecureREBufferCompositionCommonCallExpectations " diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp index 13796650c8..fa31643df1 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp @@ -80,8 +80,7 @@ void DisplayTransactionTest::injectMockScheduler(PhysicalDisplayId displayId) { std::unique_ptr<EventThread>(mEventThread), std::unique_ptr<EventThread>(mSFEventThread), TestableSurfaceFlinger::DefaultDisplayMode{displayId}, - TestableSurfaceFlinger::SchedulerCallbackImpl::kMock, - TestableSurfaceFlinger::VsyncTrackerCallbackImpl::kMock); + TestableSurfaceFlinger::SchedulerCallbackImpl::kMock); } void DisplayTransactionTest::injectMockComposer(int virtualDisplayCount) { diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h index ee12276994..f26336a655 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h @@ -347,7 +347,6 @@ struct HwcDisplayVariant { // The HWC active configuration id static constexpr hal::HWConfigId HWC_ACTIVE_CONFIG_ID = 2001; - static constexpr PowerMode INIT_POWER_MODE = hal::PowerMode::ON; static void injectPendingHotplugEvent(DisplayTransactionTest* test, Connection connection) { test->mFlinger.mutablePendingHotplugEvents().emplace_back( @@ -355,7 +354,7 @@ struct HwcDisplayVariant { } // Called by tests to inject a HWC display setup - template <bool kInitPowerMode = true> + template <hal::PowerMode kPowerMode = hal::PowerMode::ON> static void injectHwcDisplayWithNoDefaultCapabilities(DisplayTransactionTest* test) { const auto displayId = DisplayVariant::DISPLAY_ID::get(); ASSERT_FALSE(GpuVirtualDisplayId::tryCast(displayId)); @@ -364,22 +363,37 @@ struct HwcDisplayVariant { .setHwcDisplayId(HWC_DISPLAY_ID) .setResolution(DisplayVariant::RESOLUTION) .setActiveConfig(HWC_ACTIVE_CONFIG_ID) - .setPowerMode(kInitPowerMode ? std::make_optional(INIT_POWER_MODE) : std::nullopt) + .setPowerMode(kPowerMode) .inject(&test->mFlinger, test->mComposer); } // Called by tests to inject a HWC display setup - template <bool kInitPowerMode = true> + // + // TODO(b/241285876): The `kExpectSetPowerModeOnce` argument is set to `false` by tests that + // power on/off displays several times. Replace those catch-all expectations with `InSequence` + // and `RetiresOnSaturation`. + // + template <hal::PowerMode kPowerMode = hal::PowerMode::ON, bool kExpectSetPowerModeOnce = true> static void injectHwcDisplay(DisplayTransactionTest* test) { - if constexpr (kInitPowerMode) { + if constexpr (kExpectSetPowerModeOnce) { + if constexpr (kPowerMode == hal::PowerMode::ON) { + EXPECT_CALL(*test->mComposer, getDisplayCapabilities(HWC_DISPLAY_ID, _)) + .WillOnce(DoAll(SetArgPointee<1>(std::vector<DisplayCapability>({})), + Return(Error::NONE))); + } + + EXPECT_CALL(*test->mComposer, setPowerMode(HWC_DISPLAY_ID, kPowerMode)) + .WillOnce(Return(Error::NONE)); + } else { EXPECT_CALL(*test->mComposer, getDisplayCapabilities(HWC_DISPLAY_ID, _)) - .WillOnce(DoAll(SetArgPointee<1>(std::vector<DisplayCapability>({})), - Return(Error::NONE))); + .WillRepeatedly(DoAll(SetArgPointee<1>(std::vector<DisplayCapability>({})), + Return(Error::NONE))); - EXPECT_CALL(*test->mComposer, setPowerMode(HWC_DISPLAY_ID, INIT_POWER_MODE)) - .WillOnce(Return(Error::NONE)); + EXPECT_CALL(*test->mComposer, setPowerMode(HWC_DISPLAY_ID, _)) + .WillRepeatedly(Return(Error::NONE)); } - injectHwcDisplayWithNoDefaultCapabilities<kInitPowerMode>(test); + + injectHwcDisplayWithNoDefaultCapabilities<kPowerMode>(test); } static std::shared_ptr<compositionengine::Display> injectCompositionDisplay( @@ -447,9 +461,11 @@ struct HwcDisplayVariant { ? IComposerClient::DisplayConnectionType::INTERNAL : IComposerClient::DisplayConnectionType::EXTERNAL; + using ::testing::AtLeast; EXPECT_CALL(*test->mComposer, getDisplayConnectionType(HWC_DISPLAY_ID, _)) - .WillOnce(DoAll(SetArgPointee<1>(CONNECTION_TYPE), - Return(hal::V2_4::Error::NONE))); + .Times(AtLeast(1)) + .WillRepeatedly(DoAll(SetArgPointee<1>(CONNECTION_TYPE), + Return(hal::V2_4::Error::NONE))); } EXPECT_CALL(*test->mComposer, setClientTargetSlotCount(_)) @@ -481,14 +497,17 @@ constexpr uint32_t GRALLOC_USAGE_PHYSICAL_DISPLAY = constexpr int PHYSICAL_DISPLAY_FLAGS = 0x1; -template <typename PhysicalDisplay, int width, int height> +template <typename PhysicalDisplay, int width, int height, + Secure secure = (PhysicalDisplay::CONNECTION_TYPE == ui::DisplayConnectionType::Internal) + ? Secure::TRUE + : Secure::FALSE> struct PhysicalDisplayVariant - : DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height, Async::FALSE, - Secure::TRUE, PhysicalDisplay::PRIMARY, GRALLOC_USAGE_PHYSICAL_DISPLAY, + : DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height, Async::FALSE, secure, + 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, + Async::FALSE, secure, PhysicalDisplay::PRIMARY, GRALLOC_USAGE_PHYSICAL_DISPLAY, PHYSICAL_DISPLAY_FLAGS>, PhysicalDisplay> {}; @@ -515,6 +534,7 @@ struct SecondaryDisplay { }; struct TertiaryDisplay { + static constexpr auto CONNECTION_TYPE = ui::DisplayConnectionType::External; static constexpr Primary PRIMARY = Primary::FALSE; static constexpr uint8_t PORT = 253; static constexpr HWDisplayId HWC_DISPLAY_ID = 1003; diff --git a/services/surfaceflinger/tests/unittests/DualDisplayTransactionTest.h b/services/surfaceflinger/tests/unittests/DualDisplayTransactionTest.h new file mode 100644 index 0000000000..90e716ff1f --- /dev/null +++ b/services/surfaceflinger/tests/unittests/DualDisplayTransactionTest.h @@ -0,0 +1,57 @@ +/* + * 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 "DisplayTransactionTestHelpers.h" + +namespace android { + +template <hal::PowerMode kInnerDisplayPowerMode, hal::PowerMode kOuterDisplayPowerMode, + bool kExpectSetPowerModeOnce = true> +struct DualDisplayTransactionTest : DisplayTransactionTest { + static constexpr bool kWithMockScheduler = false; + DualDisplayTransactionTest() : DisplayTransactionTest(kWithMockScheduler) {} + + void SetUp() override { + injectMockScheduler(kInnerDisplayId); + + { + InnerDisplayVariant::injectHwcDisplay<kInnerDisplayPowerMode, kExpectSetPowerModeOnce>( + this); + + auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this); + injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector()); + injector.setPowerMode(kInnerDisplayPowerMode); + mInnerDisplay = injector.inject(); + } + { + OuterDisplayVariant::injectHwcDisplay<kOuterDisplayPowerMode, kExpectSetPowerModeOnce>( + this); + + auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this); + injector.setPowerMode(kOuterDisplayPowerMode); + mOuterDisplay = injector.inject(); + } + } + + static inline PhysicalDisplayId kInnerDisplayId = InnerDisplayVariant::DISPLAY_ID::get(); + static inline PhysicalDisplayId kOuterDisplayId = OuterDisplayVariant::DISPLAY_ID::get(); + + sp<DisplayDevice> mInnerDisplay, mOuterDisplay; +}; + +} // namespace android diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp index 8891c06c75..d5ec654c59 100644 --- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp +++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp @@ -55,6 +55,9 @@ constexpr PhysicalDisplayId DISPLAY_ID_64BIT = constexpr std::chrono::duration VSYNC_PERIOD(16ms); +constexpr int HDCP_V1 = 2; +constexpr int HDCP_V2 = 3; + } // namespace class EventThreadTest : public testing::Test, public IEventThreadCallback { @@ -82,6 +85,7 @@ protected: bool throttleVsync(TimePoint, uid_t) override; Period getVsyncPeriod(uid_t) override; void resync() override; + void onExpectedPresentTimePosted(TimePoint) override; void setupEventThread(); sp<MockEventThreadConnection> createConnection(ConnectionEventRecorder& recorder, @@ -104,6 +108,7 @@ protected: int32_t expectedConfigId, nsecs_t expectedVsyncPeriod); void expectThrottleVsyncReceived(nsecs_t expectedTimestamp, uid_t); + void expectOnExpectedPresentTimePosted(nsecs_t expectedPresentTime); void expectUidFrameRateMappingEventReceivedByConnection(PhysicalDisplayId expectedDisplayId, std::vector<FrameRateOverride>); @@ -128,6 +133,7 @@ protected: mVSyncCallbackUnregisterRecorder; AsyncCallRecorder<void (*)()> mResyncCallRecorder; AsyncCallRecorder<void (*)(nsecs_t, uid_t)> mThrottleVsyncCallRecorder; + AsyncCallRecorder<void (*)(nsecs_t)> mOnExpectedPresentTimePostedRecorder; ConnectionEventRecorder mConnectionEventCallRecorder{0}; ConnectionEventRecorder mThrottledConnectionEventCallRecorder{0}; @@ -187,6 +193,10 @@ void EventThreadTest::resync() { mResyncCallRecorder.recordCall(); } +void EventThreadTest::onExpectedPresentTimePosted(TimePoint expectedPresentTime) { + mOnExpectedPresentTimePostedRecorder.recordCall(expectedPresentTime.ns()); +} + void EventThreadTest::setupEventThread() { mTokenManager = std::make_unique<frametimeline::impl::TokenManager>(); mThread = std::make_unique<impl::EventThread>("EventThreadTest", mVsyncSchedule, @@ -241,6 +251,12 @@ void EventThreadTest::expectThrottleVsyncReceived(nsecs_t expectedTimestamp, uid EXPECT_EQ(uid, std::get<1>(args.value())); } +void EventThreadTest::expectOnExpectedPresentTimePosted(nsecs_t expectedPresentTime) { + auto args = mOnExpectedPresentTimePostedRecorder.waitForCall(); + ASSERT_TRUE(args.has_value()); + EXPECT_EQ(expectedPresentTime, std::get<0>(args.value())); +} + void EventThreadTest::expectVsyncEventReceivedByConnection( const char* name, ConnectionEventRecorder& connectionEventRecorder, nsecs_t expectedTimestamp, unsigned expectedCount) { @@ -407,6 +423,7 @@ TEST_F(EventThreadTest, requestNextVsyncPostsASingleVSyncEventToTheConnection) { onVSyncEvent(123, 456, 789); expectThrottleVsyncReceived(456, mConnectionUid); expectVsyncEventReceivedByConnection(123, 1u); + expectOnExpectedPresentTimePosted(456); // EventThread is requesting one more callback due to VsyncRequest::SingleSuppressCallback expectVSyncCallbackScheduleReceived(true); @@ -471,7 +488,7 @@ TEST_F(EventThreadTest, getLatestVsyncEventData) { mock::VSyncTracker& mockTracker = *static_cast<mock::VSyncTracker*>(&mVsyncSchedule->getTracker()); - EXPECT_CALL(mockTracker, nextAnticipatedVSyncTimeFrom(_)) + EXPECT_CALL(mockTracker, nextAnticipatedVSyncTimeFrom(_, _)) .WillOnce(Return(preferredExpectedPresentationTime)); VsyncEventData vsyncEventData = mThread->getLatestVsyncEventData(mConnection); @@ -559,16 +576,19 @@ TEST_F(EventThreadTest, setVsyncRateOnePostsAllEventsToThatConnection) { onVSyncEvent(123, 456, 789); expectThrottleVsyncReceived(456, mConnectionUid); expectVsyncEventReceivedByConnection(123, 1u); + expectOnExpectedPresentTimePosted(456); // A second event should go to the same places. onVSyncEvent(456, 123, 0); expectThrottleVsyncReceived(123, mConnectionUid); expectVsyncEventReceivedByConnection(456, 2u); + expectOnExpectedPresentTimePosted(123); // A third event should go to the same places. onVSyncEvent(789, 777, 111); expectThrottleVsyncReceived(777, mConnectionUid); expectVsyncEventReceivedByConnection(789, 3u); + expectOnExpectedPresentTimePosted(777); } TEST_F(EventThreadTest, setVsyncRateTwoPostsEveryOtherEventToThatConnection) { @@ -833,6 +853,19 @@ TEST_F(EventThreadTest, requestNextVsyncWithThrottleVsyncDoesntPostVSync) { expectVSyncCallbackScheduleReceived(true); } +TEST_F(EventThreadTest, postHcpLevelsChanged) { + setupEventThread(); + + mThread->onHdcpLevelsChanged(EXTERNAL_DISPLAY_ID, HDCP_V1, HDCP_V2); + auto args = mConnectionEventCallRecorder.waitForCall(); + ASSERT_TRUE(args.has_value()); + const auto& event = std::get<0>(args.value()); + EXPECT_EQ(DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE, event.header.type); + EXPECT_EQ(EXTERNAL_DISPLAY_ID, event.header.displayId); + EXPECT_EQ(HDCP_V1, event.hdcpLevelsChange.connectedLevel); + EXPECT_EQ(HDCP_V2, event.hdcpLevelsChange.maxLevel); +} + } // namespace } // namespace android diff --git a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp index 0c820fb291..0adf0b617a 100644 --- a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp +++ b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp @@ -28,6 +28,7 @@ namespace android { +using namespace com::android::graphics::surfaceflinger; using testing::Return; class TestableFlagManager : public FlagManager { @@ -86,26 +87,26 @@ TEST_F(FlagManagerTest, legacyReturnsValue) { TEST_F(FlagManagerTest, creashesIfQueriedBeforeBoot) { mFlagManager.markBootIncomplete(); - EXPECT_DEATH(FlagManager::getInstance().late_boot_misc2(), ""); + EXPECT_DEATH(FlagManager::getInstance() + .refresh_rate_overlay_on_external_display(), ""); } TEST_F(FlagManagerTest, returnsOverrideTrue) { mFlagManager.markBootCompleted(); - SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::late_boot_misc2, false); + SET_FLAG_FOR_TEST(flags::refresh_rate_overlay_on_external_display, false); // This is stored in a static variable, so this test depends on the fact // that this flag has not been read in this process. EXPECT_CALL(mFlagManager, getBoolProperty).WillOnce(Return(true)); - EXPECT_TRUE(mFlagManager.late_boot_misc2()); + EXPECT_TRUE(mFlagManager.refresh_rate_overlay_on_external_display()); // Further calls will not result in further calls to getBoolProperty. - EXPECT_TRUE(mFlagManager.late_boot_misc2()); + EXPECT_TRUE(mFlagManager.refresh_rate_overlay_on_external_display()); } TEST_F(FlagManagerTest, returnsOverrideReadonly) { - SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::add_sf_skipped_frames_to_trace, - false); + SET_FLAG_FOR_TEST(flags::add_sf_skipped_frames_to_trace, false); // This is stored in a static variable, so this test depends on the fact // that this flag has not been read in this process. @@ -113,12 +114,13 @@ TEST_F(FlagManagerTest, returnsOverrideReadonly) { EXPECT_TRUE(mFlagManager.add_sf_skipped_frames_to_trace()); } -TEST_F(FlagManagerTest, returnsOverrideFalse) { +// disabling this test since we need to use a unique flag for this test, +// but we only one server flag currently. Re-enable once we have a new flag +// and change this test to use a unique flag. +TEST_F(FlagManagerTest, DISABLED_returnsOverrideFalse) { mFlagManager.markBootCompleted(); - SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags:: - refresh_rate_overlay_on_external_display, - true); + SET_FLAG_FOR_TEST(flags::refresh_rate_overlay_on_external_display, true); // This is stored in a static variable, so this test depends on the fact // that this flag has not been read in this process. @@ -129,7 +131,7 @@ TEST_F(FlagManagerTest, returnsOverrideFalse) { TEST_F(FlagManagerTest, ignoresOverrideInUnitTestMode) { mFlagManager.setUnitTestMode(); - SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::multithreaded_present, true); + SET_FLAG_FOR_TEST(flags::multithreaded_present, true); // If this has not been called in this process, it will be called. // Regardless, the result is ignored. @@ -144,13 +146,13 @@ TEST_F(FlagManagerTest, returnsValue) { EXPECT_CALL(mFlagManager, getBoolProperty).WillRepeatedly(Return(std::nullopt)); { - SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::late_boot_misc2, true); - EXPECT_EQ(true, mFlagManager.late_boot_misc2()); + SET_FLAG_FOR_TEST(flags::refresh_rate_overlay_on_external_display, true); + EXPECT_EQ(true, mFlagManager.refresh_rate_overlay_on_external_display()); } { - SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::late_boot_misc2, false); - EXPECT_EQ(false, mFlagManager.late_boot_misc2()); + SET_FLAG_FOR_TEST(flags::refresh_rate_overlay_on_external_display, false); + EXPECT_EQ(false, mFlagManager.refresh_rate_overlay_on_external_display()); } } @@ -160,28 +162,14 @@ TEST_F(FlagManagerTest, readonlyReturnsValue) { EXPECT_CALL(mFlagManager, getBoolProperty).WillRepeatedly(Return(std::nullopt)); { - SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::misc1, true); + SET_FLAG_FOR_TEST(flags::misc1, true); EXPECT_EQ(true, mFlagManager.misc1()); } { - SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::misc1, false); + SET_FLAG_FOR_TEST(flags::misc1, false); EXPECT_EQ(false, mFlagManager.misc1()); } } -TEST_F(FlagManagerTest, dontSkipOnEarlyIsNotCached) { - EXPECT_CALL(mFlagManager, getBoolProperty).WillRepeatedly(Return(std::nullopt)); - - const auto initialValue = com::android::graphics::surfaceflinger::flags::dont_skip_on_early(); - - com::android::graphics::surfaceflinger::flags::dont_skip_on_early(true); - EXPECT_EQ(true, mFlagManager.dont_skip_on_early()); - - com::android::graphics::surfaceflinger::flags::dont_skip_on_early(false); - EXPECT_EQ(false, mFlagManager.dont_skip_on_early()); - - com::android::graphics::surfaceflinger::flags::dont_skip_on_early(initialValue); -} - } // namespace android diff --git a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp index f695b096a7..9e8e306621 100644 --- a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp +++ b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp @@ -24,7 +24,11 @@ #include <gtest/gtest.h> #include <gui/LayerMetadata.h> +#include "Client.h" // temporarily needed for LayerCreationArgs #include "FpsReporter.h" +#include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/LayerHierarchy.h" +#include "FrontEnd/LayerLifecycleManager.h" #include "Layer.h" #include "TestableSurfaceFlinger.h" #include "fake/FakeClock.h" @@ -76,7 +80,15 @@ protected: sp<Layer> createBufferStateLayer(LayerMetadata metadata); - TestableSurfaceFlinger mFlinger; + LayerCreationArgs createArgs(uint32_t id, bool canBeRoot, uint32_t parentId, + LayerMetadata metadata); + + void createRootLayer(uint32_t id, LayerMetadata metadata); + + void createLayer(uint32_t id, uint32_t parentId, LayerMetadata metadata); + + frontend::LayerLifecycleManager mLifecycleManager; + mock::FrameTimeline mFrameTimeline = mock::FrameTimeline(std::make_shared<impl::TimeStats>(), 0); @@ -89,8 +101,8 @@ protected: sp<TestableFpsListener> mFpsListener; fake::FakeClock* mClock = new fake::FakeClock(); - sp<FpsReporter> mFpsReporter = sp<FpsReporter>::make(mFrameTimeline, *(mFlinger.flinger()), - std::unique_ptr<Clock>(mClock)); + sp<FpsReporter> mFpsReporter = + sp<FpsReporter>::make(mFrameTimeline, std::unique_ptr<Clock>(mClock)); }; FpsReporterTest::FpsReporterTest() { @@ -98,9 +110,6 @@ FpsReporterTest::FpsReporterTest() { ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - mFlinger.setupMockScheduler(); - mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>()); - mFpsListener = sp<TestableFpsListener>::make(); } @@ -110,76 +119,94 @@ FpsReporterTest::~FpsReporterTest() { ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); } -sp<Layer> FpsReporterTest::createBufferStateLayer(LayerMetadata metadata = {}) { +LayerCreationArgs FpsReporterTest::createArgs(uint32_t id, bool canBeRoot, uint32_t parentId, + LayerMetadata metadata) { sp<Client> client; - LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", LAYER_FLAGS, metadata); - return sp<Layer>::make(args); + LayerCreationArgs args(std::make_optional(id)); + args.name = "testlayer"; + args.addToRoot = canBeRoot; + args.flags = LAYER_FLAGS; + args.metadata = metadata; + args.parentId = parentId; + return args; +} + +void FpsReporterTest::createRootLayer(uint32_t id, LayerMetadata metadata = LayerMetadata()) { + std::vector<std::unique_ptr<frontend::RequestedLayerState>> layers; + layers.emplace_back(std::make_unique<frontend::RequestedLayerState>( + createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/UNASSIGNED_LAYER_ID, + /*metadata=*/metadata))); + mLifecycleManager.addLayers(std::move(layers)); +} + +void FpsReporterTest::createLayer(uint32_t id, uint32_t parentId, + LayerMetadata metadata = LayerMetadata()) { + std::vector<std::unique_ptr<frontend::RequestedLayerState>> layers; + layers.emplace_back(std::make_unique<frontend::RequestedLayerState>( + createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentId, + /*mirror=*/metadata))); + mLifecycleManager.addLayers(std::move(layers)); } namespace { TEST_F(FpsReporterTest, callsListeners) { - mParent = createBufferStateLayer(); constexpr int32_t kTaskId = 12; LayerMetadata targetMetadata; targetMetadata.setInt32(gui::METADATA_TASK_ID, kTaskId); - mTarget = createBufferStateLayer(targetMetadata); - mChild = createBufferStateLayer(); - mGrandChild = createBufferStateLayer(); - mUnrelated = createBufferStateLayer(); - mParent->addChild(mTarget); - mTarget->addChild(mChild); - mChild->addChild(mGrandChild); - mParent->commitChildList(); - mFlinger.mutableCurrentState().layersSortedByZ.add(mParent); - mFlinger.mutableCurrentState().layersSortedByZ.add(mTarget); - mFlinger.mutableCurrentState().layersSortedByZ.add(mChild); - mFlinger.mutableCurrentState().layersSortedByZ.add(mGrandChild); + + createRootLayer(1, targetMetadata); + createLayer(11, 1); + createLayer(111, 11); + + frontend::LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); float expectedFps = 44.0; - EXPECT_CALL(mFrameTimeline, - computeFps(UnorderedElementsAre(mTarget->getSequence(), mChild->getSequence(), - mGrandChild->getSequence()))) + EXPECT_CALL(mFrameTimeline, computeFps(UnorderedElementsAre(1, 11, 111))) .WillOnce(Return(expectedFps)); mFpsReporter->addListener(mFpsListener, kTaskId); mClock->advanceTime(600ms); - mFpsReporter->dispatchLayerFps(); + mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy()); EXPECT_EQ(expectedFps, mFpsListener->lastReportedFps); mFpsReporter->removeListener(mFpsListener); Mock::VerifyAndClearExpectations(&mFrameTimeline); EXPECT_CALL(mFrameTimeline, computeFps(_)).Times(0); - mFpsReporter->dispatchLayerFps(); + mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy()); } TEST_F(FpsReporterTest, rateLimits) { const constexpr int32_t kTaskId = 12; LayerMetadata targetMetadata; targetMetadata.setInt32(gui::METADATA_TASK_ID, kTaskId); - mTarget = createBufferStateLayer(targetMetadata); - mFlinger.mutableCurrentState().layersSortedByZ.add(mTarget); + createRootLayer(1); + createLayer(11, 1, targetMetadata); + + frontend::LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); float firstFps = 44.0; float secondFps = 53.0; - EXPECT_CALL(mFrameTimeline, computeFps(UnorderedElementsAre(mTarget->getSequence()))) + EXPECT_CALL(mFrameTimeline, computeFps(UnorderedElementsAre(11))) .WillOnce(Return(firstFps)) .WillOnce(Return(secondFps)); mFpsReporter->addListener(mFpsListener, kTaskId); mClock->advanceTime(600ms); - mFpsReporter->dispatchLayerFps(); + mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy()); EXPECT_EQ(firstFps, mFpsListener->lastReportedFps); mClock->advanceTime(200ms); - mFpsReporter->dispatchLayerFps(); + mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy()); EXPECT_EQ(firstFps, mFpsListener->lastReportedFps); mClock->advanceTime(200ms); - mFpsReporter->dispatchLayerFps(); + mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy()); EXPECT_EQ(firstFps, mFpsListener->lastReportedFps); mClock->advanceTime(200ms); - mFpsReporter->dispatchLayerFps(); + mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy()); EXPECT_EQ(secondFps, mFpsListener->lastReportedFps); } diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp index 6edecff6a6..2cff2f2929 100644 --- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp +++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp @@ -21,6 +21,7 @@ #undef LOG_TAG #define LOG_TAG "LibSurfaceFlingerUnittests" +#include <optional> #include <vector> // StrictMock<T> derives from T and is not marked final, so the destructor of T is expected to be @@ -82,6 +83,8 @@ struct HWComposerTest : testing::Test { EXPECT_CALL(*mHal, setVsyncEnabled(hwcDisplayId, Hwc2::IComposerClient::Vsync::DISABLE)); EXPECT_CALL(*mHal, onHotplugConnect(hwcDisplayId)); } + + void setVrrTimeoutHint(bool status) { mHwc.mEnableVrrTimeout = status; } }; TEST_F(HWComposerTest, isHeadless) { @@ -99,9 +102,32 @@ TEST_F(HWComposerTest, isHeadless) { ASSERT_TRUE(mHwc.isHeadless()); } +TEST_F(HWComposerTest, getDisplayConnectionType) { + // Unknown display. + EXPECT_EQ(mHwc.getDisplayConnectionType(PhysicalDisplayId::fromPort(0)), + ui::DisplayConnectionType::Internal); + + constexpr hal::HWDisplayId kHwcDisplayId = 1; + expectHotplugConnect(kHwcDisplayId); + + const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED); + ASSERT_TRUE(info); + + EXPECT_CALL(*mHal, getDisplayConnectionType(kHwcDisplayId, _)) + .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::EXTERNAL), + Return(V2_4::Error::NONE))); + + // The first call caches the connection type. + EXPECT_EQ(mHwc.getDisplayConnectionType(info->id), ui::DisplayConnectionType::External); + + // Subsequent calls return the cached connection type. + EXPECT_EQ(mHwc.getDisplayConnectionType(info->id), ui::DisplayConnectionType::External); + EXPECT_EQ(mHwc.getDisplayConnectionType(info->id), ui::DisplayConnectionType::External); +} + TEST_F(HWComposerTest, getActiveMode) { // Unknown display. - EXPECT_EQ(mHwc.getActiveMode(PhysicalDisplayId::fromPort(0)), std::nullopt); + EXPECT_EQ(mHwc.getActiveMode(PhysicalDisplayId::fromPort(0)), ftl::Unexpected(BAD_INDEX)); constexpr hal::HWDisplayId kHwcDisplayId = 2; expectHotplugConnect(kHwcDisplayId); @@ -114,14 +140,20 @@ TEST_F(HWComposerTest, getActiveMode) { EXPECT_CALL(*mHal, getActiveConfig(kHwcDisplayId, _)) .WillOnce(Return(HalError::BAD_DISPLAY)); - EXPECT_EQ(mHwc.getActiveMode(info->id), std::nullopt); + EXPECT_EQ(mHwc.getActiveMode(info->id), ftl::Unexpected(UNKNOWN_ERROR)); + } + { + EXPECT_CALL(*mHal, getActiveConfig(kHwcDisplayId, _)) + .WillOnce(Return(HalError::BAD_CONFIG)); + + EXPECT_EQ(mHwc.getActiveMode(info->id), ftl::Unexpected(NO_INIT)); } { constexpr hal::HWConfigId kConfigId = 42; EXPECT_CALL(*mHal, getActiveConfig(kHwcDisplayId, _)) .WillOnce(DoAll(SetArgPointee<1>(kConfigId), Return(HalError::NONE))); - EXPECT_EQ(mHwc.getActiveMode(info->id), kConfigId); + EXPECT_EQ(mHwc.getActiveMode(info->id).value_opt(), kConfigId); } } @@ -323,6 +355,7 @@ TEST_F(HWComposerTest, getModesWithDisplayConfigurations_VRR_ON) { EXPECT_TRUE(mHwc.getModes(info->id, kMaxFrameIntervalNs).empty()); } { + setVrrTimeoutHint(true); constexpr int32_t kWidth = 480; constexpr int32_t kHeight = 720; constexpr int32_t kConfigGroup = 1; @@ -330,10 +363,8 @@ TEST_F(HWComposerTest, getModesWithDisplayConfigurations_VRR_ON) { const hal::VrrConfig vrrConfig = hal::VrrConfig{.minFrameIntervalNs = static_cast<Fps>(120_Hz).getPeriodNsecs(), .notifyExpectedPresentConfig = hal::VrrConfig:: - NotifyExpectedPresentConfig{.notifyExpectedPresentHeadsUpNs = - ms2ns(30), - .notifyExpectedPresentTimeoutNs = - ms2ns(30)}}; + NotifyExpectedPresentConfig{.headsUpNs = ms2ns(30), + .timeoutNs = ms2ns(30)}}; hal::DisplayConfiguration displayConfiguration{.configId = kConfigId, .width = kWidth, .height = kHeight, @@ -363,9 +394,9 @@ TEST_F(HWComposerTest, getModesWithDisplayConfigurations_VRR_ON) { displayConfiguration.dpi = {kDpi, kDpi}; EXPECT_CALL(*mHal, getDisplayConfigurations(kHwcDisplayId, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(std::vector<hal::DisplayConfiguration>{ - displayConfiguration}), - Return(HalError::NONE))); + .WillRepeatedly(DoAll(SetArgPointee<2>(std::vector<hal::DisplayConfiguration>{ + displayConfiguration}), + Return(HalError::NONE))); modes = mHwc.getModes(info->id, kMaxFrameIntervalNs); EXPECT_EQ(modes.size(), size_t{1}); @@ -377,6 +408,10 @@ TEST_F(HWComposerTest, getModesWithDisplayConfigurations_VRR_ON) { EXPECT_EQ(modes.front().vrrConfig, vrrConfig); EXPECT_EQ(modes.front().dpiX, kDpi); EXPECT_EQ(modes.front().dpiY, kDpi); + + setVrrTimeoutHint(false); + modes = mHwc.getModes(info->id, kMaxFrameIntervalNs); + EXPECT_EQ(modes.front().vrrConfig->notifyExpectedPresentConfig, std::nullopt); } } @@ -437,7 +472,7 @@ TEST_F(HWComposerSetCallbackTest, loadsLayerMetadataSupport) { {kMetadata1Name, kMetadata1Mandatory}, {kMetadata2Name, kMetadata2Mandatory}, }), - Return(hardware::graphics::composer::V2_4::Error::NONE))); + Return(V2_4::Error::NONE))); EXPECT_CALL(*mHal, getOverlaySupport(_)).WillOnce(Return(HalError::NONE)); EXPECT_CALL(*mHal, getHdrConversionCapabilities(_)).WillOnce(Return(HalError::NONE)); @@ -455,8 +490,7 @@ TEST_F(HWComposerSetCallbackTest, loadsLayerMetadataSupport) { TEST_F(HWComposerSetCallbackTest, handlesUnsupportedCallToGetLayerGenericMetadataKeys) { EXPECT_CALL(*mHal, getCapabilities()).WillOnce(Return(std::vector<aidl::Capability>{})); - EXPECT_CALL(*mHal, getLayerGenericMetadataKeys(_)) - .WillOnce(Return(hardware::graphics::composer::V2_4::Error::UNSUPPORTED)); + EXPECT_CALL(*mHal, getLayerGenericMetadataKeys(_)).WillOnce(Return(V2_4::Error::UNSUPPORTED)); EXPECT_CALL(*mHal, getOverlaySupport(_)).WillOnce(Return(HalError::UNSUPPORTED)); EXPECT_CALL(*mHal, getHdrConversionCapabilities(_)).WillOnce(Return(HalError::UNSUPPORTED)); EXPECT_CALL(*mHal, registerCallback(_)); @@ -516,7 +550,7 @@ TEST_F(HWComposerLayerGenericMetadataTest, forwardsSupportedMetadata) { setLayerGenericMetadata(kDisplayId, kLayerId, kLayerGenericMetadata1Name, kLayerGenericMetadata1Mandatory, kLayerGenericMetadata1Value)) - .WillOnce(Return(hardware::graphics::composer::V2_4::Error::NONE)); + .WillOnce(Return(V2_4::Error::NONE)); auto result = mLayer.setLayerGenericMetadata(kLayerGenericMetadata1Name, kLayerGenericMetadata1Mandatory, kLayerGenericMetadata1Value); @@ -526,7 +560,7 @@ TEST_F(HWComposerLayerGenericMetadataTest, forwardsSupportedMetadata) { setLayerGenericMetadata(kDisplayId, kLayerId, kLayerGenericMetadata2Name, kLayerGenericMetadata2Mandatory, kLayerGenericMetadata2Value)) - .WillOnce(Return(hardware::graphics::composer::V2_4::Error::UNSUPPORTED)); + .WillOnce(Return(V2_4::Error::UNSUPPORTED)); result = mLayer.setLayerGenericMetadata(kLayerGenericMetadata2Name, kLayerGenericMetadata2Mandatory, kLayerGenericMetadata2Value); diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp index 95f19406b4..2b333f4b87 100644 --- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp @@ -45,7 +45,8 @@ protected: // reparenting tests TEST_F(LayerHierarchyTest, addLayer) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2}; EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); @@ -64,7 +65,8 @@ TEST_F(LayerHierarchyTest, addLayer) { } TEST_F(LayerHierarchyTest, reparentLayer) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentLayer(2, 11); reparentLayer(111, 12); reparentLayer(1221, 1); @@ -79,7 +81,8 @@ TEST_F(LayerHierarchyTest, reparentLayer) { } TEST_F(LayerHierarchyTest, reparentLayerToNull) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentLayer(2, UNASSIGNED_LAYER_ID); reparentLayer(11, UNASSIGNED_LAYER_ID); @@ -96,7 +99,8 @@ TEST_F(LayerHierarchyTest, reparentLayerToNull) { } TEST_F(LayerHierarchyTest, reparentLayerToNullAndDestroyHandles) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentLayer(2, UNASSIGNED_LAYER_ID); reparentLayer(11, UNASSIGNED_LAYER_ID); reparentLayer(1221, UNASSIGNED_LAYER_ID); @@ -115,7 +119,8 @@ TEST_F(LayerHierarchyTest, reparentLayerToNullAndDestroyHandles) { } TEST_F(LayerHierarchyTest, destroyHandleThenDestroyParentLayer) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); destroyLayerHandle(111); UPDATE_AND_VERIFY(hierarchyBuilder); @@ -139,7 +144,8 @@ TEST_F(LayerHierarchyTest, destroyHandleThenDestroyParentLayer) { } TEST_F(LayerHierarchyTest, layerSurvivesTemporaryReparentToNull) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentLayer(11, UNASSIGNED_LAYER_ID); reparentLayer(11, 1); @@ -154,7 +160,8 @@ TEST_F(LayerHierarchyTest, layerSurvivesTemporaryReparentToNull) { // offscreen tests TEST_F(LayerHierarchyTest, layerMovesOnscreen) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentLayer(11, UNASSIGNED_LAYER_ID); UPDATE_AND_VERIFY(hierarchyBuilder); @@ -170,7 +177,8 @@ TEST_F(LayerHierarchyTest, layerMovesOnscreen) { } TEST_F(LayerHierarchyTest, addLayerToOffscreenParent) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentLayer(11, UNASSIGNED_LAYER_ID); UPDATE_AND_VERIFY(hierarchyBuilder); @@ -187,7 +195,8 @@ TEST_F(LayerHierarchyTest, addLayerToOffscreenParent) { // rel-z tests TEST_F(LayerHierarchyTest, setRelativeParent) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentRelativeLayer(11, 2); UPDATE_AND_VERIFY(hierarchyBuilder); @@ -200,7 +209,8 @@ TEST_F(LayerHierarchyTest, setRelativeParent) { } TEST_F(LayerHierarchyTest, reparentFromRelativeParentWithSetLayer) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentRelativeLayer(11, 2); UPDATE_AND_VERIFY(hierarchyBuilder); @@ -216,7 +226,8 @@ TEST_F(LayerHierarchyTest, reparentFromRelativeParentWithSetLayer) { } TEST_F(LayerHierarchyTest, reparentToRelativeParent) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentRelativeLayer(11, 2); UPDATE_AND_VERIFY(hierarchyBuilder); @@ -231,7 +242,8 @@ TEST_F(LayerHierarchyTest, reparentToRelativeParent) { } TEST_F(LayerHierarchyTest, setParentAsRelativeParent) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentLayer(11, 2); UPDATE_AND_VERIFY(hierarchyBuilder); @@ -246,7 +258,8 @@ TEST_F(LayerHierarchyTest, setParentAsRelativeParent) { } TEST_F(LayerHierarchyTest, relativeChildMovesOffscreenIsNotTraversable) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentRelativeLayer(11, 2); UPDATE_AND_VERIFY(hierarchyBuilder); @@ -262,7 +275,8 @@ TEST_F(LayerHierarchyTest, relativeChildMovesOffscreenIsNotTraversable) { } TEST_F(LayerHierarchyTest, reparentRelativeLayer) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentRelativeLayer(11, 2); UPDATE_AND_VERIFY(hierarchyBuilder); @@ -294,7 +308,8 @@ TEST_F(LayerHierarchyTest, reparentRelativeLayer) { // mirror tests TEST_F(LayerHierarchyTest, canTraverseMirrorLayer) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11); UPDATE_AND_VERIFY(hierarchyBuilder); @@ -308,7 +323,8 @@ TEST_F(LayerHierarchyTest, canTraverseMirrorLayer) { } TEST_F(LayerHierarchyTest, canMirrorOffscreenLayer) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentLayer(11, UNASSIGNED_LAYER_ID); mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11); @@ -324,7 +340,8 @@ TEST_F(LayerHierarchyTest, canMirrorOffscreenLayer) { TEST_F(LayerHierarchyTest, newChildLayerIsUpdatedInMirrorHierarchy) { mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11); mLifecycleManager.commitChanges(); - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); createLayer(1111, 111); createLayer(112, 11); @@ -340,7 +357,8 @@ TEST_F(LayerHierarchyTest, newChildLayerIsUpdatedInMirrorHierarchy) { // mirror & relatives tests TEST_F(LayerHierarchyTest, mirrorWithRelativeOutsideMirrorHierarchy) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentRelativeLayer(111, 12); mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11); @@ -371,7 +389,8 @@ TEST_F(LayerHierarchyTest, mirrorWithRelativeOutsideMirrorHierarchy) { } TEST_F(LayerHierarchyTest, mirrorWithRelativeInsideMirrorHierarchy) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentRelativeLayer(1221, 12); mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 12); @@ -401,7 +420,8 @@ TEST_F(LayerHierarchyTest, mirrorWithRelativeInsideMirrorHierarchy) { } TEST_F(LayerHierarchyTest, childMovesOffscreenWhenRelativeParentDies) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentRelativeLayer(11, 2); reparentLayer(2, UNASSIGNED_LAYER_ID); @@ -427,7 +447,8 @@ TEST_F(LayerHierarchyTest, childMovesOffscreenWhenRelativeParentDies) { } TEST_F(LayerHierarchyTest, offscreenLayerCannotBeRelativeToOnscreenLayer) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentRelativeLayer(1221, 2); UPDATE_AND_VERIFY(hierarchyBuilder); @@ -462,7 +483,8 @@ TEST_F(LayerHierarchyTest, offscreenLayerCannotBeRelativeToOnscreenLayer) { } TEST_F(LayerHierarchyTest, backgroundLayersAreBehindParentLayer) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); updateBackgroundColor(1, 0.5); UPDATE_AND_VERIFY(hierarchyBuilder); @@ -485,7 +507,8 @@ TEST_F(LayerHierarchyTest, ParentBecomesTheChild) { createLayer(11, 1); reparentLayer(1, 11); mLifecycleManager.commitChanges(); - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); std::vector<uint32_t> expectedTraversalPath = {}; EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); @@ -502,17 +525,11 @@ TEST_F(LayerHierarchyTest, RelativeLoops) { createLayer(11, 1); reparentRelativeLayer(11, 2); reparentRelativeLayer(2, 11); - mLifecycleManager.commitChanges(); - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); - - // fix loop - uint32_t invalidRelativeRoot; - bool hasRelZLoop = hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot); - EXPECT_TRUE(hasRelZLoop); - mLifecycleManager.fixRelativeZLoop(invalidRelativeRoot); - hierarchyBuilder.update(mLifecycleManager.getLayers(), mLifecycleManager.getDestroyedLayers()); - EXPECT_EQ(invalidRelativeRoot, 11u); - EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot)); + LayerHierarchyBuilder hierarchyBuilder; + // this call is expected to fix the loop! + hierarchyBuilder.update(mLifecycleManager); + uint32_t unused; + EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(unused)); std::vector<uint32_t> expectedTraversalPath = {1, 11, 2, 2}; EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); @@ -534,16 +551,11 @@ TEST_F(LayerHierarchyTest, IndirectRelativeLoops) { createLayer(221, 22); reparentRelativeLayer(22, 111); reparentRelativeLayer(11, 221); - mLifecycleManager.commitChanges(); - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); - - // fix loop - uint32_t invalidRelativeRoot; - bool hasRelZLoop = hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot); - EXPECT_TRUE(hasRelZLoop); - mLifecycleManager.fixRelativeZLoop(invalidRelativeRoot); - hierarchyBuilder.update(mLifecycleManager.getLayers(), mLifecycleManager.getDestroyedLayers()); - EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot)); + LayerHierarchyBuilder hierarchyBuilder; + // this call is expected to fix the loop! + hierarchyBuilder.update(mLifecycleManager); + uint32_t unused; + EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(unused)); std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 22, 221, 2, 21, 22, 221}; EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); @@ -554,7 +566,8 @@ TEST_F(LayerHierarchyTest, IndirectRelativeLoops) { } TEST_F(LayerHierarchyTest, ReparentRootLayerToNull) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentLayer(1, UNASSIGNED_LAYER_ID); UPDATE_AND_VERIFY(hierarchyBuilder); @@ -568,7 +581,8 @@ TEST_F(LayerHierarchyTest, ReparentRootLayerToNull) { TEST_F(LayerHierarchyTest, AddRemoveLayerInSameTransaction) { // remove default hierarchy mLifecycleManager = LayerLifecycleManager(); - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); createRootLayer(1); destroyLayerHandle(1); UPDATE_AND_VERIFY(hierarchyBuilder); @@ -582,7 +596,8 @@ TEST_F(LayerHierarchyTest, AddRemoveLayerInSameTransaction) { // traversal path test TEST_F(LayerHierarchyTest, traversalPathId) { setZ(122, -1); - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); auto checkTraversalPathIdVisitor = [](const LayerHierarchy& hierarchy, const LayerHierarchy::TraversalPath& traversalPath) -> bool { @@ -605,7 +620,8 @@ TEST_F(LayerHierarchyTest, zorderRespectsLayerSequenceId) { createLayer(53, 5); mLifecycleManager.commitChanges(); - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); UPDATE_AND_VERIFY(hierarchyBuilder); std::vector<uint32_t> expectedTraversalPath = {1, 11, 2, 4, 5, 51, 53}; EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); @@ -639,7 +655,8 @@ TEST_F(LayerHierarchyTest, zorderRespectsLayerZ) { setZ(13, 1); mLifecycleManager.commitChanges(); - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); UPDATE_AND_VERIFY(hierarchyBuilder); std::vector<uint32_t> expectedTraversalPath = {1, 11, 13, 12}; EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); @@ -661,7 +678,8 @@ TEST_F(LayerHierarchyTest, zorderRespectsLayerStack) { setLayerStack(2, 10); mLifecycleManager.commitChanges(); - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); UPDATE_AND_VERIFY(hierarchyBuilder); std::vector<uint32_t> expectedTraversalPath = {2, 21, 1, 11}; EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); @@ -672,7 +690,8 @@ TEST_F(LayerHierarchyTest, zorderRespectsLayerStack) { } TEST_F(LayerHierarchyTest, canMirrorDisplay) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot); createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0)); setLayerStack(3, 1); @@ -687,7 +706,8 @@ TEST_F(LayerHierarchyTest, canMirrorDisplay) { } TEST_F(LayerHierarchyTest, mirrorNonExistingDisplay) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot); createDisplayMirrorLayer(3, ui::LayerStack::fromValue(5)); setLayerStack(3, 1); @@ -701,7 +721,8 @@ TEST_F(LayerHierarchyTest, mirrorNonExistingDisplay) { } TEST_F(LayerHierarchyTest, newRootLayerIsMirrored) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot); createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0)); setLayerStack(3, 1); @@ -719,7 +740,8 @@ TEST_F(LayerHierarchyTest, newRootLayerIsMirrored) { } TEST_F(LayerHierarchyTest, removedRootLayerIsNoLongerMirrored) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot); createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0)); setLayerStack(3, 1); @@ -737,7 +759,8 @@ TEST_F(LayerHierarchyTest, removedRootLayerIsNoLongerMirrored) { } TEST_F(LayerHierarchyTest, canMirrorDisplayWithMirrors) { - LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); reparentLayer(12, UNASSIGNED_LAYER_ID); mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11); UPDATE_AND_VERIFY(hierarchyBuilder); diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h index 7e9abce690..67e624922c 100644 --- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h +++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h @@ -176,14 +176,12 @@ protected: void destroyLayerHandle(uint32_t id) { mLifecycleManager.onHandlesDestroyed({{id, "test"}}); } void updateAndVerify(LayerHierarchyBuilder& hierarchyBuilder) { - if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) { - hierarchyBuilder.update(mLifecycleManager.getLayers(), - mLifecycleManager.getDestroyedLayers()); - } + hierarchyBuilder.update(mLifecycleManager); mLifecycleManager.commitChanges(); // rebuild layer hierarchy from scratch and verify that it matches the updated state. - LayerHierarchyBuilder newBuilder(mLifecycleManager.getLayers()); + LayerHierarchyBuilder newBuilder; + newBuilder.update(mLifecycleManager); EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), getTraversalPath(newBuilder.getHierarchy())); EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), @@ -512,10 +510,7 @@ protected: } void update(LayerSnapshotBuilder& snapshotBuilder) { - if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) { - mHierarchyBuilder.update(mLifecycleManager.getLayers(), - mLifecycleManager.getDestroyedLayers()); - } + mHierarchyBuilder.update(mLifecycleManager); LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(), .layerLifecycleManager = mLifecycleManager, .includeMetadata = false, @@ -530,7 +525,7 @@ protected: mLifecycleManager.commitChanges(); } - LayerHierarchyBuilder mHierarchyBuilder{{}}; + LayerHierarchyBuilder mHierarchyBuilder; DisplayInfos mFrontEndDisplayInfos; bool mHasDisplayChanges = false; diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp index 8a412f8509..110f324c8b 100644 --- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp @@ -56,7 +56,7 @@ protected: LayerHistoryIntegrationTest() : LayerSnapshotTestBase() { mFlinger.resetScheduler(mScheduler); mLifecycleManager = {}; - mHierarchyBuilder = {{}}; + mHierarchyBuilder = {}; } void updateLayerSnapshotsAndLayerHistory(nsecs_t now) { @@ -165,12 +165,8 @@ protected: DisplayModeId(0)); mock::SchedulerCallback mSchedulerCallback; - mock::VsyncTrackerCallback mVsyncTrackerCallback; - - TestableScheduler* mScheduler = - new TestableScheduler(mSelector, mSchedulerCallback, mVsyncTrackerCallback); - TestableSurfaceFlinger mFlinger; + TestableScheduler* mScheduler = new TestableScheduler(mSelector, mFlinger, mSchedulerCallback); }; namespace { diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp index 0ae3ca33b1..9b8ff42782 100644 --- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp @@ -146,12 +146,8 @@ protected: DisplayModeId(0)); mock::SchedulerCallback mSchedulerCallback; - - mock::VsyncTrackerCallback mVsyncTrackerCallback; - TestableScheduler* mScheduler = - new TestableScheduler(mSelector, mSchedulerCallback, mVsyncTrackerCallback); - TestableSurfaceFlinger mFlinger; + TestableScheduler* mScheduler = new TestableScheduler(mSelector, mFlinger, mSchedulerCallback); }; namespace { @@ -650,7 +646,7 @@ TEST_F(LayerHistoryTest, oneLayerExplicitVoteWithCategory) { // Second LayerRequirement is the frame rate specification EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summarizeLayerHistory(time)[1].vote); EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[1].desiredRefreshRate); - EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[1].frameRateCategory); + EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[1].frameRateCategory); // layer became inactive, but the vote stays setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic); diff --git a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp index 07a522ac4e..c1fa6ac6f2 100644 --- a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp @@ -64,10 +64,8 @@ protected: HI_FPS)), DisplayModeId(0)); mock::SchedulerCallback mSchedulerCallback; - mock::VsyncTrackerCallback mVsyncTrackerCallback; - TestableScheduler* mScheduler = - new TestableScheduler(mSelector, mSchedulerCallback, mVsyncTrackerCallback); TestableSurfaceFlinger mFlinger; + TestableScheduler* mScheduler = new TestableScheduler(mSelector, mFlinger, mSchedulerCallback); }; namespace { @@ -215,7 +213,8 @@ TEST_F(LayerInfoTest, getRefreshRateVote_explicitVote) { TEST_F(LayerInfoTest, getRefreshRateVote_explicitVoteWithCategory) { LayerInfo::LayerVote vote = {.type = LayerHistory::LayerVoteType::ExplicitDefault, .fps = 20_Hz, - .category = FrameRateCategory::High}; + .category = FrameRateCategory::High, + .categorySmoothSwitchOnly = true}; layerInfo.setLayerVote(vote); auto actualVotes = @@ -223,10 +222,12 @@ TEST_F(LayerInfoTest, getRefreshRateVote_explicitVoteWithCategory) { ASSERT_EQ(actualVotes.size(), 2u); ASSERT_EQ(actualVotes[0].type, LayerHistory::LayerVoteType::ExplicitCategory); ASSERT_EQ(actualVotes[0].category, vote.category); + ASSERT_TRUE(actualVotes[0].categorySmoothSwitchOnly); ASSERT_EQ(actualVotes[1].type, vote.type); ASSERT_EQ(actualVotes[1].fps, vote.fps); ASSERT_EQ(actualVotes[1].seamlessness, vote.seamlessness); - ASSERT_EQ(actualVotes[1].category, vote.category); + ASSERT_EQ(actualVotes[1].category, FrameRateCategory::Default); + ASSERT_TRUE(actualVotes[1].categorySmoothSwitchOnly); } TEST_F(LayerInfoTest, getRefreshRateVote_explicitCategory) { diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index 50cd784725..3baa48d002 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -58,13 +58,24 @@ protected: void update(LayerSnapshotBuilder& actualBuilder, LayerSnapshotBuilder::Args& args) { if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) { - mHierarchyBuilder.update(mLifecycleManager.getLayers(), - mLifecycleManager.getDestroyedLayers()); + mHierarchyBuilder.update(mLifecycleManager); } args.root = mHierarchyBuilder.getHierarchy(); actualBuilder.update(args); } + void update(LayerSnapshotBuilder& actualBuilder) { + LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(), + .layerLifecycleManager = mLifecycleManager, + .includeMetadata = false, + .displays = mFrontEndDisplayInfos, + .globalShadowSettings = globalShadowSettings, + .supportsBlur = true, + .supportedLayerGenericMetadata = {}, + .genericLayerMetadataKeyMap = {}}; + update(actualBuilder, args); + } + void updateAndVerify(LayerSnapshotBuilder& actualBuilder, bool hasDisplayChanges, const std::vector<uint32_t> expectedVisibleLayerIdsInZOrder) { LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(), @@ -354,6 +365,23 @@ TEST_F(LayerSnapshotTest, CanCropTouchableRegion) { EXPECT_EQ(getSnapshot({.id = 111})->inputInfo.touchableRegion.bounds(), modifiedTouchCrop); } +TEST_F(LayerSnapshotTest, CanCropTouchableRegionWithDisplayTransform) { + DisplayInfo displayInfo; + displayInfo.transform = ui::Transform(ui::Transform::RotationFlags::ROT_90, 1000, 1000); + mFrontEndDisplayInfos.emplace_or_replace(ui::LayerStack::fromValue(1), displayInfo); + + Rect touchCrop{300, 300, 400, 500}; + createRootLayer(3); + setCrop(3, touchCrop); + setLayerStack(3, 1); + Region touch{Rect{0, 0, 1000, 1000}}; + setTouchableRegionCrop(3, touch, /*touchCropId=*/3, /*replaceTouchableRegionWithCrop=*/false); + + UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3}); + Rect rotatedCrop = {500, 300, 700, 400}; + EXPECT_EQ(getSnapshot({.id = 3})->inputInfo.touchableRegion.bounds(), rotatedCrop); +} + TEST_F(LayerSnapshotTest, blurUpdatesWhenAlphaChanges) { int blurRadius = 42; setBackgroundBlurRadius(1221, static_cast<uint32_t>(blurRadius)); @@ -651,7 +679,7 @@ TEST_F(LayerSnapshotTest, frameRateWithCategory) { // │ └── 13 // └── 2 setFrameRate(11, 244.f, 0, 0); - setFrameRateCategory(122, 3 /* Normal */); + setFrameRateCategory(122, ANATIVEWINDOW_FRAME_RATE_CATEGORY_NORMAL); UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); // verify parent 1 gets no vote @@ -846,7 +874,7 @@ TEST_F(LayerSnapshotTest, frameRateSelectionStrategyWithCategory) { // │ │ └── 1221 // │ └── 13 // └── 2 - setFrameRateCategory(12, 4 /* high */); + setFrameRateCategory(12, ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH); setFrameRate(122, 123.f, 0, 0); setFrameRateSelectionStrategy(12, 1 /* OverrideChildren */); @@ -888,7 +916,7 @@ TEST_F(LayerSnapshotTest, frameRateSelectionStrategyWithCategory) { // │ │ └── 1221 // │ └── 13 // └── 2 - setFrameRateCategory(12, 0 /* default */); + setFrameRateCategory(12, ANATIVEWINDOW_FRAME_RATE_CATEGORY_DEFAULT); setFrameRateSelectionStrategy(12, 0 /* Default */); UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); // verify parent 1 gets no vote @@ -1183,4 +1211,51 @@ TEST_F(LayerSnapshotTest, propagateDropInputMode) { EXPECT_EQ(getSnapshot(11)->dropInputMode, gui::DropInputMode::ALL); } +TEST_F(LayerSnapshotTest, NonVisibleLayerWithInput) { + LayerHierarchyTestBase::createRootLayer(3); + setColor(3, {-1._hf, -1._hf, -1._hf}); + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + + 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().layerId = 3; + transactions.back().states.front().state.windowInfoHandle = sp<gui::WindowInfoHandle>::make(); + auto inputInfo = transactions.back().states.front().state.windowInfoHandle->editInfo(); + inputInfo->token = sp<BBinder>::make(); + mLifecycleManager.applyTransactions(transactions); + + update(mSnapshotBuilder); + + bool foundInputLayer = false; + mSnapshotBuilder.forEachInputSnapshot([&](const frontend::LayerSnapshot& snapshot) { + if (snapshot.uniqueSequence == 3) { + foundInputLayer = true; + } + }); + EXPECT_TRUE(foundInputLayer); +} + +TEST_F(LayerSnapshotTest, canOccludePresentation) { + setFlags(12, layer_state_t::eCanOccludePresentation, layer_state_t::eCanOccludePresentation); + LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(), + .layerLifecycleManager = mLifecycleManager, + .includeMetadata = false, + .displays = mFrontEndDisplayInfos, + .displayChanges = false, + .globalShadowSettings = globalShadowSettings, + .supportsBlur = true, + .supportedLayerGenericMetadata = {}, + .genericLayerMetadataKeyMap = {}}; + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + + EXPECT_EQ(getSnapshot(1)->inputInfo.canOccludePresentation, false); + + // ensure we can set the property on the window info for layer and all its children + EXPECT_EQ(getSnapshot(12)->inputInfo.canOccludePresentation, true); + EXPECT_EQ(getSnapshot(121)->inputInfo.canOccludePresentation, true); + EXPECT_EQ(getSnapshot(1221)->inputInfo.canOccludePresentation, true); +} + } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp index 9aa089f900..f5661fccf5 100644 --- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp +++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp @@ -41,6 +41,7 @@ struct NoOpCompositor final : ICompositor { return {}; } void sample() override {} + void sendNotifyExpectedPresentHint(PhysicalDisplayId) {} } gNoOpCompositor; class TestableMessageQueue : public impl::MessageQueue { @@ -72,7 +73,8 @@ struct MockTokenManager : frametimeline::TokenManager { struct MessageQueueTest : testing::Test { void SetUp() override { EXPECT_CALL(*mVSyncDispatch, registerCallback(_, "sf")).WillOnce(Return(mCallbackToken)); - EXPECT_NO_FATAL_FAILURE(mEventQueue.initVsync(mVSyncDispatch, mTokenManager, kDuration)); + EXPECT_NO_FATAL_FAILURE( + mEventQueue.initVsyncInternal(mVSyncDispatch, mTokenManager, kDuration)); EXPECT_CALL(*mVSyncDispatch, unregisterCallback(mCallbackToken)).Times(1); } @@ -91,7 +93,7 @@ namespace { TEST_F(MessageQueueTest, commit) { const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(), .readyDuration = 0, - .earliestVsync = 0}; + .lastVsync = 0}; EXPECT_FALSE(mEventQueue.getScheduledFrameTime()); EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); @@ -105,7 +107,7 @@ TEST_F(MessageQueueTest, commitTwice) { InSequence s; const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(), .readyDuration = 0, - .earliestVsync = 0}; + .lastVsync = 0}; EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); @@ -124,7 +126,7 @@ TEST_F(MessageQueueTest, commitTwiceWithCallback) { InSequence s; const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(), .readyDuration = 0, - .earliestVsync = 0}; + .lastVsync = 0}; EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); @@ -151,7 +153,7 @@ TEST_F(MessageQueueTest, commitTwiceWithCallback) { const auto timingAfterCallback = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(), .readyDuration = 0, - .earliestVsync = kPresentTime.ns()}; + .lastVsync = kPresentTime.ns()}; EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timingAfterCallback)).WillOnce(Return(0)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); @@ -163,7 +165,7 @@ TEST_F(MessageQueueTest, commitWithDurationChange) { const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDifferentDuration.ns(), .readyDuration = 0, - .earliestVsync = 0}; + .lastVsync = 0}; EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(0)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp index 0cacf810c0..39a8aacaf8 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp @@ -103,7 +103,7 @@ struct TestableRefreshRateSelector : RefreshRateSelector { auto& mutableGetRankedRefreshRatesCache() { return mGetRankedFrameRatesCache; } auto getRankedFrameRates(const std::vector<LayerRequirement>& layers, - GlobalSignals signals) const { + GlobalSignals signals = {}) const { const auto result = RefreshRateSelector::getRankedFrameRates(layers, signals); EXPECT_TRUE(std::is_sorted(result.ranking.begin(), result.ranking.end(), @@ -1607,6 +1607,92 @@ TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_60_12 } } +TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_HighHint) { + auto selector = createSelector(makeModes(kMode24, kMode30, kMode60, kMode120), kModeId60); + + std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}}; + auto& lr1 = layers[0]; + auto& lr2 = layers[1]; + + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::HighHint; + lr1.name = "ExplicitCategory HighHint"; + lr2.vote = LayerVoteType::NoVote; + lr2.name = "NoVote"; + auto actualRankedFrameRates = selector.getRankedFrameRates(layers); + // Gets touch boost + EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps); + EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId()); + EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch); + + // No touch boost, for example a game that uses setFrameRate(30, default compatibility). + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::HighHint; + lr1.name = "ExplicitCategory HighHint"; + lr2.vote = LayerVoteType::ExplicitDefault; + lr2.desiredRefreshRate = 30_Hz; + lr2.name = "30Hz ExplicitDefault"; + actualRankedFrameRates = selector.getRankedFrameRates(layers); + EXPECT_EQ(30_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps); + EXPECT_EQ(kModeId30, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId()); + EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch); + + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::HighHint; + lr1.name = "ExplicitCategory HighHint"; + lr2.vote = LayerVoteType::ExplicitCategory; + lr2.frameRateCategory = FrameRateCategory::HighHint; + lr2.name = "ExplicitCategory HighHint#2"; + actualRankedFrameRates = selector.getRankedFrameRates(layers); + // Gets touch boost + EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps); + EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId()); + EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch); + + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::HighHint; + lr1.name = "ExplicitCategory HighHint"; + lr2.vote = LayerVoteType::ExplicitCategory; + lr2.frameRateCategory = FrameRateCategory::Low; + lr2.name = "ExplicitCategory Low"; + actualRankedFrameRates = selector.getRankedFrameRates(layers); + // Gets touch boost + EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps); + EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId()); + EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch); + + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::HighHint; + lr1.name = "ExplicitCategory HighHint"; + lr2.vote = LayerVoteType::ExplicitExactOrMultiple; + lr2.desiredRefreshRate = 30_Hz; + lr2.name = "30Hz ExplicitExactOrMultiple"; + actualRankedFrameRates = selector.getRankedFrameRates(layers); + // Gets touch boost + EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps); + EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId()); + EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch); + + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::HighHint; + lr1.name = "ExplicitCategory HighHint"; + lr2.vote = LayerVoteType::ExplicitExact; + lr2.desiredRefreshRate = 30_Hz; + lr2.name = "30Hz ExplicitExact"; + actualRankedFrameRates = selector.getRankedFrameRates(layers); + if (selector.supportsAppFrameRateOverrideByContent()) { + // Gets touch boost + EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps); + EXPECT_EQ(kModeId120, + actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId()); + EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch); + } else { + EXPECT_EQ(30_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps); + EXPECT_EQ(kModeId30, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId()); + EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch); + } +} + TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_smoothSwitchOnly_60_120_nonVrr) { if (GetParam() != Config::FrameRateOverride::Enabled) { diff --git a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp index e2e3d7b12b..fba77e997c 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp @@ -37,7 +37,7 @@ protected: ~RefreshRateStatsTest(); void resetStats(Fps fps) { - mRefreshRateStats = std::make_unique<RefreshRateStats>(mTimeStats, fps, PowerMode::OFF); + mRefreshRateStats = std::make_unique<RefreshRateStats>(mTimeStats, fps); } mock::TimeStats mTimeStats; diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index e515895a1a..b0595257a9 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -95,16 +95,13 @@ protected: kDisplay1Mode60->getId()); mock::SchedulerCallback mSchedulerCallback; - mock::VsyncTrackerCallback mVsyncTrackerCallback; - TestableScheduler* mScheduler = - new TestableScheduler{mSelector, mSchedulerCallback, mVsyncTrackerCallback}; - surfaceflinger::frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}}; + TestableSurfaceFlinger mFlinger; + TestableScheduler* mScheduler = new TestableScheduler{mSelector, mFlinger, mSchedulerCallback}; + surfaceflinger::frontend::LayerHierarchyBuilder mLayerHierarchyBuilder; ConnectionHandle mConnectionHandle; MockEventThread* mEventThread; sp<MockEventThreadConnection> mEventThreadConnection; - - TestableSurfaceFlinger mFlinger; }; SchedulerTest::SchedulerTest() { @@ -342,6 +339,61 @@ TEST_F(SchedulerTest, chooseDisplayModesSingleDisplay) { EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, globalSignals)); } +TEST_F(SchedulerTest, chooseDisplayModesSingleDisplayHighHintTouchSignal) { + mScheduler->registerDisplay(kDisplayId1, + std::make_shared<RefreshRateSelector>(kDisplay1Modes, + kDisplay1Mode60->getId())); + + using DisplayModeChoice = TestableScheduler::DisplayModeChoice; + + std::vector<RefreshRateSelector::LayerRequirement> layers = + std::vector<RefreshRateSelector::LayerRequirement>({{.weight = 1.f}, {.weight = 1.f}}); + auto& lr1 = layers[0]; + auto& lr2 = layers[1]; + + // Scenario that is similar to game. Expects no touch boost. + lr1.vote = RefreshRateSelector::LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::HighHint; + lr1.name = "ExplicitCategory HighHint"; + lr2.vote = RefreshRateSelector::LayerVoteType::ExplicitDefault; + lr2.desiredRefreshRate = 30_Hz; + lr2.name = "30Hz ExplicitDefault"; + mScheduler->setContentRequirements(layers); + auto modeChoices = mScheduler->chooseDisplayModes(); + ASSERT_EQ(1u, modeChoices.size()); + auto choice = modeChoices.get(kDisplayId1); + ASSERT_TRUE(choice); + EXPECT_EQ(choice->get(), DisplayModeChoice({60_Hz, kDisplay1Mode60}, {.touch = false})); + + // Scenario that is similar to video playback and interaction. Expects touch boost. + lr1.vote = RefreshRateSelector::LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::HighHint; + lr1.name = "ExplicitCategory HighHint"; + lr2.vote = RefreshRateSelector::LayerVoteType::ExplicitExactOrMultiple; + lr2.desiredRefreshRate = 30_Hz; + lr2.name = "30Hz ExplicitExactOrMultiple"; + mScheduler->setContentRequirements(layers); + modeChoices = mScheduler->chooseDisplayModes(); + ASSERT_EQ(1u, modeChoices.size()); + choice = modeChoices.get(kDisplayId1); + ASSERT_TRUE(choice); + EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, {.touch = true})); + + // Scenario with explicit category and HighHint. Expects touch boost. + lr1.vote = RefreshRateSelector::LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::HighHint; + lr1.name = "ExplicitCategory HighHint"; + lr2.vote = RefreshRateSelector::LayerVoteType::ExplicitCategory; + lr2.frameRateCategory = FrameRateCategory::Low; + lr2.name = "ExplicitCategory Low"; + mScheduler->setContentRequirements(layers); + modeChoices = mScheduler->chooseDisplayModes(); + ASSERT_EQ(1u, modeChoices.size()); + choice = modeChoices.get(kDisplayId1); + ASSERT_TRUE(choice); + EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, {.touch = true})); +} + TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { mScheduler->registerDisplay(kDisplayId1, std::make_shared<RefreshRateSelector>(kDisplay1Modes, @@ -517,6 +569,7 @@ TEST_F(SchedulerTest, onFrameSignalMultipleDisplays) { } void sample() override {} + void sendNotifyExpectedPresentHint(PhysicalDisplayId) override {} } compositor(*mScheduler); mScheduler->doFrameSignal(compositor, VsyncId(42)); @@ -569,32 +622,32 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { frameRate.getPeriodNsecs())})); std::shared_ptr<VSyncPredictor> vrrTracker = std::make_shared<VSyncPredictor>(kMode, kHistorySize, kMinimumSamplesForPrediction, - kOutlierTolerancePercent, mVsyncTrackerCallback); + kOutlierTolerancePercent); std::shared_ptr<RefreshRateSelector> vrrSelectorPtr = std::make_shared<RefreshRateSelector>(makeModes(kMode), kMode->getId()); TestableScheduler scheduler{std::make_unique<android::mock::VsyncController>(), vrrTracker, vrrSelectorPtr, - sp<VsyncModulator>::make(VsyncConfigSet{}), - mSchedulerCallback, - mVsyncTrackerCallback}; + mFlinger.getFactory(), + mFlinger.getTimeStats(), + mSchedulerCallback}; scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, vrrTracker); vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate); scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate); vrrTracker->addVsyncTimestamp(0); - // Next frame at refresh rate as no previous frame - EXPECT_EQ(refreshRate, - scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(0))); - EXPECT_EQ(Fps::fromPeriodNsecs(1000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), - TimePoint::fromNs(500))); + TimePoint::fromNs(1000))); EXPECT_EQ(Fps::fromPeriodNsecs(1000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), - TimePoint::fromNs(1500))); + TimePoint::fromNs(2000))); + // Not crossing the min frame period + EXPECT_EQ(Fps::fromPeriodNsecs(1500), + scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), + TimePoint::fromNs(2500))); // Change render rate frameRate = Fps::fromPeriodNsecs(2000); vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate); @@ -602,10 +655,10 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { EXPECT_EQ(Fps::fromPeriodNsecs(2000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), - TimePoint::fromNs(2500))); + TimePoint::fromNs(2000))); EXPECT_EQ(Fps::fromPeriodNsecs(2000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), - TimePoint::fromNs(4500))); + TimePoint::fromNs(4000))); } TEST_F(SchedulerTest, resyncAllToHardwareVsync) FTL_FAKE_GUARD(kMainThreadContext) { diff --git a/services/surfaceflinger/tests/unittests/StrongTypingTest.cpp b/services/surfaceflinger/tests/unittests/StrongTypingTest.cpp deleted file mode 100644 index 45b761036a..0000000000 --- a/services/surfaceflinger/tests/unittests/StrongTypingTest.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <gmock/gmock.h> -#include <gtest/gtest.h> -#include "Scheduler/StrongTyping.h" - -using namespace testing; - -namespace android { - -TEST(StrongTypeTest, comparison) { - using SpunkyType = StrongTyping<int, struct SpunkyTypeTag, Compare>; - SpunkyType f1(10); - - EXPECT_TRUE(f1 == f1); - EXPECT_TRUE(SpunkyType(10) != SpunkyType(11)); - EXPECT_FALSE(SpunkyType(31) != SpunkyType(31)); - - EXPECT_TRUE(SpunkyType(10) < SpunkyType(11)); - EXPECT_TRUE(SpunkyType(-1) < SpunkyType(0)); - EXPECT_FALSE(SpunkyType(-10) < SpunkyType(-20)); - - EXPECT_TRUE(SpunkyType(10) <= SpunkyType(11)); - EXPECT_TRUE(SpunkyType(10) <= SpunkyType(10)); - EXPECT_TRUE(SpunkyType(-10) <= SpunkyType(1)); - EXPECT_FALSE(SpunkyType(10) <= SpunkyType(9)); - - EXPECT_TRUE(SpunkyType(11) >= SpunkyType(11)); - EXPECT_TRUE(SpunkyType(12) >= SpunkyType(11)); - EXPECT_FALSE(SpunkyType(11) >= SpunkyType(12)); - - EXPECT_FALSE(SpunkyType(11) > SpunkyType(12)); - EXPECT_TRUE(SpunkyType(-11) < SpunkyType(7)); -} - -TEST(StrongTypeTest, addition) { - using FunkyType = StrongTyping<int, struct FunkyTypeTag, Compare, Add>; - FunkyType f2(22); - FunkyType f1(10); - - EXPECT_THAT(f1 + f2, Eq(FunkyType(32))); - EXPECT_THAT(f2 + f1, Eq(FunkyType(32))); - - EXPECT_THAT(++f1.value(), Eq(11)); - EXPECT_THAT(f1.value(), Eq(11)); - EXPECT_THAT(f1++.value(), Eq(11)); - EXPECT_THAT(f1++.value(), Eq(12)); - EXPECT_THAT(f1.value(), Eq(13)); - - auto f3 = f1; - EXPECT_THAT(f1, Eq(f3)); - EXPECT_THAT(f1, Lt(f2)); - - f3 += f1; - EXPECT_THAT(f1.value(), Eq(13)); - EXPECT_THAT(f3.value(), Eq(26)); -} -} // namespace android diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp new file mode 100644 index 0000000000..f127213f0d --- /dev/null +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 2024 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 "CommitAndCompositeTest.h" + +#define EXPECT_COLOR_MATRIX_CHANGED(current, drawing) \ + EXPECT_EQ(current, mFlinger.currentState().colorMatrixChanged); \ + EXPECT_EQ(drawing, mFlinger.drawingState().colorMatrixChanged); + +namespace android { + +class ColorMatrixTest : public CommitAndCompositeTest {}; + +TEST_F(ColorMatrixTest, colorMatrixChanged) { + EXPECT_COLOR_MATRIX_CHANGED(true, true); + mFlinger.mutableTransactionFlags() |= eTransactionNeeded; + + mFlinger.commitAndComposite(); + EXPECT_COLOR_MATRIX_CHANGED(false, false); + + mFlinger.setDaltonizerType(ColorBlindnessType::Deuteranomaly); + EXPECT_COLOR_MATRIX_CHANGED(true, false); + + mFlinger.commit(); + EXPECT_COLOR_MATRIX_CHANGED(false, true); + + mFlinger.commitAndComposite(); + EXPECT_COLOR_MATRIX_CHANGED(false, false); +} + +TEST_F(ColorMatrixTest, colorMatrixChangedAfterDisplayTransaction) { + EXPECT_COLOR_MATRIX_CHANGED(true, true); + mFlinger.mutableTransactionFlags() |= eTransactionNeeded; + + mFlinger.commitAndComposite(); + EXPECT_COLOR_MATRIX_CHANGED(false, false); + + mFlinger.createDisplay(String8("Test Display"), false); + + mFlinger.commit(); + EXPECT_COLOR_MATRIX_CHANGED(false, true); + + mFlinger.commitAndComposite(); + EXPECT_COLOR_MATRIX_CHANGED(false, false); +} + +} // namespace android diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp index 7ad97a2133..15a6db626a 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp @@ -21,9 +21,21 @@ #include "mock/DisplayHardware/MockDisplayMode.h" #include "mock/MockDisplayModeSpecs.h" +#include <com_android_graphics_surfaceflinger_flags.h> +#include <common/test/FlagUtils.h> #include <ftl/fake_guard.h> #include <scheduler/Fps.h> +using namespace com::android::graphics::surfaceflinger; + +#define EXPECT_SET_ACTIVE_CONFIG(displayId, modeId) \ + EXPECT_CALL(*mComposer, \ + setActiveConfigWithConstraints(displayId, \ + static_cast<hal::HWConfigId>( \ + ftl::to_underlying(modeId)), \ + _, _)) \ + .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))) + namespace android { namespace { @@ -53,7 +65,7 @@ public: auto vsyncController = std::make_unique<mock::VsyncController>(); auto vsyncTracker = std::make_shared<mock::VSyncTracker>(); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); + EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_, _)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) .WillRepeatedly(Return( TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); @@ -138,14 +150,14 @@ void DisplayModeSwitchingTest::setupScheduler( auto vsyncController = std::make_unique<mock::VsyncController>(); auto vsyncTracker = std::make_shared<mock::VSyncTracker>(); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); + EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_, _)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) .WillRepeatedly( Return(TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); EXPECT_CALL(*vsyncTracker, minFramePeriod()) .WillRepeatedly(Return(Period::fromNs( TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD))); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); + EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_, _)).WillRepeatedly(Return(0)); mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), std::move(eventThread), std::move(sfEventThread), std::move(selectorPtr), @@ -161,8 +173,7 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithRefreshRequ mFlinger.onActiveDisplayChanged(nullptr, *mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), - mock::createDisplayModeSpecs(kModeId90.value(), false, 0, - 120)); + mock::createDisplayModeSpecs(kModeId90, false, 0, 120)); ASSERT_TRUE(mDisplay->getDesiredMode()); EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId90); @@ -170,10 +181,7 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithRefreshRequ // Verify that next commit will call setActiveConfigWithConstraints in HWC const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; - EXPECT_CALL(*mComposer, - setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID, - hal::HWConfigId(kModeId90.value()), _, _)) - .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); + EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId90); mFlinger.commit(); @@ -202,8 +210,7 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithoutRefreshR mFlinger.onActiveDisplayChanged(nullptr, *mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), - mock::createDisplayModeSpecs(kModeId90.value(), true, 0, - 120)); + mock::createDisplayModeSpecs(kModeId90, true, 0, 120)); ASSERT_TRUE(mDisplay->getDesiredMode()); EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId90); @@ -212,10 +219,7 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithoutRefreshR // Verify that next commit will call setActiveConfigWithConstraints in HWC // and complete the mode change. const VsyncPeriodChangeTimeline timeline{.refreshRequired = false}; - EXPECT_CALL(*mComposer, - setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID, - hal::HWConfigId(kModeId90.value()), _, _)) - .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); + EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId90); EXPECT_CALL(*mAppEventThread, onModeChanged(scheduler::FrameRateMode{90_Hz, ftl::as_non_null(kMode90)})); @@ -238,28 +242,20 @@ TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) { mFlinger.onActiveDisplayChanged(nullptr, *mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), - mock::createDisplayModeSpecs(kModeId90.value(), false, 0, - 120)); + mock::createDisplayModeSpecs(kModeId90, false, 0, 120)); const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; - EXPECT_CALL(*mComposer, - setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID, - hal::HWConfigId(kModeId90.value()), _, _)) - .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); + EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId90); mFlinger.commit(); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), - mock::createDisplayModeSpecs(kModeId120.value(), false, 0, - 180)); + mock::createDisplayModeSpecs(kModeId120, false, 0, 180)); ASSERT_TRUE(mDisplay->getDesiredMode()); EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId120); - EXPECT_CALL(*mComposer, - setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID, - hal::HWConfigId(kModeId120.value()), _, _)) - .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); + EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId120); mFlinger.commit(); @@ -281,8 +277,7 @@ TEST_F(DisplayModeSwitchingTest, changeResolutionOnActiveDisplayWithoutRefreshRe mFlinger.onActiveDisplayChanged(nullptr, *mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), - mock::createDisplayModeSpecs(kModeId90_4K.value(), false, 0, - 120)); + mock::createDisplayModeSpecs(kModeId90_4K, false, 0, 120)); ASSERT_TRUE(mDisplay->getDesiredMode()); EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId90_4K); @@ -291,10 +286,7 @@ TEST_F(DisplayModeSwitchingTest, changeResolutionOnActiveDisplayWithoutRefreshRe // Verify that next commit will call setActiveConfigWithConstraints in HWC // and complete the mode change. const VsyncPeriodChangeTimeline timeline{.refreshRequired = false}; - EXPECT_CALL(*mComposer, - setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID, - hal::HWConfigId(kModeId90_4K.value()), _, _)) - .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); + EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId90_4K); EXPECT_CALL(*mAppEventThread, onHotplugReceived(mDisplay->getPhysicalId(), true)); @@ -331,7 +323,7 @@ MATCHER_P2(ModeSwitchingTo, flinger, modeId, "") { } if (arg->getDesiredMode()->mode.modePtr->getId() != modeId) { - *result_listener << "Unexpected desired mode " << modeId; + *result_listener << "Unexpected desired mode " << ftl::to_underlying(modeId); return false; } @@ -345,14 +337,15 @@ MATCHER_P2(ModeSwitchingTo, flinger, modeId, "") { MATCHER_P(ModeSettledTo, modeId, "") { if (const auto desiredOpt = arg->getDesiredMode()) { - *result_listener << "Unsettled desired mode " << desiredOpt->mode.modePtr->getId(); + *result_listener << "Unsettled desired mode " + << ftl::to_underlying(desiredOpt->mode.modePtr->getId()); return false; } ftl::FakeGuard guard(kMainThreadContext); if (arg->getActiveMode().modePtr->getId() != modeId) { - *result_listener << "Settled to unexpected active mode " << modeId; + *result_listener << "Settled to unexpected active mode " << ftl::to_underlying(modeId); return false; } @@ -360,6 +353,13 @@ MATCHER_P(ModeSettledTo, modeId, "") { } TEST_F(DisplayModeSwitchingTest, innerXorOuterDisplay) { + SET_FLAG_FOR_TEST(flags::connected_display, true); + + // For the inner display, this is handled by setupHwcHotplugCallExpectations. + EXPECT_CALL(*mComposer, getDisplayConnectionType(kOuterDisplayHwcId, _)) + .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::INTERNAL), + Return(hal::V2_4::Error::NONE))); + const auto [innerDisplay, outerDisplay] = injectOuterDisplay(); EXPECT_TRUE(innerDisplay->isPoweredOn()); @@ -376,22 +376,19 @@ TEST_F(DisplayModeSwitchingTest, innerXorOuterDisplay) { EXPECT_EQ(NO_ERROR, mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(), - mock::createDisplayModeSpecs(kModeId90.value(), - false, 0.f, 120.f))); + mock::createDisplayModeSpecs(kModeId90, false, + 0.f, 120.f))); EXPECT_EQ(NO_ERROR, mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(), - mock::createDisplayModeSpecs(kModeId60.value(), - false, 0.f, 120.f))); + mock::createDisplayModeSpecs(kModeId60, false, + 0.f, 120.f))); EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90)); EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; - EXPECT_CALL(*mComposer, - setActiveConfigWithConstraints(kInnerDisplayHwcId, - hal::HWConfigId(kModeId90.value()), _, _)) - .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); + EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90); mFlinger.commit(); @@ -412,10 +409,7 @@ TEST_F(DisplayModeSwitchingTest, innerXorOuterDisplay) { EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90)); EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60)); - EXPECT_CALL(*mComposer, - setActiveConfigWithConstraints(kOuterDisplayHwcId, - hal::HWConfigId(kModeId60.value()), _, _)) - .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); + EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId60); mFlinger.commit(); @@ -429,6 +423,12 @@ TEST_F(DisplayModeSwitchingTest, innerXorOuterDisplay) { } TEST_F(DisplayModeSwitchingTest, innerAndOuterDisplay) { + SET_FLAG_FOR_TEST(flags::connected_display, true); + + // For the inner display, this is handled by setupHwcHotplugCallExpectations. + EXPECT_CALL(*mComposer, getDisplayConnectionType(kOuterDisplayHwcId, _)) + .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::INTERNAL), + Return(hal::V2_4::Error::NONE))); const auto [innerDisplay, outerDisplay] = injectOuterDisplay(); EXPECT_TRUE(innerDisplay->isPoweredOn()); @@ -447,27 +447,20 @@ TEST_F(DisplayModeSwitchingTest, innerAndOuterDisplay) { EXPECT_EQ(NO_ERROR, mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(), - mock::createDisplayModeSpecs(kModeId90.value(), - false, 0.f, 120.f))); + mock::createDisplayModeSpecs(kModeId90, false, + 0.f, 120.f))); EXPECT_EQ(NO_ERROR, mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(), - mock::createDisplayModeSpecs(kModeId60.value(), - false, 0.f, 120.f))); + mock::createDisplayModeSpecs(kModeId60, false, + 0.f, 120.f))); EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90)); EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60)); const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; - EXPECT_CALL(*mComposer, - setActiveConfigWithConstraints(kInnerDisplayHwcId, - hal::HWConfigId(kModeId90.value()), _, _)) - .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); - - EXPECT_CALL(*mComposer, - setActiveConfigWithConstraints(kOuterDisplayHwcId, - hal::HWConfigId(kModeId60.value()), _, _)) - .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); + EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90); + EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId60); mFlinger.commit(); @@ -486,8 +479,8 @@ TEST_F(DisplayModeSwitchingTest, powerOffDuringModeSet) { EXPECT_EQ(NO_ERROR, mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), - mock::createDisplayModeSpecs(kModeId90.value(), - false, 0.f, 120.f))); + mock::createDisplayModeSpecs(kModeId90, false, + 0.f, 120.f))); EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90)); @@ -495,10 +488,7 @@ TEST_F(DisplayModeSwitchingTest, powerOffDuringModeSet) { mDisplay->setPowerMode(hal::PowerMode::OFF); const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; - EXPECT_CALL(*mComposer, - setActiveConfigWithConstraints(kInnerDisplayHwcId, - hal::HWConfigId(kModeId90.value()), _, _)) - .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); + EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90); mFlinger.commit(); @@ -512,6 +502,13 @@ TEST_F(DisplayModeSwitchingTest, powerOffDuringModeSet) { } TEST_F(DisplayModeSwitchingTest, powerOffDuringConcurrentModeSet) { + SET_FLAG_FOR_TEST(flags::connected_display, true); + + // For the inner display, this is handled by setupHwcHotplugCallExpectations. + EXPECT_CALL(*mComposer, getDisplayConnectionType(kOuterDisplayHwcId, _)) + .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::INTERNAL), + Return(hal::V2_4::Error::NONE))); + const auto [innerDisplay, outerDisplay] = injectOuterDisplay(); EXPECT_TRUE(innerDisplay->isPoweredOn()); @@ -530,13 +527,13 @@ TEST_F(DisplayModeSwitchingTest, powerOffDuringConcurrentModeSet) { EXPECT_EQ(NO_ERROR, mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(), - mock::createDisplayModeSpecs(kModeId90.value(), - false, 0.f, 120.f))); + mock::createDisplayModeSpecs(kModeId90, false, + 0.f, 120.f))); EXPECT_EQ(NO_ERROR, mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(), - mock::createDisplayModeSpecs(kModeId60.value(), - false, 0.f, 120.f))); + mock::createDisplayModeSpecs(kModeId60, false, + 0.f, 120.f))); EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90)); EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60)); @@ -545,10 +542,7 @@ TEST_F(DisplayModeSwitchingTest, powerOffDuringConcurrentModeSet) { outerDisplay->setPowerMode(hal::PowerMode::OFF); const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; - EXPECT_CALL(*mComposer, - setActiveConfigWithConstraints(kInnerDisplayHwcId, - hal::HWConfigId(kModeId90.value()), _, _)) - .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); + EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90); mFlinger.commit(); @@ -567,10 +561,7 @@ TEST_F(DisplayModeSwitchingTest, powerOffDuringConcurrentModeSet) { // Only the outer display is powered on. mFlinger.onActiveDisplayChanged(innerDisplay.get(), *outerDisplay); - EXPECT_CALL(*mComposer, - setActiveConfigWithConstraints(kOuterDisplayHwcId, - hal::HWConfigId(kModeId60.value()), _, _)) - .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); + EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId60); mFlinger.commit(); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp index 41d8f9e4e0..19f8debe84 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp @@ -17,9 +17,9 @@ #undef LOG_TAG #define LOG_TAG "LibSurfaceFlingerUnittests" -#include "DisplayTransactionTestHelpers.h" #include <com_android_graphics_surfaceflinger_flags.h> #include <common/test/FlagUtils.h> +#include "DualDisplayTransactionTest.h" #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -29,35 +29,9 @@ using namespace com::android::graphics::surfaceflinger; namespace android { namespace { -struct FoldableTest : DisplayTransactionTest { - static constexpr bool kWithMockScheduler = false; - FoldableTest() : DisplayTransactionTest(kWithMockScheduler) {} - - void SetUp() override { - injectMockScheduler(kInnerDisplayId); - - // Inject inner and outer displays with uninitialized power modes. - constexpr bool kInitPowerMode = false; - { - InnerDisplayVariant::injectHwcDisplay<kInitPowerMode>(this); - auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this); - injector.setPowerMode(std::nullopt); - injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector()); - mInnerDisplay = injector.inject(); - } - { - OuterDisplayVariant::injectHwcDisplay<kInitPowerMode>(this); - auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this); - injector.setPowerMode(std::nullopt); - mOuterDisplay = injector.inject(); - } - } - - static inline PhysicalDisplayId kInnerDisplayId = InnerDisplayVariant::DISPLAY_ID::get(); - static inline PhysicalDisplayId kOuterDisplayId = OuterDisplayVariant::DISPLAY_ID::get(); - - sp<DisplayDevice> mInnerDisplay, mOuterDisplay; -}; +constexpr bool kExpectSetPowerModeOnce = false; +struct FoldableTest : DualDisplayTransactionTest<hal::PowerMode::OFF, hal::PowerMode::OFF, + kExpectSetPowerModeOnce> {}; TEST_F(FoldableTest, promotesPacesetterOnBoot) { // When the device boots, the inner display should be the pacesetter. diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp index 29acfaa1a5..4e9fba7bda 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp @@ -17,79 +17,16 @@ #undef LOG_TAG #define LOG_TAG "SurfaceFlingerGetDisplayStatsTest" -#include <compositionengine/Display.h> -#include <compositionengine/mock/DisplaySurface.h> #include <gmock/gmock.h> #include <gtest/gtest.h> -#include <renderengine/mock/RenderEngine.h> #include <ui/DisplayStatInfo.h> -#include "TestableSurfaceFlinger.h" -#include "mock/DisplayHardware/MockComposer.h" -#include "mock/DisplayHardware/MockPowerAdvisor.h" -#include "mock/MockTimeStats.h" -#include "mock/system/window/MockNativeWindow.h" -using namespace android; -using namespace testing; +#include "CommitAndCompositeTest.h" namespace android { namespace { -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; -using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector; -constexpr hal::HWDisplayId HWC_DISPLAY = FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID; -constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u); -constexpr int DEFAULT_DISPLAY_WIDTH = 1920; -constexpr int DEFAULT_DISPLAY_HEIGHT = 1024; - -class SurfaceFlingerGetDisplayStatsTest : public Test { -public: - void SetUp() override; - -protected: - TestableSurfaceFlinger mFlinger; - renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine(); - sp<DisplayDevice> mDisplay; - sp<compositionengine::mock::DisplaySurface> mDisplaySurface = - sp<compositionengine::mock::DisplaySurface>::make(); - sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make(); - mock::TimeStats* mTimeStats = new mock::TimeStats(); - Hwc2::mock::PowerAdvisor* mPowerAdvisor = nullptr; - Hwc2::mock::Composer* mComposer = nullptr; -}; - -void SurfaceFlingerGetDisplayStatsTest::SetUp() { - mFlinger.setupMockScheduler({.displayId = DEFAULT_DISPLAY_ID}); - mComposer = new Hwc2::mock::Composer(); - mPowerAdvisor = new Hwc2::mock::PowerAdvisor(); - mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine)); - mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats)); - mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer)); - mFlinger.setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor>(mPowerAdvisor)); - static constexpr bool kIsPrimary = true; - FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, hal::DisplayType::PHYSICAL, kIsPrimary) - .setPowerMode(hal::PowerMode::ON) - .inject(&mFlinger, mComposer); - auto compostionEngineDisplayArgs = - compositionengine::DisplayCreationArgsBuilder() - .setId(DEFAULT_DISPLAY_ID) - .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT}) - .setPowerAdvisor(mPowerAdvisor) - .setName("injected display") - .build(); - auto compositionDisplay = - compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(), - std::move(compostionEngineDisplayArgs)); - mDisplay = - FakeDisplayDeviceInjector(mFlinger, compositionDisplay, - ui::DisplayConnectionType::Internal, HWC_DISPLAY, kIsPrimary) - .setDisplaySurface(mDisplaySurface) - .setNativeWindow(mNativeWindow) - .setPowerMode(hal::PowerMode::ON) - .setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector()) - .skipRegisterDisplay() - .inject(); -} +struct SurfaceFlingerGetDisplayStatsTest : CommitAndCompositeTest {}; // TODO (b/277364366): Clients should be updated to pass in the display they want. TEST_F(SurfaceFlingerGetDisplayStatsTest, nullptrSucceeds) { diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp index a270dc91f2..897f9a0319 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp @@ -17,8 +17,14 @@ #undef LOG_TAG #define LOG_TAG "LibSurfaceFlingerUnittests" +#include <aidl/android/hardware/graphics/common/DisplayHotplugEvent.h> +#include <com_android_graphics_surfaceflinger_flags.h> +#include <common/test/FlagUtils.h> #include "DisplayTransactionTestHelpers.h" +using namespace com::android::graphics::surfaceflinger; +using ::aidl::android::hardware::graphics::common::DisplayHotplugEvent; + namespace android { class HotplugTest : public DisplayTransactionTest {}; @@ -87,6 +93,8 @@ TEST_F(HotplugTest, ignoresDuplicateDisconnection) { } TEST_F(HotplugTest, rejectsHotplugIfFailedToLoadDisplayModes) { + SET_FLAG_FOR_TEST(flags::connected_display, true); + // Inject a primary display. PrimaryDisplayVariant::injectHwcDisplay(this); @@ -94,6 +102,10 @@ TEST_F(HotplugTest, rejectsHotplugIfFailedToLoadDisplayModes) { constexpr bool kFailedHotplug = true; ExternalDisplay::setupHwcHotplugCallExpectations<kFailedHotplug>(this); + EXPECT_CALL(*mEventThread, + onHotplugConnectionError(static_cast<int32_t>(DisplayHotplugEvent::ERROR_UNKNOWN))) + .Times(1); + // Simulate a connect event that fails to load display modes due to HWC already having // disconnected the display but SF yet having to process the queued disconnect event. EXPECT_CALL(*mComposer, getActiveConfig(ExternalDisplay::HWC_DISPLAY_ID, _)) diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp index fc5f2b0b90..eaf468432c 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp @@ -17,66 +17,49 @@ #undef LOG_TAG #define LOG_TAG "LibSurfaceFlingerUnittests" -#include "DisplayTransactionTestHelpers.h" +#include "DualDisplayTransactionTest.h" namespace android { namespace { -class InitializeDisplaysTest : public DisplayTransactionTest {}; +constexpr bool kExpectSetPowerModeOnce = false; +struct InitializeDisplaysTest : DualDisplayTransactionTest<hal::PowerMode::OFF, hal::PowerMode::OFF, + kExpectSetPowerModeOnce> {}; -TEST_F(InitializeDisplaysTest, commitsPrimaryDisplay) { - using Case = SimplePrimaryDisplayCase; - - // -------------------------------------------------------------------- - // Preconditions - - // A primary display is set up - Case::Display::injectHwcDisplay(this); - auto primaryDisplay = Case::Display::makeFakeExistingDisplayInjector(this); - primaryDisplay.inject(); - - // -------------------------------------------------------------------- - // Call Expectations - - // We expect a call to get the active display config. - Case::Display::setupHwcGetActiveConfigCallExpectations(this); - - // We expect a scheduled commit for the display transaction. - EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1); +TEST_F(InitializeDisplaysTest, initializesDisplays) { + // Scheduled by the display transaction, and by powering on each display. + EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(3); EXPECT_CALL(static_cast<mock::VSyncTracker&>( mFlinger.scheduler()->getVsyncSchedule()->getTracker()), - nextAnticipatedVSyncTimeFrom(_)) + nextAnticipatedVSyncTimeFrom(_, _)) .WillRepeatedly(Return(0)); - // -------------------------------------------------------------------- - // Invocation - FTL_FAKE_GUARD(kMainThreadContext, mFlinger.initializeDisplays()); - // -------------------------------------------------------------------- - // Postconditions + for (const auto& display : {mInnerDisplay, mOuterDisplay}) { + const auto token = display->getDisplayToken().promote(); + ASSERT_TRUE(token); + + ASSERT_TRUE(hasCurrentDisplayState(token)); + const auto& state = getCurrentDisplayState(token); - // The primary display should have a current state - ASSERT_TRUE(hasCurrentDisplayState(primaryDisplay.token())); - const auto& primaryDisplayState = getCurrentDisplayState(primaryDisplay.token()); + const ui::LayerStack expectedLayerStack = display == mInnerDisplay + ? ui::DEFAULT_LAYER_STACK + : ui::LayerStack::fromValue(ui::DEFAULT_LAYER_STACK.id + 1); - // The primary display state should be reset - EXPECT_EQ(ui::DEFAULT_LAYER_STACK, primaryDisplayState.layerStack); - EXPECT_EQ(ui::ROTATION_0, primaryDisplayState.orientation); - EXPECT_EQ(Rect::INVALID_RECT, primaryDisplayState.orientedDisplaySpaceRect); - EXPECT_EQ(Rect::INVALID_RECT, primaryDisplayState.layerStackSpaceRect); + EXPECT_EQ(expectedLayerStack, state.layerStack); + EXPECT_EQ(ui::ROTATION_0, state.orientation); + EXPECT_EQ(Rect::INVALID_RECT, state.orientedDisplaySpaceRect); + EXPECT_EQ(Rect::INVALID_RECT, state.layerStackSpaceRect); - // The width and height should both be zero - EXPECT_EQ(0u, primaryDisplayState.width); - EXPECT_EQ(0u, primaryDisplayState.height); + EXPECT_EQ(0u, state.width); + EXPECT_EQ(0u, state.height); - // The display should be set to PowerMode::ON - ASSERT_TRUE(hasDisplayDevice(primaryDisplay.token())); - auto displayDevice = primaryDisplay.mutableDisplayDevice(); - EXPECT_EQ(PowerMode::ON, displayDevice->getPowerMode()); + ASSERT_TRUE(hasDisplayDevice(token)); + EXPECT_EQ(PowerMode::ON, getDisplayDevice(token).getPowerMode()); + } - // The display transaction needed flag should be set. EXPECT_TRUE(hasTransactionFlagSet(eDisplayTransactionNeeded)); } diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp index 7206e2977d..91b901838c 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp @@ -17,31 +17,81 @@ #undef LOG_TAG #define LOG_TAG "LibSurfaceFlingerUnittests" +#include <gui/SurfaceComposerClient.h> #include "DisplayTransactionTestHelpers.h" namespace android { using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; +using android::hardware::graphics::composer::V2_1::Error; class NotifyExpectedPresentTest : public DisplayTransactionTest { public: void SetUp() override { - mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this).inject(); - FakeHwcDisplayInjector(mDisplay->getPhysicalId(), hal::DisplayType::PHYSICAL, kIsPrimary) + const auto display = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this).inject(); + mPhysicalDisplayId = display->getPhysicalId(); + FakeHwcDisplayInjector(mPhysicalDisplayId, hal::DisplayType::PHYSICAL, /*isPrimary=*/true) .setPowerMode(hal::PowerMode::ON) .inject(&mFlinger, mComposer); + + ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(mPhysicalDisplayId, + TimePoint::fromNs(0), + kFps60Hz)); + mCompositor = std::make_unique<Compositor>(mPhysicalDisplayId, mFlinger); } protected: - sp<DisplayDevice> mDisplay; - static constexpr bool kIsPrimary = true; - static constexpr hal::HWDisplayId HWC_DISPLAY_ID = - FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID; -}; + struct Compositor final : ICompositor { + explicit Compositor(PhysicalDisplayId displayId, TestableSurfaceFlinger& surfaceFlinger) + : displayId(displayId), surfaceFlinger(surfaceFlinger) {} -TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentTimeout) { - const auto physicDisplayId = mDisplay->getPhysicalId(); - auto expectedPresentTime = systemTime() + ms2ns(10); + void sendNotifyExpectedPresentHint(PhysicalDisplayId id) override { + surfaceFlinger.sendNotifyExpectedPresentHint(id); + } + + bool commit(PhysicalDisplayId, const scheduler::FrameTargets&) override { + return committed; + } + + CompositeResultsPerDisplay composite(PhysicalDisplayId pacesetterId, + const scheduler::FrameTargeters& targeters) override { + pacesetterIds.composite = pacesetterId; + CompositeResultsPerDisplay results; + + for (const auto& [id, targeter] : targeters) { + vsyncIds.composite.emplace_back(id, targeter->target().vsyncId()); + surfaceFlinger.resetNotifyExpectedPresentHintState(pacesetterId); + results.try_emplace(id, + CompositeResult{.compositionCoverage = + CompositionCoverage::Hwc}); + } + + return results; + } + + void sample() override {} + void configure() override {} + + struct { + PhysicalDisplayId commit; + PhysicalDisplayId composite; + } pacesetterIds; + + using VsyncIds = std::vector<std::pair<PhysicalDisplayId, VsyncId>>; + struct { + VsyncIds commit; + VsyncIds composite; + } vsyncIds; + + bool committed = true; + PhysicalDisplayId displayId; + TestableSurfaceFlinger& surfaceFlinger; + }; + + PhysicalDisplayId mPhysicalDisplayId; + std::unique_ptr<Compositor> mCompositor; + static constexpr hal::HWDisplayId kHwcDisplayId = + FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID; static constexpr Fps kFps60Hz = 60_Hz; static constexpr int32_t kFrameInterval5HzNs = static_cast<Fps>(5_Hz).getPeriodNsecs(); static constexpr int32_t kFrameInterval60HzNs = kFps60Hz.getPeriodNsecs(); @@ -49,89 +99,171 @@ TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentTimeout) { static constexpr Period kVsyncPeriod = Period::fromNs(static_cast<Fps>(240_Hz).getPeriodNsecs()); static constexpr Period kTimeoutNs = Period::fromNs(kFrameInterval5HzNs); - static constexpr auto kLastExpectedPresentTimestamp = TimePoint::fromNs(0); +}; - ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(physicDisplayId, - kLastExpectedPresentTimestamp, - kFps60Hz)); +TEST_F(NotifyExpectedPresentTest, noNotifyExpectedPresentHintCall_absentTimeout) { + auto expectedPresentTime = systemTime() + ms2ns(10); + ASSERT_NO_FATAL_FAILURE( + mFlinger.setNotifyExpectedPresentData(mPhysicalDisplayId, + TimePoint::fromNs(expectedPresentTime), + kFps60Hz)); + EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0); + for (int i = 0; i < 5; i++) { + expectedPresentTime += 2 * kFrameInterval5HzNs; + mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod, + TimePoint::fromNs(expectedPresentTime), kFps60Hz, + /*timeoutOpt*/ std::nullopt); + EXPECT_TRUE( + mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime)); + ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId)); + } +} +TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentHint_zeroTimeout) { + auto expectedPresentTime = systemTime() + ms2ns(10); { - // Very first ExpectedPresent after idle, no previous timestamp + // Very first ExpectedPresent after idle, no previous timestamp. EXPECT_CALL(*mComposer, - notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime, - kFrameInterval60HzNs)) + notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs)) .WillOnce(Return(Error::NONE)); - mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod, + mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod, TimePoint::fromNs(expectedPresentTime), kFps60Hz, kTimeoutNs); + ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId)); + + // Present frame + mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42}); + // Present happens and NotifyExpectedPresentHintStatus is start. + ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId)); } { - // Absent timeoutNs - expectedPresentTime += 2 * kFrameInterval5HzNs; - EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0); - mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod, + mCompositor->committed = false; + expectedPresentTime += kFrameInterval60HzNs; + EXPECT_CALL(*mComposer, + notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs)) + .WillOnce(Return(Error::NONE)); + mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod, TimePoint::fromNs(expectedPresentTime), kFps60Hz, - /*timeoutOpt*/ std::nullopt); + Period::fromNs(0)); + EXPECT_TRUE( + mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime)); + ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId)); + mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42}); + // Hint sent + ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId)); } { - // Timeout is 0 expectedPresentTime += kFrameInterval60HzNs; EXPECT_CALL(*mComposer, - notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime, - kFrameInterval60HzNs)) + notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs)) .WillOnce(Return(Error::NONE)); - mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod, + mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod, TimePoint::fromNs(expectedPresentTime), kFps60Hz, Period::fromNs(0)); + EXPECT_TRUE( + mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime)); + // Hint is executed + ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId)); + mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42}); + ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId)); } +} + +TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentTimeout) { + auto expectedPresentTime = systemTime() + ms2ns(10); { - // ExpectedPresent is after the timeoutNs + // Very first ExpectedPresent after idle, no previous timestamp + mCompositor->committed = false; + EXPECT_CALL(*mComposer, + notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs)) + .WillOnce(Return(Error::NONE)); + mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod, + TimePoint::fromNs(expectedPresentTime), kFps60Hz, + kTimeoutNs); + ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId)); + mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42}); + ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId)); + } + { + // ExpectedPresentTime is after the timeoutNs + mCompositor->committed = true; + expectedPresentTime += 2 * kFrameInterval5HzNs; + EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0); + mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod, + TimePoint::fromNs(expectedPresentTime), kFps60Hz, + kTimeoutNs); + EXPECT_TRUE( + mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime)); + ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId)); + mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42}); + // Present happens notifyExpectedPresentHintStatus is Start + ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId)); + + // Another expectedPresent after timeout expectedPresentTime += 2 * kFrameInterval5HzNs; EXPECT_CALL(*mComposer, - notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime, - kFrameInterval60HzNs)) + notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs)) .WillOnce(Return(Error::NONE)); - mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod, + mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod, TimePoint::fromNs(expectedPresentTime), kFps60Hz, kTimeoutNs); + EXPECT_TRUE( + mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime)); + ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId)); + mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42}); + ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId)); } { // ExpectedPresent has not changed - EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0); - mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod, + EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0); + mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod, TimePoint::fromNs(expectedPresentTime), kFps60Hz, kTimeoutNs); + EXPECT_TRUE( + mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime)); + ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId)); + mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42}); + ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId)); } { - // ExpectedPresent is after the last reported ExpectedPresent. + // ExpectedPresent is after the last reported ExpectedPresent and within timeout. expectedPresentTime += kFrameInterval60HzNs; - EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0); - mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod, + EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0); + mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod, TimePoint::fromNs(expectedPresentTime), kFps60Hz, kTimeoutNs); + EXPECT_TRUE( + mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime)); + ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId)); + mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42}); + ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId)); } { // ExpectedPresent is before the last reported ExpectedPresent but after the timeoutNs, // representing we changed our decision and want to present earlier than previously // reported. + mCompositor->committed = false; expectedPresentTime -= kFrameInterval120HzNs; EXPECT_CALL(*mComposer, - notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime, - kFrameInterval60HzNs)) + notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs)) .WillOnce(Return(Error::NONE)); - mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod, + mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod, TimePoint::fromNs(expectedPresentTime), kFps60Hz, kTimeoutNs); + EXPECT_TRUE( + mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime)); + ASSERT_TRUE(mFlinger.verifyHintIsScheduledOnPresent(mPhysicalDisplayId)); + mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42}); + ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId)); } } TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentRenderRateChanged) { - const auto physicDisplayId = mDisplay->getPhysicalId(); const auto now = systemTime(); auto expectedPresentTime = now; static constexpr Period kTimeoutNs = Period::fromNs(static_cast<Fps>(1_Hz).getPeriodNsecs()); - ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(physicDisplayId, + ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(mPhysicalDisplayId, TimePoint::fromNs(now), Fps::fromValue(0))); static constexpr int32_t kFrameIntervalNs120Hz = static_cast<Fps>(120_Hz).getPeriodNsecs(); @@ -147,7 +279,7 @@ TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentRenderRateChanged) { struct FrameRateIntervalTestData { int32_t frameIntervalNs; - bool callExpectedPresent; + bool callNotifyExpectedPresentHint; }; const std::vector<FrameRateIntervalTestData> frameIntervals = { {kFrameIntervalNs60Hz, true}, {kFrameIntervalNs96Hz, true}, @@ -159,21 +291,35 @@ TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentRenderRateChanged) { {kFrameIntervalNs20Hz, false}, {kFrameIntervalNs120Hz, true}, }; - for (const auto& [frameIntervalNs, callExpectedPresent] : frameIntervals) { - { - expectedPresentTime += frameIntervalNs; - if (callExpectedPresent) { - EXPECT_CALL(*mComposer, - notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime, - frameIntervalNs)) - .WillOnce(Return(Error::NONE)); - } else { - EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0); - } - mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod, - TimePoint::fromNs(expectedPresentTime), - Fps::fromPeriodNsecs(frameIntervalNs), - kTimeoutNs); + for (size_t i = 0; i < frameIntervals.size(); i++) { + const auto& [frameIntervalNs, callNotifyExpectedPresentHint] = frameIntervals[i]; + expectedPresentTime += frameIntervalNs; + mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod, + TimePoint::fromNs(expectedPresentTime), + Fps::fromPeriodNsecs(frameIntervalNs), kTimeoutNs); + + if (callNotifyExpectedPresentHint) { + mCompositor->committed = false; + ASSERT_TRUE(mFlinger.verifyHintIsScheduledOnPresent(mPhysicalDisplayId)) + << "Hint not scheduled for frameInterval " << frameIntervalNs << " at index " + << i; + EXPECT_CALL(*mComposer, + notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, frameIntervalNs)) + .WillOnce(Return(Error::NONE)); + } else { + // Only lastExpectedPresentTime is updated + EXPECT_TRUE( + mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime)) + << "LastExpectedPresentTime for frameInterval " << frameIntervalNs + << "at index " << i << " did not match for frameInterval " << frameIntervalNs; + EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0); + } + mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42}); + + if (callNotifyExpectedPresentHint) { + // Present resumes the calls to the notifyExpectedPresentHint. + mCompositor->committed = true; + mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42}); } } } diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp index b80cb6625f..c3934e64f8 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp @@ -17,84 +17,18 @@ #undef LOG_TAG #define LOG_TAG "SurfaceFlingerPowerHintTest" -#include <compositionengine/Display.h> -#include <compositionengine/mock/DisplaySurface.h> -#include <gmock/gmock.h> -#include <gtest/gtest.h> -#include <renderengine/mock/RenderEngine.h> -#include <algorithm> #include <chrono> -#include <memory> -#include "TestableSurfaceFlinger.h" -#include "mock/DisplayHardware/MockComposer.h" -#include "mock/DisplayHardware/MockPowerAdvisor.h" -#include "mock/MockTimeStats.h" -#include "mock/system/window/MockNativeWindow.h" -using namespace android; -using namespace android::Hwc2::mock; -using namespace android::hardware::power; +#include "CommitAndCompositeTest.h" + using namespace std::chrono_literals; -using namespace testing; +using testing::_; +using testing::Return; namespace android { namespace { -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; -using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector; - -constexpr hal::HWDisplayId HWC_DISPLAY = FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID; -constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u); -constexpr int DEFAULT_DISPLAY_WIDTH = 1920; -constexpr int DEFAULT_DISPLAY_HEIGHT = 1024; - -class SurfaceFlingerPowerHintTest : public Test { -public: - void SetUp() override; -protected: - TestableSurfaceFlinger mFlinger; - renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine(); - sp<DisplayDevice> mDisplay; - sp<compositionengine::mock::DisplaySurface> mDisplaySurface = - sp<compositionengine::mock::DisplaySurface>::make(); - sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make(); - mock::TimeStats* mTimeStats = new mock::TimeStats(); - Hwc2::mock::PowerAdvisor* mPowerAdvisor = nullptr; - Hwc2::mock::Composer* mComposer = nullptr; -}; - -void SurfaceFlingerPowerHintTest::SetUp() { - mFlinger.setupMockScheduler({.displayId = DEFAULT_DISPLAY_ID}); - mComposer = new Hwc2::mock::Composer(); - mPowerAdvisor = new Hwc2::mock::PowerAdvisor(); - mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine)); - mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats)); - mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer)); - mFlinger.setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor>(mPowerAdvisor)); - static constexpr bool kIsPrimary = true; - FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, hal::DisplayType::PHYSICAL, kIsPrimary) - .setPowerMode(hal::PowerMode::ON) - .inject(&mFlinger, mComposer); - auto compostionEngineDisplayArgs = - compositionengine::DisplayCreationArgsBuilder() - .setId(DEFAULT_DISPLAY_ID) - .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT}) - .setPowerAdvisor(mPowerAdvisor) - .setName("injected display") - .build(); - auto compositionDisplay = - compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(), - std::move(compostionEngineDisplayArgs)); - mDisplay = - FakeDisplayDeviceInjector(mFlinger, compositionDisplay, - ui::DisplayConnectionType::Internal, HWC_DISPLAY, kIsPrimary) - .setDisplaySurface(mDisplaySurface) - .setNativeWindow(mNativeWindow) - .setPowerMode(hal::PowerMode::ON) - .setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector()) - .skipRegisterDisplay() - .inject(); -} +class SurfaceFlingerPowerHintTest : public CommitAndCompositeTest {}; TEST_F(SurfaceFlingerPowerHintTest, sendDurationsIncludingHwcWaitTime) { ON_CALL(*mPowerAdvisor, usePowerHintSession()).WillByDefault(Return(true)); diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.cpp b/services/surfaceflinger/tests/unittests/TestableScheduler.cpp new file mode 100644 index 0000000000..7b92a5bb3b --- /dev/null +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.cpp @@ -0,0 +1,30 @@ +/* + * 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 "TestableScheduler.h" +#include "TestableSurfaceFlinger.h" + +namespace android::scheduler { + +TestableScheduler::TestableScheduler(RefreshRateSelectorPtr selectorPtr, + TestableSurfaceFlinger& testableSurfaceFlinger, + ISchedulerCallback& callback) + : TestableScheduler(std::make_unique<android::mock::VsyncController>(), + std::make_shared<android::mock::VSyncTracker>(), std::move(selectorPtr), + testableSurfaceFlinger.getFactory(), + testableSurfaceFlinger.getTimeStats(), callback) {} + +} // namespace android::scheduler diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index 2a1b88e6fa..25a85dfa20 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -32,25 +32,25 @@ #include "mock/MockVSyncTracker.h" #include "mock/MockVsyncController.h" +namespace android { +class TestableSurfaceFlinger; +} // namespace android + namespace android::scheduler { class TestableScheduler : public Scheduler, private ICompositor { public: - TestableScheduler(RefreshRateSelectorPtr selectorPtr, ISchedulerCallback& callback, - IVsyncTrackerCallback& vsyncTrackerCallback) - : TestableScheduler(std::make_unique<mock::VsyncController>(), - std::make_shared<mock::VSyncTracker>(), std::move(selectorPtr), - sp<VsyncModulator>::make(VsyncConfigSet{}), callback, - vsyncTrackerCallback) {} + TestableScheduler(RefreshRateSelectorPtr selectorPtr, + TestableSurfaceFlinger& testableSurfaceFlinger, ISchedulerCallback& callback); TestableScheduler(std::unique_ptr<VsyncController> controller, std::shared_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr, - sp<VsyncModulator> modulatorPtr, ISchedulerCallback& schedulerCallback, - IVsyncTrackerCallback& vsyncTrackerCallback) + surfaceflinger::Factory& factory, TimeStats& timeStats, + ISchedulerCallback& schedulerCallback) : Scheduler(*this, schedulerCallback, (FeatureFlags)Feature::kContentDetection | Feature::kSmallDirtyContentDetection, - std::move(modulatorPtr), vsyncTrackerCallback) { + factory, selectorPtr->getActiveMode().fps, timeStats) { const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId(); registerDisplay(displayId, std::move(selectorPtr), std::move(controller), std::move(tracker)); @@ -208,6 +208,7 @@ private: return {}; } void sample() override {} + void sendNotifyExpectedPresentHint(PhysicalDisplayId) override {} }; } // namespace android::scheduler diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 22cb24bb6f..46a079cfa1 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -53,7 +53,6 @@ #include "mock/MockFrameTimeline.h" #include "mock/MockFrameTracer.h" #include "mock/MockSchedulerCallback.h" -#include "mock/MockVsyncTrackerCallback.h" #include "mock/system/window/MockNativeWindow.h" #include "Scheduler/VSyncTracker.h" @@ -205,8 +204,6 @@ public: enum class SchedulerCallbackImpl { kNoOp, kMock }; - enum class VsyncTrackerCallbackImpl { kNoOp, kMock }; - struct DefaultDisplayMode { // The ID of the injected RefreshRateSelector and its default display mode. PhysicalDisplayId displayId; @@ -216,14 +213,17 @@ public: using DisplayModesVariant = std::variant<DefaultDisplayMode, RefreshRateSelectorPtr>; - void setupScheduler( - std::unique_ptr<scheduler::VsyncController> vsyncController, - std::shared_ptr<scheduler::VSyncTracker> vsyncTracker, - std::unique_ptr<EventThread> appEventThread, std::unique_ptr<EventThread> sfEventThread, - DisplayModesVariant modesVariant, - SchedulerCallbackImpl callbackImpl = SchedulerCallbackImpl::kNoOp, - VsyncTrackerCallbackImpl vsyncTrackerCallbackImpl = VsyncTrackerCallbackImpl::kNoOp, - bool useNiceMock = false) { + surfaceflinger::Factory& getFactory() { return mFactory; } + + TimeStats& getTimeStats() { return *mFlinger->mTimeStats; } + + void setupScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController, + std::shared_ptr<scheduler::VSyncTracker> vsyncTracker, + std::unique_ptr<EventThread> appEventThread, + std::unique_ptr<EventThread> sfEventThread, + DisplayModesVariant modesVariant, + SchedulerCallbackImpl callbackImpl = SchedulerCallbackImpl::kNoOp, + bool useNiceMock = false) { RefreshRateSelectorPtr selectorPtr = ftl::match( modesVariant, [](DefaultDisplayMode arg) { @@ -234,13 +234,6 @@ public: }, [](RefreshRateSelectorPtr selectorPtr) { return selectorPtr; }); - const auto fps = selectorPtr->getActiveMode().fps; - mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps); - - mFlinger->mRefreshRateStats = - std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mTimeStats, fps, - hal::PowerMode::OFF); - mTokenManager = std::make_unique<frametimeline::impl::TokenManager>(); using ISchedulerCallback = scheduler::ISchedulerCallback; @@ -248,32 +241,22 @@ public: ? static_cast<ISchedulerCallback&>(mNoOpSchedulerCallback) : static_cast<ISchedulerCallback&>(mSchedulerCallback); - using VsyncTrackerCallback = scheduler::IVsyncTrackerCallback; - VsyncTrackerCallback& vsyncTrackerCallback = - vsyncTrackerCallbackImpl == VsyncTrackerCallbackImpl::kNoOp - ? static_cast<VsyncTrackerCallback&>(mNoOpVsyncTrackerCallback) - : static_cast<VsyncTrackerCallback&>(mVsyncTrackerCallback); - - auto modulatorPtr = sp<scheduler::VsyncModulator>::make( - mFlinger->mVsyncConfiguration->getCurrentConfigs()); - if (useNiceMock) { mScheduler = new testing::NiceMock<scheduler::TestableScheduler>(std::move(vsyncController), std::move(vsyncTracker), std::move(selectorPtr), - std::move(modulatorPtr), - schedulerCallback, - vsyncTrackerCallback); + mFactory, + *mFlinger->mTimeStats, + schedulerCallback); } else { mScheduler = new scheduler::TestableScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(selectorPtr), - std::move(modulatorPtr), - schedulerCallback, vsyncTrackerCallback); + std::move(selectorPtr), mFactory, + *mFlinger->mTimeStats, schedulerCallback); } - mScheduler->initVsync(mScheduler->getVsyncSchedule()->getDispatch(), *mTokenManager, 0ms); + mScheduler->initVsync(*mTokenManager, 0ms); mScheduler->mutableAppConnectionHandle() = mScheduler->createConnection(std::move(appEventThread)); @@ -303,17 +286,16 @@ public: auto vsyncController = makeMock<mock::VsyncController>(options.useNiceMock); auto vsyncTracker = makeSharedMock<mock::VSyncTracker>(options.useNiceMock); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); + EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_, _)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); EXPECT_CALL(*vsyncTracker, minFramePeriod()) .WillRepeatedly( Return(Period::fromNs(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD))); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); + 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, VsyncTrackerCallbackImpl::kNoOp, - options.useNiceMock); + SchedulerCallbackImpl::kNoOp, options.useNiceMock); } void resetScheduler(scheduler::Scheduler* scheduler) { mFlinger->mScheduler.reset(scheduler); } @@ -391,13 +373,14 @@ public: LOG_ALWAYS_FATAL_IF(!displayIdOpt); const auto displayId = *displayIdOpt; - constexpr bool kBackpressureGpuComposition = true; - scheduler::FrameTargeter frameTargeter(displayId, kBackpressureGpuComposition); + scheduler::FrameTargeter frameTargeter(displayId, + scheduler::Feature::kBackpressureGpuComposition); frameTargeter.beginFrame({.frameBeginTime = frameTime, .vsyncId = vsyncId, .expectedVsyncTime = expectedVsyncTime, - .sfWorkDuration = 10ms}, + .sfWorkDuration = 10ms, + .hwcMinWorkDuration = 10ms}, *mScheduler->getVsyncSchedule()); scheduler::FrameTargets targets; @@ -702,6 +685,36 @@ public: frameInterval, timeoutOpt); } + void sendNotifyExpectedPresentHint(PhysicalDisplayId displayId) { + ftl::FakeGuard guard(kMainThreadContext); + mFlinger->sendNotifyExpectedPresentHint(displayId); + } + + bool verifyHintIsScheduledOnPresent(PhysicalDisplayId displayId) { + return mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus == + SurfaceFlinger::NotifyExpectedPresentHintStatus::ScheduleOnPresent; + } + + bool verifyHintIsSent(PhysicalDisplayId displayId) { + return mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus == + SurfaceFlinger::NotifyExpectedPresentHintStatus::Sent; + } + + bool verifyHintStatusIsStart(PhysicalDisplayId displayId) { + return mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus == + SurfaceFlinger::NotifyExpectedPresentHintStatus::Start; + } + + bool verifyHintStatusIsScheduledOnTx(PhysicalDisplayId displayId) { + return mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus == + SurfaceFlinger::NotifyExpectedPresentHintStatus::ScheduleOnTx; + } + + bool verifyLastExpectedPresentTime(PhysicalDisplayId displayId, nsecs_t expectedPresentTime) { + return mFlinger->mNotifyExpectedPresentMap.at(displayId) + .lastExpectedPresentTimestamp.ns() == expectedPresentTime; + } + void setNotifyExpectedPresentData(PhysicalDisplayId displayId, TimePoint lastExpectedPresentTimestamp, Fps lastFrameInterval) { @@ -710,6 +723,11 @@ public: displayData.lastFrameInterval = lastFrameInterval; } + void resetNotifyExpectedPresentHintState(PhysicalDisplayId displayId) { + mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus = + SurfaceFlinger::NotifyExpectedPresentHintStatus::Start; + } + ~TestableSurfaceFlinger() { // All these pointer and container clears help ensure that GMock does // not report a leaked object, since the SurfaceFlinger instance may @@ -754,7 +772,6 @@ public: static constexpr int32_t DEFAULT_CONFIG_GROUP = 7; static constexpr int32_t DEFAULT_DPI = 320; static constexpr hal::HWConfigId DEFAULT_ACTIVE_CONFIG = 0; - static constexpr hal::PowerMode DEFAULT_POWER_MODE = hal::PowerMode::ON; FakeHwcDisplayInjector(HalDisplayId displayId, hal::DisplayType hwcDisplayType, bool isPrimary) @@ -797,7 +814,7 @@ public: return *this; } - auto& setPowerMode(std::optional<hal::PowerMode> mode) { + auto& setPowerMode(hal::PowerMode mode) { mPowerMode = mode; return *this; } @@ -821,9 +838,7 @@ public: mHwcDisplayType); display->mutableIsConnected() = true; - if (mPowerMode) { - display->setPowerMode(*mPowerMode); - } + display->setPowerMode(mPowerMode); flinger->mutableHwcDisplayData()[mDisplayId].hwcDisplay = std::move(display); @@ -889,7 +904,7 @@ public: int32_t mDpiY = DEFAULT_DPI; int32_t mConfigGroup = DEFAULT_CONFIG_GROUP; hal::HWConfigId mActiveConfig = DEFAULT_ACTIVE_CONFIG; - std::optional<hal::PowerMode> mPowerMode = DEFAULT_POWER_MODE; + hal::PowerMode mPowerMode = hal::PowerMode::ON; const std::unordered_set<aidl::android::hardware::graphics::composer3::Capability>* mCapabilities = nullptr; }; @@ -966,7 +981,7 @@ public: return *this; } - auto& setPowerMode(std::optional<hal::PowerMode> mode) { + auto& setPowerMode(hal::PowerMode mode) { mCreationArgs.initialPowerMode = mode; return *this; } @@ -1106,8 +1121,6 @@ private: sp<SurfaceFlinger> mFlinger; scheduler::mock::SchedulerCallback mSchedulerCallback; scheduler::mock::NoOpSchedulerCallback mNoOpSchedulerCallback; - scheduler::mock::VsyncTrackerCallback mVsyncTrackerCallback; - scheduler::mock::NoOpVsyncTrackerCallback mNoOpVsyncTrackerCallback; std::unique_ptr<frametimeline::impl::TokenManager> mTokenManager; scheduler::TestableScheduler* mScheduler = nullptr; Hwc2::mock::PowerAdvisor mPowerAdvisor; diff --git a/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp index 4a83d445fc..d071ce985f 100644 --- a/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp @@ -105,4 +105,16 @@ TEST_F(TransactionTraceWriterTest, overwriteOldFile) { verifyTraceFile(); } +// Check we cannot write to file if the trace write is disabled. +TEST_F(TransactionTraceWriterTest, canDisableTraceWriter) { + TransactionTraceWriter::getInstance().disable(); + TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ true); + EXPECT_NE(access(mFilename.c_str(), F_OK), 0); + + TransactionTraceWriter::getInstance().enable(); + TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ true); + EXPECT_EQ(access(mFilename.c_str(), F_OK), 0); + verifyTraceFile(); +} + } // namespace android
\ No newline at end of file diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp index 6a5635305a..d891008683 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp @@ -64,7 +64,7 @@ class FixedRateIdealStubTracker : public StubTracker { public: FixedRateIdealStubTracker() : StubTracker{toNs(3ms)} {} - nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final { + nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, std::optional<nsecs_t>) const final { auto const floor = timePoint % mPeriod; if (floor == 0) { return timePoint; @@ -77,7 +77,7 @@ class VRRStubTracker : public StubTracker { public: VRRStubTracker(nsecs_t period) : StubTracker(period) {} - nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point) const final { + nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point, std::optional<nsecs_t>) const final { std::lock_guard lock(mMutex); auto const normalized_to_base = time_point - mBase; auto const floor = (normalized_to_base) % mPeriod; @@ -117,7 +117,7 @@ public: mCallback.schedule( {.workDuration = mWorkload, .readyDuration = mReadyDuration, - .earliestVsync = systemTime(SYSTEM_TIME_MONOTONIC) + mWorkload + mReadyDuration}); + .lastVsync = systemTime(SYSTEM_TIME_MONOTONIC) + mWorkload + mReadyDuration}); for (auto i = 0u; i < iterations - 1; i++) { std::unique_lock lock(mMutex); @@ -130,7 +130,7 @@ public: mCallback.schedule({.workDuration = mWorkload, .readyDuration = mReadyDuration, - .earliestVsync = last + mWorkload + mReadyDuration}); + .lastVsync = last + mWorkload + mReadyDuration}); } // wait for the last callback. diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp index 2047018a15..eb4e84ef4f 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp @@ -46,14 +46,14 @@ using namespace com::android::graphics::surfaceflinger; class MockVSyncTracker : public mock::VSyncTracker { public: MockVSyncTracker(nsecs_t period) : mPeriod{period} { - ON_CALL(*this, nextAnticipatedVSyncTimeFrom(_)) + ON_CALL(*this, nextAnticipatedVSyncTimeFrom(_, _)) .WillByDefault(Invoke(this, &MockVSyncTracker::nextVSyncTime)); ON_CALL(*this, addVsyncTimestamp(_)).WillByDefault(Return(true)); ON_CALL(*this, currentPeriod()) .WillByDefault(Invoke(this, &MockVSyncTracker::getCurrentPeriod)); } - nsecs_t nextVSyncTime(nsecs_t timePoint) const { + nsecs_t nextVSyncTime(nsecs_t timePoint, std::optional<nsecs_t>) const { if (timePoint % mPeriod == 0) { return timePoint; } @@ -243,10 +243,9 @@ TEST_F(VSyncDispatchTimerQueueTest, unregistersSetAlarmOnDestruction) { 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, .lastVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); } @@ -257,10 +256,9 @@ 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, .lastVsync = intended}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); @@ -277,16 +275,14 @@ 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, .lastVsync = intended}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); result = - mDispatch->update(cb, - {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended}); + mDispatch->update(cb, {.workDuration = 300, .readyDuration = 0, .lastVsync = intended}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(700, *result); @@ -303,17 +299,18 @@ 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, .lastVsync = intended}); EXPECT_FALSE(result.has_value()); } TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFutureWithAdjustmentToTrueVsync) { - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)).WillOnce(Return(1150)); + EXPECT_CALL(*mStubTracker.get(), + nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(mPeriod))) + .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, .lastVsync = mPeriod}); advanceToNextCallback(); ASSERT_THAT(cb.mCalls.size(), Eq(1)); @@ -324,7 +321,8 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingAdjustmentPast) { auto const now = 234; mMockClock.advanceBy(234); auto const workDuration = 10 * mPeriod; - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(now + workDuration)) + EXPECT_CALL(*mStubTracker.get(), + nextAnticipatedVSyncTimeFrom(now + workDuration, std::optional<nsecs_t>(mPeriod))) .WillOnce(Return(mPeriod * 11)); EXPECT_CALL(mMockClock, alarmAt(_, mPeriod)); @@ -332,7 +330,7 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingAdjustmentPast) { const auto result = mDispatch->schedule(cb, {.workDuration = workDuration, .readyDuration = 0, - .earliestVsync = mPeriod}); + .lastVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod, *result); } @@ -342,10 +340,9 @@ 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, .lastVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod - 100, *result); EXPECT_EQ(mDispatch->cancel(cb), CancelResult::Cancelled); @@ -356,10 +353,9 @@ 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, .lastVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod - 100, *result); mMockClock.advanceBy(950); @@ -371,10 +367,9 @@ 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, .lastVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod - 100, *result); @@ -393,10 +388,9 @@ 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, .lastVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod - 100, *result); @@ -413,7 +407,8 @@ TEST_F(VSyncDispatchTimerQueueTest, unregisterSynchronizes) { } TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) { - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)) + EXPECT_CALL(*mStubTracker.get(), + nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000))) .Times(4) .WillOnce(Return(1055)) .WillOnce(Return(1063)) @@ -428,8 +423,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, .lastVsync = mPeriod}); + mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .lastVsync = mPeriod}); advanceToNextCallback(); advanceToNextCallback(); @@ -441,7 +436,7 @@ TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) { } TEST_F(VSyncDispatchTimerQueueTest, noCloseCallbacksAfterPeriodChange) { - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _)) .Times(4) .WillOnce(Return(1000)) .WillOnce(Return(2000)) @@ -455,21 +450,21 @@ TEST_F(VSyncDispatchTimerQueueTest, noCloseCallbacksAfterPeriodChange) { CountingCallback cb(mDispatch); - mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 0}); + mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 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, .lastVsync = 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, .lastVsync = 2000}); advanceToNextCallback(); @@ -478,7 +473,7 @@ TEST_F(VSyncDispatchTimerQueueTest, noCloseCallbacksAfterPeriodChange) { } TEST_F(VSyncDispatchTimerQueueTest, rearmsFaroutTimeoutWhenCancellingCloseOne) { - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _)) .Times(4) .WillOnce(Return(10000)) .WillOnce(Return(1000)) @@ -493,9 +488,8 @@ 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->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod * 10}); + mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .lastVsync = mPeriod}); mDispatch->cancel(cb1); } @@ -507,9 +501,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, .lastVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 300, .readyDuration = 0, .lastVsync = 1000}); advanceToNextCallback(); } @@ -522,9 +516,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, .lastVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000}); advanceToNextCallback(); } @@ -542,10 +536,9 @@ 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, .lastVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = closeOffset, .readyDuration = 0, .lastVsync = 1000}); advanceToNextCallback(); ASSERT_THAT(cb0.mCalls.size(), Eq(1)); @@ -553,11 +546,9 @@ 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(cb0, {.workDuration = 400, .readyDuration = 0, .lastVsync = 2000}); mDispatch->schedule(cb1, - {.workDuration = notCloseOffset, - .readyDuration = 0, - .earliestVsync = 2000}); + {.workDuration = notCloseOffset, .readyDuration = 0, .lastVsync = 2000}); advanceToNextCallback(); ASSERT_THAT(cb1.mCalls.size(), Eq(2)); EXPECT_THAT(cb1.mCalls[1], Eq(2000)); @@ -577,32 +568,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, .lastVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000}); advanceToNextCallback(); EXPECT_EQ(mDispatch->cancel(cb0), CancelResult::Cancelled); } TEST_F(VSyncDispatchTimerQueueTest, setAlarmCallsAtCorrectTimeWithChangingVsync) { - EXPECT_CALL(*mStubTracker.get(), 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, .lastVsync = 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, .lastVsync = 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, .lastVsync = 2900}); mMockClock.advanceBy(975); EXPECT_THAT(cb.mCalls.size(), Eq(3)); } @@ -616,13 +607,11 @@ TEST_F(VSyncDispatchTimerQueueTest, callbackReentrancy) { tmp = mDispatch->registerCallback( [&](auto, auto, auto) { mDispatch->schedule(tmp, - {.workDuration = 100, - .readyDuration = 0, - .earliestVsync = 2000}); + {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000}); }, "o.o"); - mDispatch->schedule(tmp, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(tmp, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000}); advanceToNextCallback(); } @@ -631,30 +620,29 @@ TEST_F(VSyncDispatchTimerQueueTest, callbackReentrantWithPastWakeup) { std::optional<nsecs_t> lastTarget; tmp = mDispatch->registerCallback( [&](auto timestamp, auto, auto) { - auto result = - mDispatch->schedule(tmp, - {.workDuration = 400, - .readyDuration = 0, - .earliestVsync = timestamp - mVsyncMoveThreshold}); + auto result = mDispatch->schedule(tmp, + {.workDuration = 400, + .readyDuration = 0, + .lastVsync = timestamp - mVsyncMoveThreshold}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod + timestamp - 400, *result); result = mDispatch->schedule(tmp, {.workDuration = 400, .readyDuration = 0, - .earliestVsync = timestamp}); + .lastVsync = timestamp}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod + timestamp - 400, *result); result = mDispatch->schedule(tmp, {.workDuration = 400, .readyDuration = 0, - .earliestVsync = timestamp + mVsyncMoveThreshold}); + .lastVsync = 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, .lastVsync = 1000}); advanceToNextCallback(); EXPECT_THAT(lastTarget, Eq(1000)); @@ -670,16 +658,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, .lastVsync = 1000}); mMockClock.advanceBy(750); - mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .lastVsync = 1000}); advanceToNextCallback(); - mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .lastVsync = 2000}); mMockClock.advanceBy(800); - mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000}); } TEST_F(VSyncDispatchTimerQueueTest, lateModifications) { @@ -692,12 +680,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, .lastVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 100, .readyDuration = 0, .lastVsync = 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, .lastVsync = 2000}); + mDispatch->schedule(cb1, {.workDuration = 150, .readyDuration = 0, .lastVsync = 1000}); advanceToNextCallback(); advanceToNextCallback(); @@ -709,8 +697,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, .lastVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .lastVsync = 20000}); } TEST_F(VSyncDispatchTimerQueueTest, setsTimerAfterCancellation) { @@ -720,17 +708,15 @@ TEST_F(VSyncDispatchTimerQueueTest, setsTimerAfterCancellation) { EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq); CountingCallback cb0(mDispatch); - mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000}); mDispatch->cancel(cb0); - mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000}); } TEST_F(VSyncDispatchTimerQueueTest, makingUpIdsError) { VSyncDispatch::CallbackToken token(100); EXPECT_FALSE( - mDispatch - ->schedule(token, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}) + mDispatch->schedule(token, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000}) .has_value()); EXPECT_THAT(mDispatch->cancel(token), Eq(CancelResult::Error)); } @@ -738,31 +724,27 @@ TEST_F(VSyncDispatchTimerQueueTest, makingUpIdsError) { 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, .lastVsync = 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, .lastVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); } // b/1450138150 TEST_F(VSyncDispatchTimerQueueTest, doesNotMoveCallbackBackwardsAndSkipAScheduledTargetVSync) { - SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false); + SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, false); 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, .lastVsync = 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, .lastVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1200, *result); @@ -772,21 +754,19 @@ TEST_F(VSyncDispatchTimerQueueTest, doesNotMoveCallbackBackwardsAndSkipASchedule // b/1450138150 TEST_F(VSyncDispatchTimerQueueTest, movesCallbackBackwardsAndSkipAScheduledTargetVSync) { - SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true); + SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, true); Sequence seq; EXPECT_CALL(mMockClock, alarmAt(_, 500)).InSequence(seq); EXPECT_CALL(mMockClock, alarmAt(_, 400)).InSequence(seq); CountingCallback cb(mDispatch); auto result = - mDispatch->schedule(cb, - {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 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, .lastVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(400, *result); @@ -795,19 +775,18 @@ TEST_F(VSyncDispatchTimerQueueTest, movesCallbackBackwardsAndSkipAScheduledTarge } TEST_F(VSyncDispatchTimerQueueTest, targetOffsetMovingBackALittleCanStillSchedule) { - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)) + EXPECT_CALL(*mStubTracker.get(), + nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(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, .lastVsync = 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, .lastVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(602, *result); } @@ -815,13 +794,12 @@ 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, .lastVsync = 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, .lastVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); } @@ -832,31 +810,28 @@ 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, .lastVsync = 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, .lastVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1100, *result); } TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesNotAffectSchedulingState) { - SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false); + SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, false); EXPECT_CALL(mMockClock, alarmAt(_, 600)); CountingCallback cb(mDispatch); auto result = - mDispatch->schedule(cb, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 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, .lastVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); @@ -864,7 +839,7 @@ TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesNotAffectSchedulingState) } TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesAffectSchedulingState) { - SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true); + SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, true); Sequence seq; EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq); @@ -872,13 +847,11 @@ TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesAffectSchedulingState) { CountingCallback cb(mDispatch); auto result = - mDispatch->schedule(cb, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 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, .lastVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(0, *result); @@ -892,10 +865,10 @@ TEST_F(VSyncDispatchTimerQueueTest, helperMove) { VSyncCallbackRegistration cb( mDispatch, [](auto, auto, auto) {}, ""); VSyncCallbackRegistration cb1(std::move(cb)); - cb.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + cb.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 1000}); cb.cancel(); - cb1.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + cb1.schedule({.workDuration = 500, .readyDuration = 0, .lastVsync = 1000}); cb1.cancel(); } @@ -908,10 +881,10 @@ TEST_F(VSyncDispatchTimerQueueTest, helperMoveAssign) { VSyncCallbackRegistration cb1( mDispatch, [](auto, auto, auto) {}, ""); cb1 = std::move(cb); - cb.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + cb.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 1000}); cb.cancel(); - cb1.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + cb1.schedule({.workDuration = 500, .readyDuration = 0, .lastVsync = 1000}); cb1.cancel(); } @@ -924,16 +897,14 @@ 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, .lastVsync = 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, .lastVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1900, *result); mMockClock.advanceBy(80); @@ -946,22 +917,22 @@ TEST_F(VSyncDispatchTimerQueueTest, skipsSchedulingIfTimerReschedulingIsImminent // If the same callback tries to reschedule itself after it's too late, timer opts to apply the // update later, as opposed to blocking the calling thread. TEST_F(VSyncDispatchTimerQueueTest, skipsSchedulingIfTimerReschedulingIsImminentSameCallback) { + SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, false); + Sequence seq; EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq); EXPECT_CALL(mMockClock, alarmAt(_, 1630)).InSequence(seq); CountingCallback cb(mDispatch); auto result = - mDispatch->schedule(cb, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 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, .lastVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1630, *result); mMockClock.advanceBy(80); @@ -970,6 +941,37 @@ TEST_F(VSyncDispatchTimerQueueTest, skipsSchedulingIfTimerReschedulingIsImminent } // b/154303580. +// If the same callback tries to reschedule itself after it's too late, timer opts to apply the +// update later, as opposed to blocking the calling thread. +TEST_F(VSyncDispatchTimerQueueTest, doesntSkipSchedulingIfTimerReschedulingIsImminentSameCallback) { + SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, true); + + Sequence seq; + EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq); + EXPECT_CALL(mMockClock, alarmAt(_, 1630)).InSequence(seq); + CountingCallback cb(mDispatch); + + auto result = + mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000}); + EXPECT_TRUE(result.has_value()); + EXPECT_EQ(600, *result); + + mMockClock.setLag(100); + mMockClock.advanceBy(620); + + result = mDispatch->schedule(cb, {.workDuration = 370, .readyDuration = 0, .lastVsync = 2000}); + EXPECT_TRUE(result.has_value()); + EXPECT_EQ(600, *result); + mMockClock.advanceBy(80); + + ASSERT_EQ(1, cb.mCalls.size()); + EXPECT_EQ(1000, cb.mCalls[0]); + + ASSERT_EQ(1, cb.mWakeupTime.size()); + EXPECT_EQ(600, cb.mWakeupTime[0]); +} + +// b/154303580. TEST_F(VSyncDispatchTimerQueueTest, skipsRearmingWhenNotNextScheduled) { Sequence seq; EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq); @@ -978,12 +980,10 @@ 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, .lastVsync = 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, .lastVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1900, *result); @@ -1007,12 +1007,10 @@ 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, .lastVsync = 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, .lastVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1900, *result); @@ -1034,21 +1032,21 @@ TEST_F(VSyncDispatchTimerQueueTest, laggedTimerGroupsCallbacksWithinLag) { CountingCallback cb2(mDispatch); Sequence seq; - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)) + EXPECT_CALL(*mStubTracker.get(), + nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000))) .InSequence(seq) .WillOnce(Return(1000)); EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq); - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)) + EXPECT_CALL(*mStubTracker.get(), + nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000))) .InSequence(seq) .WillOnce(Return(1000)); auto result = - mDispatch->schedule(cb1, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 400, .readyDuration = 0, .lastVsync = 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, .lastVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(610, *result); @@ -1070,10 +1068,9 @@ 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, .lastVsync = intended}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); advanceToNextCallback(); @@ -1087,15 +1084,15 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFutureWithReadyDuration) { } TEST_F(VSyncDispatchTimerQueueTest, updatesVsyncTimeForCloseWakeupTime) { - SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false); + SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, false); Sequence seq; EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq); 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, .lastVsync = 1000}); + mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .lastVsync = 1000}); advanceToNextCallback(); @@ -1110,7 +1107,7 @@ TEST_F(VSyncDispatchTimerQueueTest, updatesVsyncTimeForCloseWakeupTime) { } TEST_F(VSyncDispatchTimerQueueTest, doesNotUpdatesVsyncTimeForCloseWakeupTime) { - SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true); + SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, true); Sequence seq; EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq); @@ -1118,8 +1115,8 @@ TEST_F(VSyncDispatchTimerQueueTest, doesNotUpdatesVsyncTimeForCloseWakeupTime) { 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, .lastVsync = 1000}); + mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .lastVsync = 1000}); advanceToNextCallback(); @@ -1134,19 +1131,17 @@ TEST_F(VSyncDispatchTimerQueueTest, doesNotUpdatesVsyncTimeForCloseWakeupTime) { } TEST_F(VSyncDispatchTimerQueueTest, skipAVsyc) { - SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false); + SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, false); 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, .lastVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(500, *result); mMockClock.advanceBy(300); - result = mDispatch->schedule(cb, - {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000}); + result = mDispatch->schedule(cb, {.workDuration = 800, .readyDuration = 0, .lastVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1200, *result); @@ -1155,21 +1150,19 @@ TEST_F(VSyncDispatchTimerQueueTest, skipAVsyc) { } TEST_F(VSyncDispatchTimerQueueTest, dontskipAVsyc) { - SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true); + SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, true); Sequence seq; EXPECT_CALL(mMockClock, alarmAt(_, 500)).InSequence(seq); EXPECT_CALL(mMockClock, alarmAt(_, 300)).InSequence(seq); CountingCallback cb(mDispatch); auto result = - mDispatch->schedule(cb, - {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(500, *result); mMockClock.advanceBy(300); - result = mDispatch->schedule(cb, - {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000}); + result = mDispatch->schedule(cb, {.workDuration = 800, .readyDuration = 0, .lastVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(300, *result); @@ -1204,7 +1197,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, stateScheduling) { "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); EXPECT_FALSE(entry.wakeupTime()); - EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, + EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500}, *mStubTracker.get(), 0) .has_value()); auto const wakeup = entry.wakeupTime(); @@ -1219,14 +1212,15 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, stateSchedulingReallyLongWakeupLatency) auto const duration = 500; auto const now = 8750; - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(now + duration)) + EXPECT_CALL(*mStubTracker.get(), + nextAnticipatedVSyncTimeFrom(now + duration, std::optional<nsecs_t>(994))) .Times(1) .WillOnce(Return(10000)); VSyncDispatchTimerQueueEntry entry( "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); EXPECT_FALSE(entry.wakeupTime()); - EXPECT_TRUE(entry.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 994}, + EXPECT_TRUE(entry.schedule({.workDuration = 500, .readyDuration = 0, .lastVsync = 994}, *mStubTracker.get(), now) .has_value()); auto const wakeup = entry.wakeupTime(); @@ -1249,7 +1243,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, runCallback) { }, mVsyncMoveThreshold); - EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, + EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500}, *mStubTracker.get(), 0) .has_value()); auto const wakeup = entry.wakeupTime(); @@ -1272,7 +1266,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, runCallback) { } TEST_F(VSyncDispatchTimerQueueEntryTest, updateCallback) { - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _)) .Times(2) .WillOnce(Return(1000)) .WillOnce(Return(1020)); @@ -1284,7 +1278,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, updateCallback) { entry.update(*mStubTracker.get(), 0); EXPECT_FALSE(entry.wakeupTime()); - EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, + EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500}, *mStubTracker.get(), 0) .has_value()); auto wakeup = entry.wakeupTime(); @@ -1300,7 +1294,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, updateCallback) { TEST_F(VSyncDispatchTimerQueueEntryTest, skipsUpdateIfJustScheduled) { VSyncDispatchTimerQueueEntry entry( "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); - EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, + EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500}, *mStubTracker.get(), 0) .has_value()); entry.update(*mStubTracker.get(), 0); @@ -1313,24 +1307,24 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, skipsUpdateIfJustScheduled) { TEST_F(VSyncDispatchTimerQueueEntryTest, willSnapToNextTargettableVSync) { VSyncDispatchTimerQueueEntry entry( "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); - EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, + EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500}, *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}, + EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 500}, *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}, + EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .lastVsync = 500}, *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}, + EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 1001}, *mStubTracker.get(), 0) .has_value()); EXPECT_THAT(*entry.wakeupTime(), Eq(1800)); @@ -1343,23 +1337,25 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); Sequence seq; - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500, std::optional<nsecs_t>(500))) .InSequence(seq) .WillOnce(Return(1000)); - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500, std::optional<nsecs_t>(500))) .InSequence(seq) .WillOnce(Return(1000)); - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold)) + EXPECT_CALL(*mStubTracker.get(), + nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold, + std::optional<nsecs_t>(1000))) .InSequence(seq) .WillOnce(Return(2000)); - EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, + EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500}, *mStubTracker.get(), 0) .has_value()); entry.executing(); // 1000 is executing - EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500}, + EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 500}, *mStubTracker.get(), 0) .has_value()); } @@ -1367,28 +1363,31 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, TEST_F(VSyncDispatchTimerQueueEntryTest, reportsScheduledIfStillTime) { VSyncDispatchTimerQueueEntry entry( "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); - EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, + EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500}, *mStubTracker.get(), 0) .has_value()); - EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500}, + EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 500}, *mStubTracker.get(), 0) .has_value()); - EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500}, + EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .lastVsync = 500}, *mStubTracker.get(), 0) .has_value()); - EXPECT_TRUE(entry.schedule({.workDuration = 1200, .readyDuration = 0, .earliestVsync = 500}, + EXPECT_TRUE(entry.schedule({.workDuration = 1200, .readyDuration = 0, .lastVsync = 500}, *mStubTracker.get(), 0) .has_value()); } -TEST_F(VSyncDispatchTimerQueueEntryTest, storesPendingUpdatesUntilUpdate) { +TEST_F(VSyncDispatchTimerQueueEntryTest, storesPendingUpdatesUntilUpdateAndDontSkip) { static constexpr auto effectualOffset = 200; VSyncDispatchTimerQueueEntry entry( "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); EXPECT_FALSE(entry.hasPendingWorkloadUpdate()); - entry.addPendingWorkloadUpdate({.workDuration = 100, .readyDuration = 0, .earliestVsync = 400}); - entry.addPendingWorkloadUpdate( - {.workDuration = effectualOffset, .readyDuration = 0, .earliestVsync = 400}); + entry.addPendingWorkloadUpdate(*mStubTracker.get(), 0, + {.workDuration = 100, .readyDuration = 0, .lastVsync = 400}); + entry.addPendingWorkloadUpdate(*mStubTracker.get(), 0, + {.workDuration = effectualOffset, + .readyDuration = 0, + .lastVsync = 400}); EXPECT_TRUE(entry.hasPendingWorkloadUpdate()); entry.update(*mStubTracker.get(), 0); EXPECT_FALSE(entry.hasPendingWorkloadUpdate()); @@ -1410,7 +1409,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, runCallbackWithReadyDuration) { }, mVsyncMoveThreshold); - EXPECT_TRUE(entry.schedule({.workDuration = 70, .readyDuration = 30, .earliestVsync = 500}, + EXPECT_TRUE(entry.schedule({.workDuration = 70, .readyDuration = 30, .lastVsync = 500}, *mStubTracker.get(), 0) .has_value()); auto const wakeup = entry.wakeupTime(); diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index 7a498c9d29..b9f3d70c6b 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -26,12 +26,12 @@ #include <common/test/FlagUtils.h> #include "Scheduler/VSyncPredictor.h" #include "mock/DisplayHardware/MockDisplayMode.h" -#include "mock/MockVsyncTrackerCallback.h" #include <gmock/gmock.h> #include <gtest/gtest.h> #include <algorithm> #include <chrono> +#include <optional> #include <utility> #include <com_android_graphics_surfaceflinger_flags.h> @@ -81,14 +81,13 @@ struct VSyncPredictorTest : testing::Test { nsecs_t mNow = 0; nsecs_t mPeriod = 1000; ftl::NonNull<DisplayModePtr> mMode = displayMode(mPeriod); - scheduler::mock::VsyncTrackerCallback mVsyncTrackerCallback; static constexpr size_t kHistorySize = 10; static constexpr size_t kMinimumSamplesForPrediction = 6; static constexpr size_t kOutlierTolerancePercent = 25; static constexpr nsecs_t mMaxRoundingError = 100; VSyncPredictor tracker{mMode, kHistorySize, kMinimumSamplesForPrediction, - kOutlierTolerancePercent, mVsyncTrackerCallback}; + kOutlierTolerancePercent}; }; TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) { @@ -409,8 +408,7 @@ TEST_F(VSyncPredictorTest, doesNotPredictBeforeTimePointWithHigherIntercept) { // See b/151146131 TEST_F(VSyncPredictorTest, hasEnoughPrecision) { const auto mode = displayMode(mPeriod); - VSyncPredictor tracker{mode, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent, - mVsyncTrackerCallback}; + VSyncPredictor tracker{mode, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent}; std::vector<nsecs_t> const simulatedVsyncs{840873348817, 840890049444, 840906762675, 840923581635, 840940161584, 840956868096, 840973702473, 840990256277, 841007116851, @@ -657,48 +655,6 @@ TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod)); } -TEST_F(VSyncPredictorTest, vsyncTrackerCallback) { - SET_FLAG_FOR_TEST(flags::vrr_config, true); - - const auto refreshRate = Fps::fromPeriodNsecs(mPeriod); - NotifyExpectedPresentConfig notifyExpectedPresentConfig; - notifyExpectedPresentConfig.notifyExpectedPresentTimeoutNs = Period::fromNs(30).ns(); - - hal::VrrConfig vrrConfig; - vrrConfig.notifyExpectedPresentConfig = notifyExpectedPresentConfig; - vrrConfig.minFrameIntervalNs = refreshRate.getPeriodNsecs(); - - const int32_t kGroup = 0; - const auto kResolution = ui::Size(1920, 1080); - const auto mode = - ftl::as_non_null(createVrrDisplayMode(DisplayModeId(0), refreshRate, vrrConfig, kGroup, - kResolution, DEFAULT_DISPLAY_ID)); - - tracker.setDisplayModePtr(mode); - auto last = mNow; - for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { - EXPECT_CALL(mVsyncTrackerCallback, - onVsyncGenerated(TimePoint::fromNs(last + mPeriod), mode, - FpsMatcher(refreshRate))) - .Times(1); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); - mNow += mPeriod; - last = mNow; - tracker.addVsyncTimestamp(mNow); - } - - tracker.setRenderRate(refreshRate / 2); - { - // out of render rate phase - EXPECT_CALL(mVsyncTrackerCallback, - onVsyncGenerated(TimePoint::fromNs(mNow + 3 * mPeriod), mode, - FpsMatcher(refreshRate / 2))) - .Times(1); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), - Eq(mNow + 3 * mPeriod)); - } -} - TEST_F(VSyncPredictorTest, adjustsVrrTimeline) { SET_FLAG_FOR_TEST(flags::vrr_config, true); @@ -715,22 +671,23 @@ TEST_F(VSyncPredictorTest, adjustsVrrTimeline) { .build()); VSyncPredictor vrrTracker{kMode, kHistorySize, kMinimumSamplesForPrediction, - kOutlierTolerancePercent, mVsyncTrackerCallback}; + kOutlierTolerancePercent}; vrrTracker.setRenderRate(minFrameRate); vrrTracker.addVsyncTimestamp(0); EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700)); - EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1300)); + EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000)); vrrTracker.onFrameBegin(TimePoint::fromNs(2000), TimePoint::fromNs(1500)); - EXPECT_EQ(1500, vrrTracker.nextAnticipatedVSyncTimeFrom(1300)); - EXPECT_EQ(2500, vrrTracker.nextAnticipatedVSyncTimeFrom(2300)); + EXPECT_EQ(3500, vrrTracker.nextAnticipatedVSyncTimeFrom(2000, 2000)); + EXPECT_EQ(4500, vrrTracker.nextAnticipatedVSyncTimeFrom(3500, 3500)); - vrrTracker.onFrameMissed(TimePoint::fromNs(2500)); - EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2300)); - EXPECT_EQ(4000, vrrTracker.nextAnticipatedVSyncTimeFrom(3300)); + // Miss when starting 4500 and expect the next vsync will be at 5000 (next one) + vrrTracker.onFrameBegin(TimePoint::fromNs(3500), TimePoint::fromNs(2500)); + vrrTracker.onFrameMissed(TimePoint::fromNs(4500)); + EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4500, 4500)); + EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); } - } // namespace android::scheduler // TODO(b/129481165): remove the #pragma below and fix conversion issues diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h index 5bcce501e6..685d8f9e95 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h @@ -24,7 +24,7 @@ inline DisplayMode::Builder createDisplayModeBuilder( DisplayModeId modeId, Fps displayRefreshRate, int32_t group = 0, ui::Size resolution = ui::Size(1920, 1080), PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(0)) { - return DisplayMode::Builder(hal::HWConfigId(modeId.value())) + return DisplayMode::Builder(hal::HWConfigId(ftl::to_underlying(modeId))) .setId(modeId) .setPhysicalDisplayId(displayId) .setVsyncPeriod(displayRefreshRate.getPeriodNsecs()) @@ -45,8 +45,8 @@ inline DisplayModePtr createDisplayMode(PhysicalDisplayId displayId, DisplayMode } inline DisplayModePtr createVrrDisplayMode( - DisplayModeId modeId, Fps displayRefreshRate, hal::VrrConfig vrrConfig, int32_t group = 0, - ui::Size resolution = ui::Size(1920, 1080), + DisplayModeId modeId, Fps displayRefreshRate, std::optional<hal::VrrConfig> vrrConfig, + int32_t group = 0, ui::Size resolution = ui::Size(1920, 1080), PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(0)) { return createDisplayModeBuilder(modeId, displayRefreshRate, group, resolution, displayId) .setVrrConfig(std::move(vrrConfig)) diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h index 7413235c19..602bdfc152 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h @@ -53,7 +53,8 @@ public: MOCK_METHOD(hal::Error, getRequests, (hal::DisplayRequest *, (std::unordered_map<Layer *, hal::LayerRequest> *)), (override)); - MOCK_METHOD(hal::Error, getConnectionType, (ui::DisplayConnectionType *), (const, override)); + MOCK_METHOD((ftl::Expected<ui::DisplayConnectionType, hal::Error>), getConnectionType, (), + (const, override)); MOCK_METHOD(hal::Error, supportsDoze, (bool *), (const, override)); MOCK_METHOD(hal::Error, getHdrCapabilities, (android::HdrCapabilities *), (const, override)); MOCK_METHOD(hal::Error, getDisplayedContentSamplingAttributes, diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h index a088aabc11..ed1405b058 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h @@ -18,12 +18,21 @@ #include "binder/Status.h" +// FMQ library in IPower does questionable conversions +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wconversion" #include <aidl/android/hardware/power/IPower.h> +#pragma clang diagnostic pop + #include <gmock/gmock.h> using aidl::android::hardware::power::Boost; +using aidl::android::hardware::power::ChannelConfig; using aidl::android::hardware::power::IPower; using aidl::android::hardware::power::IPowerHintSession; +using aidl::android::hardware::power::SessionConfig; +using aidl::android::hardware::power::SessionTag; + using aidl::android::hardware::power::Mode; using android::binder::Status; @@ -42,6 +51,14 @@ public: int64_t durationNanos, std::shared_ptr<IPowerHintSession>* session), (override)); MOCK_METHOD(ndk::ScopedAStatus, getHintSessionPreferredRate, (int64_t * rate), (override)); + MOCK_METHOD(ndk::ScopedAStatus, createHintSessionWithConfig, + (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, + int64_t durationNanos, SessionTag tag, SessionConfig* config, + std::shared_ptr<IPowerHintSession>* _aidl_return), + (override)); + MOCK_METHOD(ndk::ScopedAStatus, getSessionChannel, + (int32_t tgid, int32_t uid, ChannelConfig* _aidl_return), (override)); + MOCK_METHOD(ndk::ScopedAStatus, closeSessionChannel, (int32_t tgid, int32_t uid), (override)); MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override)); MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override)); MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override)); diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h index 364618d61a..27564b26de 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h @@ -18,10 +18,15 @@ #include "binder/Status.h" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wconversion" #include <aidl/android/hardware/power/IPower.h> +#pragma clang diagnostic pop + #include <gmock/gmock.h> using aidl::android::hardware::power::IPowerHintSession; +using aidl::android::hardware::power::SessionConfig; using aidl::android::hardware::power::SessionHint; using aidl::android::hardware::power::SessionMode; using android::binder::Status; @@ -47,6 +52,7 @@ public: MOCK_METHOD(ndk::ScopedAStatus, sendHint, (SessionHint), (override)); MOCK_METHOD(ndk::ScopedAStatus, setThreads, (const ::std::vector<int32_t>&), (override)); MOCK_METHOD(ndk::ScopedAStatus, setMode, (SessionMode, bool), (override)); + MOCK_METHOD(ndk::ScopedAStatus, getSessionConfig, (SessionConfig * _aidl_return), (override)); }; } // namespace android::Hwc2::mock diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h index 68fe3c52d4..ae41e7ea75 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h @@ -19,7 +19,10 @@ #include <gmock/gmock.h> #include <scheduler/Time.h> +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wconversion" #include <powermanager/PowerHalController.h> +#pragma clang diagnostic pop namespace android { namespace hardware { @@ -31,8 +34,6 @@ class IPower; namespace android::Hwc2::mock { -using aidl::android::hardware::power::Boost; -using aidl::android::hardware::power::Mode; using android::power::HalResult; class MockPowerHalController : public power::PowerHalController { @@ -40,12 +41,22 @@ public: MockPowerHalController(); ~MockPowerHalController() override; MOCK_METHOD(void, init, (), (override)); - MOCK_METHOD(HalResult<void>, setBoost, (Boost, int32_t), (override)); - MOCK_METHOD(HalResult<void>, setMode, (Mode, bool), (override)); + MOCK_METHOD(HalResult<void>, setBoost, (aidl::android::hardware::power::Boost, int32_t), + (override)); + MOCK_METHOD(HalResult<void>, setMode, (aidl::android::hardware::power::Mode, bool), (override)); MOCK_METHOD(HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>, createHintSession, (int32_t, int32_t, const std::vector<int32_t>&, int64_t), (override)); + MOCK_METHOD(HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>, + createHintSessionWithConfig, + (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, + int64_t durationNanos, aidl::android::hardware::power::SessionTag tag, + aidl::android::hardware::power::SessionConfig* config), + (override)); MOCK_METHOD(HalResult<int64_t>, getHintSessionPreferredRate, (), (override)); + MOCK_METHOD(HalResult<aidl::android::hardware::power::ChannelConfig>, getSessionChannel, + (int tgid, int uid), (override)); + MOCK_METHOD(HalResult<void>, closeSessionChannel, (int tgid, int uid), (override)); }; } // namespace android::Hwc2::mock
\ No newline at end of file diff --git a/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h b/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h index a71e82cc75..7b18a82283 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h +++ b/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h @@ -18,12 +18,15 @@ #include <android/gui/DisplayModeSpecs.h> +#include "DisplayHardware/DisplayMode.h" + namespace android::mock { -inline gui::DisplayModeSpecs createDisplayModeSpecs(int32_t defaultMode, bool allowGroupSwitching, - float minFps, float maxFps) { +inline gui::DisplayModeSpecs createDisplayModeSpecs(DisplayModeId defaultMode, + bool allowGroupSwitching, float minFps, + float maxFps) { gui::DisplayModeSpecs specs; - specs.defaultMode = defaultMode; + specs.defaultMode = ftl::to_underlying(defaultMode); specs.allowGroupSwitching = allowGroupSwitching; specs.primaryRanges.physical.min = minFps; specs.primaryRanges.physical.max = maxFps; diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h index 866af3bbd0..e2b0ed1df9 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h +++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h @@ -59,6 +59,9 @@ public: MOCK_METHOD(void, requestLatestConfig, (const sp<android::EventThreadConnection>&)); MOCK_METHOD(void, pauseVsyncCallback, (bool)); MOCK_METHOD(void, onNewVsyncSchedule, (std::shared_ptr<scheduler::VsyncSchedule>), (override)); + MOCK_METHOD(void, onHdcpLevelsChanged, + (PhysicalDisplayId displayId, int32_t connectedLevel, int32_t maxLevel), + (override)); }; } // namespace android::mock diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h index 22b2cccf03..4ca05423d7 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h +++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h @@ -28,6 +28,8 @@ struct SchedulerCallback final : ISchedulerCallback { MOCK_METHOD(void, kernelTimerChanged, (bool), (override)); MOCK_METHOD(void, triggerOnFrameRateOverridesChanged, (), (override)); MOCK_METHOD(void, onChoreographerAttached, (), (override)); + MOCK_METHOD(void, onExpectedPresentTimePosted, (TimePoint, ftl::NonNull<DisplayModePtr>, Fps), + (override)); }; struct NoOpSchedulerCallback final : ISchedulerCallback { @@ -36,6 +38,7 @@ struct NoOpSchedulerCallback final : ISchedulerCallback { void kernelTimerChanged(bool) override {} void triggerOnFrameRateOverridesChanged() override {} void onChoreographerAttached() override {} + void onExpectedPresentTimePosted(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {} }; } // namespace android::scheduler::mock diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h index e588bb9a3f..3870983133 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h +++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h @@ -28,7 +28,8 @@ public: ~VSyncTracker() override; MOCK_METHOD(bool, addVsyncTimestamp, (nsecs_t), (override)); - MOCK_METHOD(nsecs_t, nextAnticipatedVSyncTimeFrom, (nsecs_t), (const, override)); + MOCK_METHOD(nsecs_t, nextAnticipatedVSyncTimeFrom, (nsecs_t, std::optional<nsecs_t>), + (const, override)); MOCK_METHOD(nsecs_t, currentPeriod, (), (const, override)); MOCK_METHOD(Period, minFramePeriod, (), (const, override)); MOCK_METHOD(void, resetModel, (), (override)); diff --git a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp index 971a0b960e..e11a809bc1 100644 --- a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp +++ b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp @@ -152,6 +152,7 @@ BENCHMARK_WRAPPER(VibratorBench, off, { BENCHMARK_WRAPPER(VibratorBench, setAmplitude, { if (!hasCapabilities(vibrator::Capabilities::AMPLITUDE_CONTROL, state)) { + state.SkipWithMessage("missing capability"); return; } @@ -180,6 +181,7 @@ BENCHMARK_WRAPPER(VibratorBench, setAmplitude, { BENCHMARK_WRAPPER(VibratorBench, setAmplitudeCached, { if (!hasCapabilities(vibrator::Capabilities::AMPLITUDE_CONTROL, state)) { + state.SkipWithMessage("missing capability"); return; } @@ -200,6 +202,7 @@ BENCHMARK_WRAPPER(VibratorBench, setAmplitudeCached, { BENCHMARK_WRAPPER(VibratorBench, setExternalControl, { if (!hasCapabilities(vibrator::Capabilities::EXTERNAL_CONTROL, state)) { + state.SkipWithMessage("missing capability"); return; } @@ -221,6 +224,7 @@ BENCHMARK_WRAPPER(VibratorBench, setExternalControl, { BENCHMARK_WRAPPER(VibratorBench, setExternalControlCached, { if (!hasCapabilities(vibrator::Capabilities::EXTERNAL_CONTROL, state)) { + state.SkipWithMessage("missing capability"); return; } @@ -239,6 +243,7 @@ BENCHMARK_WRAPPER(VibratorBench, setExternalControlCached, { BENCHMARK_WRAPPER(VibratorBench, setExternalAmplitudeCached, { if (!hasCapabilities(vibrator::Capabilities::EXTERNAL_AMPLITUDE_CONTROL, state)) { + state.SkipWithMessage("missing capability"); return; } @@ -331,9 +336,11 @@ protected: BENCHMARK_WRAPPER(VibratorEffectsBench, alwaysOnEnable, { if (!hasCapabilities(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) { + state.SkipWithMessage("missing capability"); return; } if (!hasArgs(state)) { + state.SkipWithMessage("missing args"); return; } @@ -357,9 +364,11 @@ BENCHMARK_WRAPPER(VibratorEffectsBench, alwaysOnEnable, { BENCHMARK_WRAPPER(VibratorEffectsBench, alwaysOnDisable, { if (!hasCapabilities(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) { + state.SkipWithMessage("missing capability"); return; } if (!hasArgs(state)) { + state.SkipWithMessage("missing args"); return; } @@ -384,6 +393,7 @@ BENCHMARK_WRAPPER(VibratorEffectsBench, alwaysOnDisable, { BENCHMARK_WRAPPER(VibratorEffectsBench, performEffect, { if (!hasArgs(state)) { + state.SkipWithMessage("missing args"); return; } @@ -441,9 +451,11 @@ protected: BENCHMARK_WRAPPER(VibratorPrimitivesBench, performComposedEffect, { if (!hasCapabilities(vibrator::Capabilities::COMPOSE_EFFECTS, state)) { + state.SkipWithMessage("missing capability"); return; } if (!hasArgs(state)) { + state.SkipWithMessage("missing args"); return; } @@ -468,4 +480,4 @@ BENCHMARK_WRAPPER(VibratorPrimitivesBench, performComposedEffect, { } }); -BENCHMARK_MAIN();
\ No newline at end of file +BENCHMARK_MAIN(); diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp index 0e45d2d8c4..81fd1185b6 100644 --- a/vulkan/libvulkan/driver.cpp +++ b/vulkan/libvulkan/driver.cpp @@ -340,8 +340,9 @@ void Hal::UnloadBuiltinDriver() { ALOGD("Unload builtin Vulkan driver."); // Close the opened device - ALOG_ASSERT(!hal_.dev_->common.close(hal_.dev_->common), - "hw_device_t::close() failed."); + int err = hal_.dev_->common.close( + const_cast<struct hw_device_t*>(&hal_.dev_->common)); + ALOG_ASSERT(!err, "hw_device_t::close() failed."); // Close the opened shared library in the hw_module_t android_unload_sphal_library(hal_.dev_->common.module->dso); diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp index 0df5e77181..13141933ac 100644 --- a/vulkan/libvulkan/swapchain.cpp +++ b/vulkan/libvulkan/swapchain.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "vulkan/vulkan_core.h" #define ATRACE_TAG ATRACE_TAG_GRAPHICS #include <aidl/android/hardware/graphics/common/Dataspace.h> @@ -158,6 +159,25 @@ int InvertTransformToNative(VkSurfaceTransformFlagBitsKHR transform) { } } +const static VkColorSpaceKHR colorSpaceSupportedByVkEXTSwapchainColorspace[] = { + VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT, + VK_COLOR_SPACE_DISPLAY_P3_LINEAR_EXT, + VK_COLOR_SPACE_DCI_P3_NONLINEAR_EXT, + VK_COLOR_SPACE_BT709_LINEAR_EXT, + VK_COLOR_SPACE_BT709_NONLINEAR_EXT, + VK_COLOR_SPACE_BT2020_LINEAR_EXT, + VK_COLOR_SPACE_HDR10_ST2084_EXT, + VK_COLOR_SPACE_HDR10_HLG_EXT, + VK_COLOR_SPACE_ADOBERGB_LINEAR_EXT, + VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT, + VK_COLOR_SPACE_PASS_THROUGH_EXT, + VK_COLOR_SPACE_DCI_P3_LINEAR_EXT}; + +const static VkColorSpaceKHR + colorSpaceSupportedByVkEXTSwapchainColorspaceOnFP16SurfaceOnly[] = { + VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT, + VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT}; + class TimingInfo { public: TimingInfo(const VkPresentTimeGOOGLE* qp, uint64_t nativeFrameId) @@ -745,16 +765,22 @@ VkResult GetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice pdev, }; if (colorspace_ext) { - all_formats.emplace_back(VkSurfaceFormatKHR{ - VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_PASS_THROUGH_EXT}); - all_formats.emplace_back(VkSurfaceFormatKHR{ - VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_PASS_THROUGH_EXT}); - all_formats.emplace_back(VkSurfaceFormatKHR{ - VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_BT709_LINEAR_EXT}); - all_formats.emplace_back(VkSurfaceFormatKHR{ - VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT}); - all_formats.emplace_back(VkSurfaceFormatKHR{ - VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT}); + for (VkColorSpaceKHR colorSpace : + colorSpaceSupportedByVkEXTSwapchainColorspace) { + if (GetNativeDataspace(colorSpace, GetNativePixelFormat( + VK_FORMAT_R8G8B8A8_UNORM)) != + DataSpace::UNKNOWN) { + all_formats.emplace_back( + VkSurfaceFormatKHR{VK_FORMAT_R8G8B8A8_UNORM, colorSpace}); + } + + if (GetNativeDataspace(colorSpace, GetNativePixelFormat( + VK_FORMAT_R8G8B8A8_SRGB)) != + DataSpace::UNKNOWN) { + all_formats.emplace_back( + VkSurfaceFormatKHR{VK_FORMAT_R8G8B8A8_SRGB, colorSpace}); + } + } } // NOTE: Any new formats that are added must be coordinated across different @@ -766,9 +792,16 @@ VkResult GetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice pdev, all_formats.emplace_back(VkSurfaceFormatKHR{ VK_FORMAT_R5G6B5_UNORM_PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}); if (colorspace_ext) { - all_formats.emplace_back( - VkSurfaceFormatKHR{VK_FORMAT_R5G6B5_UNORM_PACK16, - VK_COLOR_SPACE_PASS_THROUGH_EXT}); + for (VkColorSpaceKHR colorSpace : + colorSpaceSupportedByVkEXTSwapchainColorspace) { + if (GetNativeDataspace( + colorSpace, + GetNativePixelFormat(VK_FORMAT_R5G6B5_UNORM_PACK16)) != + DataSpace::UNKNOWN) { + all_formats.emplace_back(VkSurfaceFormatKHR{ + VK_FORMAT_R5G6B5_UNORM_PACK16, colorSpace}); + } + } } } @@ -777,15 +810,28 @@ VkResult GetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice pdev, all_formats.emplace_back(VkSurfaceFormatKHR{ VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}); if (colorspace_ext) { - all_formats.emplace_back( - VkSurfaceFormatKHR{VK_FORMAT_R16G16B16A16_SFLOAT, - VK_COLOR_SPACE_PASS_THROUGH_EXT}); - all_formats.emplace_back( - VkSurfaceFormatKHR{VK_FORMAT_R16G16B16A16_SFLOAT, - VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT}); - all_formats.emplace_back( - VkSurfaceFormatKHR{VK_FORMAT_R16G16B16A16_SFLOAT, - VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT}); + for (VkColorSpaceKHR colorSpace : + colorSpaceSupportedByVkEXTSwapchainColorspace) { + if (GetNativeDataspace( + colorSpace, + GetNativePixelFormat(VK_FORMAT_R16G16B16A16_SFLOAT)) != + DataSpace::UNKNOWN) { + all_formats.emplace_back(VkSurfaceFormatKHR{ + VK_FORMAT_R16G16B16A16_SFLOAT, colorSpace}); + } + } + + for ( + VkColorSpaceKHR colorSpace : + colorSpaceSupportedByVkEXTSwapchainColorspaceOnFP16SurfaceOnly) { + if (GetNativeDataspace( + colorSpace, + GetNativePixelFormat(VK_FORMAT_R16G16B16A16_SFLOAT)) != + DataSpace::UNKNOWN) { + all_formats.emplace_back(VkSurfaceFormatKHR{ + VK_FORMAT_R16G16B16A16_SFLOAT, colorSpace}); + } + } } } @@ -795,12 +841,16 @@ VkResult GetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice pdev, VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}); if (colorspace_ext) { - all_formats.emplace_back( - VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32, - VK_COLOR_SPACE_PASS_THROUGH_EXT}); - all_formats.emplace_back( - VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32, - VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT}); + for (VkColorSpaceKHR colorSpace : + colorSpaceSupportedByVkEXTSwapchainColorspace) { + if (GetNativeDataspace( + colorSpace, GetNativePixelFormat( + VK_FORMAT_A2B10G10R10_UNORM_PACK32)) != + DataSpace::UNKNOWN) { + all_formats.emplace_back(VkSurfaceFormatKHR{ + VK_FORMAT_A2B10G10R10_UNORM_PACK32, colorSpace}); + } + } } } @@ -833,12 +883,18 @@ VkResult GetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice pdev, VkSurfaceFormatKHR{VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}); if (colorspace_ext) { - all_formats.emplace_back( - VkSurfaceFormatKHR{VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16, - VK_COLOR_SPACE_PASS_THROUGH_EXT}); - all_formats.emplace_back( - VkSurfaceFormatKHR{VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16, - VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT}); + for (VkColorSpaceKHR colorSpace : + colorSpaceSupportedByVkEXTSwapchainColorspace) { + if (GetNativeDataspace( + colorSpace, + GetNativePixelFormat( + VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16)) != + DataSpace::UNKNOWN) { + all_formats.emplace_back(VkSurfaceFormatKHR{ + VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16, + colorSpace}); + } + } } } @@ -1369,197 +1425,221 @@ static VkResult getProducerUsage(const VkDevice& device, const VkPhysicalDevice& pdev = GetData(device).driver_physical_device; const InstanceData& instance_data = GetData(pdev); const InstanceDriverTable& instance_dispatch = instance_data.driver; - if (!instance_dispatch.GetPhysicalDeviceImageFormatProperties2 && - !instance_dispatch.GetPhysicalDeviceImageFormatProperties2KHR) { - uint64_t native_usage = 0; - void* usage_info_pNext = nullptr; - VkResult result; + if (instance_dispatch.GetPhysicalDeviceImageFormatProperties2 || + instance_dispatch.GetPhysicalDeviceImageFormatProperties2KHR) { + // Look through the create_info pNext chain passed to createSwapchainKHR + // for an image compression control struct. + // if one is found AND the appropriate extensions are enabled, create a + // VkImageCompressionControlEXT structure to pass on to + // GetPhysicalDeviceImageFormatProperties2 + void* compression_control_pNext = nullptr; VkImageCompressionControlEXT image_compression = {}; - const auto& dispatch = GetData(device).driver; - if (dispatch.GetSwapchainGrallocUsage4ANDROID) { - ATRACE_BEGIN("GetSwapchainGrallocUsage4ANDROID"); - VkGrallocUsageInfo2ANDROID gralloc_usage_info = {}; - gralloc_usage_info.sType = - VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_2_ANDROID; - gralloc_usage_info.format = create_info->imageFormat; - gralloc_usage_info.imageUsage = create_info->imageUsage; - gralloc_usage_info.swapchainImageUsage = swapchain_image_usage; - - // Look through the pNext chain for an image compression control struct - // if one is found AND the appropriate extensions are enabled, - // append it to be the gralloc usage pNext chain - const VkSwapchainCreateInfoKHR* create_infos = create_info; - while (create_infos->pNext) { - create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>( - create_infos->pNext); - switch (create_infos->sType) { - case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: { - const VkImageCompressionControlEXT* compression_infos = - reinterpret_cast<const VkImageCompressionControlEXT*>( - create_infos); - image_compression = *compression_infos; - image_compression.pNext = nullptr; - usage_info_pNext = &image_compression; - } break; - - default: - // Ignore all other info structs - break; - } + const VkSwapchainCreateInfoKHR* create_infos = create_info; + while (create_infos->pNext) { + create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(create_infos->pNext); + switch (create_infos->sType) { + case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: { + const VkImageCompressionControlEXT* compression_infos = + reinterpret_cast<const VkImageCompressionControlEXT*>(create_infos); + image_compression = *compression_infos; + image_compression.pNext = nullptr; + compression_control_pNext = &image_compression; + } break; + default: + // Ignore all other info structs + break; } - gralloc_usage_info.pNext = usage_info_pNext; + } - result = dispatch.GetSwapchainGrallocUsage4ANDROID( - device, &gralloc_usage_info, &native_usage); - ATRACE_END(); - if (result != VK_SUCCESS) { - ALOGE("vkGetSwapchainGrallocUsage4ANDROID failed: %d", result); - return VK_ERROR_SURFACE_LOST_KHR; - } - } else if (dispatch.GetSwapchainGrallocUsage3ANDROID) { - ATRACE_BEGIN("GetSwapchainGrallocUsage3ANDROID"); - VkGrallocUsageInfoANDROID gralloc_usage_info = {}; - gralloc_usage_info.sType = VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_ANDROID; - gralloc_usage_info.format = create_info->imageFormat; - gralloc_usage_info.imageUsage = create_info->imageUsage; - - // Look through the pNext chain for an image compression control struct - // if one is found AND the appropriate extensions are enabled, - // append it to be the gralloc usage pNext chain - const VkSwapchainCreateInfoKHR* create_infos = create_info; - while (create_infos->pNext) { - create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>( - create_infos->pNext); - switch (create_infos->sType) { - case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: { - const VkImageCompressionControlEXT* compression_infos = - reinterpret_cast<const VkImageCompressionControlEXT*>( - create_infos); - image_compression = *compression_infos; - image_compression.pNext = nullptr; - usage_info_pNext = &image_compression; - } break; + // call GetPhysicalDeviceImageFormatProperties2KHR + VkPhysicalDeviceExternalImageFormatInfo external_image_format_info = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO, + .pNext = compression_control_pNext, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID, + }; - default: - // Ignore all other info structs - break; - } - } - gralloc_usage_info.pNext = usage_info_pNext; + // AHB does not have an sRGB format so we can't pass it to GPDIFP + // We need to convert the format to unorm if it is srgb + VkFormat format = create_info->imageFormat; + if (format == VK_FORMAT_R8G8B8A8_SRGB) { + format = VK_FORMAT_R8G8B8A8_UNORM; + } - result = dispatch.GetSwapchainGrallocUsage3ANDROID( - device, &gralloc_usage_info, &native_usage); - ATRACE_END(); - if (result != VK_SUCCESS) { - ALOGE("vkGetSwapchainGrallocUsage3ANDROID failed: %d", result); - return VK_ERROR_SURFACE_LOST_KHR; - } - } else if (dispatch.GetSwapchainGrallocUsage2ANDROID) { - uint64_t consumer_usage, producer_usage; - ATRACE_BEGIN("GetSwapchainGrallocUsage2ANDROID"); - result = dispatch.GetSwapchainGrallocUsage2ANDROID( - device, create_info->imageFormat, create_info->imageUsage, - swapchain_image_usage, &consumer_usage, &producer_usage); - ATRACE_END(); + VkPhysicalDeviceImageFormatInfo2 image_format_info = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, + .pNext = &external_image_format_info, + .format = format, + .type = VK_IMAGE_TYPE_2D, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = create_info->imageUsage, + .flags = create_protected_swapchain ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u, + }; + + VkAndroidHardwareBufferUsageANDROID ahb_usage; + ahb_usage.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_USAGE_ANDROID; + ahb_usage.pNext = nullptr; + + VkImageFormatProperties2 image_format_properties; + image_format_properties.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2; + image_format_properties.pNext = &ahb_usage; + + if (instance_dispatch.GetPhysicalDeviceImageFormatProperties2) { + VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2( + pdev, &image_format_info, &image_format_properties); if (result != VK_SUCCESS) { - ALOGE("vkGetSwapchainGrallocUsage2ANDROID failed: %d", result); + ALOGE("VkGetPhysicalDeviceImageFormatProperties2 for AHB usage failed: %d", result); return VK_ERROR_SURFACE_LOST_KHR; } - native_usage = - convertGralloc1ToBufferUsage(producer_usage, consumer_usage); - } else if (dispatch.GetSwapchainGrallocUsageANDROID) { - ATRACE_BEGIN("GetSwapchainGrallocUsageANDROID"); - int32_t legacy_usage = 0; - result = dispatch.GetSwapchainGrallocUsageANDROID( - device, create_info->imageFormat, create_info->imageUsage, - &legacy_usage); - ATRACE_END(); + } + else { + VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2KHR( + pdev, &image_format_info, + &image_format_properties); if (result != VK_SUCCESS) { - ALOGE("vkGetSwapchainGrallocUsageANDROID failed: %d", result); + ALOGE("VkGetPhysicalDeviceImageFormatProperties2KHR for AHB usage failed: %d", + result); return VK_ERROR_SURFACE_LOST_KHR; } - native_usage = static_cast<uint64_t>(legacy_usage); } - *producer_usage = native_usage; - - return VK_SUCCESS; - } - // Look through the create_info pNext chain passed to createSwapchainKHR - // for an image compression control struct. - // if one is found AND the appropriate extensions are enabled, create a - // VkImageCompressionControlEXT structure to pass on to GetPhysicalDeviceImageFormatProperties2 - void* compression_control_pNext = nullptr; - VkImageCompressionControlEXT image_compression = {}; - const VkSwapchainCreateInfoKHR* create_infos = create_info; - while (create_infos->pNext) { - create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(create_infos->pNext); - switch (create_infos->sType) { - case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: { - const VkImageCompressionControlEXT* compression_infos = - reinterpret_cast<const VkImageCompressionControlEXT*>(create_infos); - image_compression = *compression_infos; - image_compression.pNext = nullptr; - compression_control_pNext = &image_compression; - } break; - default: - // Ignore all other info structs - break; + // Determine if USAGE_FRONT_BUFFER is needed. + // GPDIFP2 has no means of using VkSwapchainImageUsageFlagsANDROID when + // querying for producer_usage. So androidHardwareBufferUsage will not + // contain USAGE_FRONT_BUFFER. We need to manually check for usage here. + if (!(swapchain_image_usage & VK_SWAPCHAIN_IMAGE_USAGE_SHARED_BIT_ANDROID)) { + *producer_usage = ahb_usage.androidHardwareBufferUsage; + return VK_SUCCESS; } - } - // call GetPhysicalDeviceImageFormatProperties2KHR - VkPhysicalDeviceExternalImageFormatInfo external_image_format_info = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO, - .pNext = compression_control_pNext, - .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID, - }; + // Check if USAGE_FRONT_BUFFER is supported for this swapchain + AHardwareBuffer_Desc ahb_desc = { + .width = create_info->imageExtent.width, + .height = create_info->imageExtent.height, + .layers = create_info->imageArrayLayers, + .format = create_info->imageFormat, + .usage = ahb_usage.androidHardwareBufferUsage | AHARDWAREBUFFER_USAGE_FRONT_BUFFER, + .stride = 0, // stride is always ignored when calling isSupported() + }; - // AHB does not have an sRGB format so we can't pass it to GPDIFP - // We need to convert the format to unorm if it is srgb - VkFormat format = create_info->imageFormat; - if (format == VK_FORMAT_R8G8B8A8_SRGB) { - format = VK_FORMAT_R8G8B8A8_UNORM; + // If FRONT_BUFFER is not supported, + // then we need to call GetSwapchainGrallocUsageXAndroid below + if (AHardwareBuffer_isSupported(&ahb_desc)) { + *producer_usage = ahb_usage.androidHardwareBufferUsage; + *producer_usage |= AHARDWAREBUFFER_USAGE_FRONT_BUFFER; + return VK_SUCCESS; + } } - VkPhysicalDeviceImageFormatInfo2 image_format_info = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, - .pNext = &external_image_format_info, - .format = format, - .type = VK_IMAGE_TYPE_2D, - .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = create_info->imageUsage, - .flags = create_protected_swapchain ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u, - }; + uint64_t native_usage = 0; + void* usage_info_pNext = nullptr; + VkResult result; + VkImageCompressionControlEXT image_compression = {}; + const auto& dispatch = GetData(device).driver; + if (dispatch.GetSwapchainGrallocUsage4ANDROID) { + ATRACE_BEGIN("GetSwapchainGrallocUsage4ANDROID"); + VkGrallocUsageInfo2ANDROID gralloc_usage_info = {}; + gralloc_usage_info.sType = + VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_2_ANDROID; + gralloc_usage_info.format = create_info->imageFormat; + gralloc_usage_info.imageUsage = create_info->imageUsage; + gralloc_usage_info.swapchainImageUsage = swapchain_image_usage; + + // Look through the pNext chain for an image compression control struct + // if one is found AND the appropriate extensions are enabled, + // append it to be the gralloc usage pNext chain + const VkSwapchainCreateInfoKHR* create_infos = create_info; + while (create_infos->pNext) { + create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>( + create_infos->pNext); + switch (create_infos->sType) { + case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: { + const VkImageCompressionControlEXT* compression_infos = + reinterpret_cast<const VkImageCompressionControlEXT*>( + create_infos); + image_compression = *compression_infos; + image_compression.pNext = nullptr; + usage_info_pNext = &image_compression; + } break; + + default: + // Ignore all other info structs + break; + } + } + gralloc_usage_info.pNext = usage_info_pNext; - VkAndroidHardwareBufferUsageANDROID ahb_usage; - ahb_usage.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_USAGE_ANDROID; - ahb_usage.pNext = nullptr; + result = dispatch.GetSwapchainGrallocUsage4ANDROID( + device, &gralloc_usage_info, &native_usage); + ATRACE_END(); + if (result != VK_SUCCESS) { + ALOGE("vkGetSwapchainGrallocUsage4ANDROID failed: %d", result); + return VK_ERROR_SURFACE_LOST_KHR; + } + } else if (dispatch.GetSwapchainGrallocUsage3ANDROID) { + ATRACE_BEGIN("GetSwapchainGrallocUsage3ANDROID"); + VkGrallocUsageInfoANDROID gralloc_usage_info = {}; + gralloc_usage_info.sType = VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_ANDROID; + gralloc_usage_info.format = create_info->imageFormat; + gralloc_usage_info.imageUsage = create_info->imageUsage; + + // Look through the pNext chain for an image compression control struct + // if one is found AND the appropriate extensions are enabled, + // append it to be the gralloc usage pNext chain + const VkSwapchainCreateInfoKHR* create_infos = create_info; + while (create_infos->pNext) { + create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>( + create_infos->pNext); + switch (create_infos->sType) { + case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: { + const VkImageCompressionControlEXT* compression_infos = + reinterpret_cast<const VkImageCompressionControlEXT*>( + create_infos); + image_compression = *compression_infos; + image_compression.pNext = nullptr; + usage_info_pNext = &image_compression; + } break; - VkImageFormatProperties2 image_format_properties; - image_format_properties.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2; - image_format_properties.pNext = &ahb_usage; + default: + // Ignore all other info structs + break; + } + } + gralloc_usage_info.pNext = usage_info_pNext; - if (instance_dispatch.GetPhysicalDeviceImageFormatProperties2) { - VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2( - pdev, &image_format_info, &image_format_properties); + result = dispatch.GetSwapchainGrallocUsage3ANDROID( + device, &gralloc_usage_info, &native_usage); + ATRACE_END(); if (result != VK_SUCCESS) { - ALOGE("VkGetPhysicalDeviceImageFormatProperties2 for AHB usage failed: %d", result); + ALOGE("vkGetSwapchainGrallocUsage3ANDROID failed: %d", result); return VK_ERROR_SURFACE_LOST_KHR; } - } - else { - VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2KHR( - pdev, &image_format_info, - &image_format_properties); + } else if (dispatch.GetSwapchainGrallocUsage2ANDROID) { + uint64_t consumer_usage, producer_usage; + ATRACE_BEGIN("GetSwapchainGrallocUsage2ANDROID"); + result = dispatch.GetSwapchainGrallocUsage2ANDROID( + device, create_info->imageFormat, create_info->imageUsage, + swapchain_image_usage, &consumer_usage, &producer_usage); + ATRACE_END(); if (result != VK_SUCCESS) { - ALOGE("VkGetPhysicalDeviceImageFormatProperties2KHR for AHB usage failed: %d", - result); + ALOGE("vkGetSwapchainGrallocUsage2ANDROID failed: %d", result); return VK_ERROR_SURFACE_LOST_KHR; } + native_usage = + convertGralloc1ToBufferUsage(producer_usage, consumer_usage); + } else if (dispatch.GetSwapchainGrallocUsageANDROID) { + ATRACE_BEGIN("GetSwapchainGrallocUsageANDROID"); + int32_t legacy_usage = 0; + result = dispatch.GetSwapchainGrallocUsageANDROID( + device, create_info->imageFormat, create_info->imageUsage, + &legacy_usage); + ATRACE_END(); + if (result != VK_SUCCESS) { + ALOGE("vkGetSwapchainGrallocUsageANDROID failed: %d", result); + return VK_ERROR_SURFACE_LOST_KHR; + } + native_usage = static_cast<uint64_t>(legacy_usage); } - - *producer_usage = ahb_usage.androidHardwareBufferUsage; + *producer_usage = native_usage; return VK_SUCCESS; } @@ -1762,6 +1842,8 @@ VkResult CreateSwapchainKHR(VkDevice device, } int query_value; + // TODO: Now that we are calling into GPDSC2 directly, this query may be redundant + // the call to std::max(min_buffer_count, num_images) may be redundant as well err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &query_value); if (err != android::OK || query_value < 0) { @@ -1778,12 +1860,33 @@ VkResult CreateSwapchainKHR(VkDevice device, // with extra images (which they can't actually use!). const uint32_t min_buffer_count = min_undequeued_buffers + 1; - uint32_t num_images; - if (create_info->presentMode == VK_PRESENT_MODE_MAILBOX_KHR) { - num_images = std::max(3u, create_info->minImageCount); - } else { - num_images = create_info->minImageCount; - } + // Call into GPDSC2 to get the minimum and maximum allowable buffer count for the surface of + // interest. This step is only necessary if the app requests a number of images + // (create_info->minImageCount) that is less or more than the surface capabilities. + // An app should be calling GPDSC2 and using those values to set create_info, but in the + // event that the app has hard-coded image counts an error can occur + VkSurfacePresentModeEXT present_mode = { + VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_EXT, + nullptr, + create_info->presentMode + }; + VkPhysicalDeviceSurfaceInfo2KHR surface_info2 = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR, + &present_mode, + create_info->surface + }; + VkSurfaceCapabilities2KHR surface_capabilities2 = { + VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR, + nullptr, + {}, + }; + result = GetPhysicalDeviceSurfaceCapabilities2KHR(GetData(device).driver_physical_device, + &surface_info2, &surface_capabilities2); + + uint32_t num_images = create_info->minImageCount; + num_images = std::clamp(num_images, + surface_capabilities2.surfaceCapabilities.minImageCount, + surface_capabilities2.surfaceCapabilities.maxImageCount); const uint32_t buffer_count = std::max(min_buffer_count, num_images); err = native_window_set_buffer_count(window, buffer_count); |