diff options
165 files changed, 6741 insertions, 3230 deletions
diff --git a/cmds/servicemanager/Access.cpp b/cmds/servicemanager/Access.cpp index 711038ce63..809872417d 100644 --- a/cmds/servicemanager/Access.cpp +++ b/cmds/servicemanager/Access.cpp @@ -22,6 +22,8 @@ #include <selinux/android.h> #include <selinux/avc.h> +#include <sstream> + namespace android { #ifdef VENDORSERVICEMANAGER @@ -80,6 +82,12 @@ static int auditCallback(void *data, security_class_t /*cls*/, char *buf, size_t } #endif +std::string Access::CallingContext::toDebugString() const { + std::stringstream ss; + ss << "Caller(pid=" << debugPid << ",uid=" << uid << ",sid=" << sid << ")"; + return ss.str(); +} + Access::Access() { #ifdef __ANDROID__ union selinux_callback cb; diff --git a/cmds/servicemanager/Access.h b/cmds/servicemanager/Access.h index 77c2cd4ed6..4ee9b907d7 100644 --- a/cmds/servicemanager/Access.h +++ b/cmds/servicemanager/Access.h @@ -36,6 +36,8 @@ public: pid_t debugPid; uid_t uid; std::string sid; + + std::string toDebugString() const; }; virtual CallingContext getCallingContext(); diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp index a828b52cde..a5c0c602c7 100644 --- a/cmds/servicemanager/ServiceManager.cpp +++ b/cmds/servicemanager/ServiceManager.cpp @@ -115,18 +115,20 @@ static std::string getAidlInstanceName(const vintf::ManifestInstance& instance) return instance.package() + "." + instance.interface() + "/" + instance.instance(); } -static bool isVintfDeclared(const std::string& name) { +static bool isVintfDeclared(const Access::CallingContext& ctx, const std::string& name) { NativeName nname; if (NativeName::fill(name, &nname)) { bool found = forEachManifest([&](const ManifestWithDescription& mwd) { if (mwd.manifest->hasNativeInstance(nname.package, nname.instance)) { - ALOGI("Found %s in %s VINTF manifest.", name.c_str(), mwd.description); + ALOGI("%s Found %s in %s VINTF manifest.", ctx.toDebugString().c_str(), + name.c_str(), mwd.description); return true; // break } return false; // continue }); if (!found) { - ALOGI("Could not find %s in the VINTF manifest.", name.c_str()); + ALOGI("%s Could not find %s in the VINTF manifest.", ctx.toDebugString().c_str(), + name.c_str()); } return found; } @@ -136,7 +138,8 @@ static bool isVintfDeclared(const std::string& name) { bool found = forEachManifest([&](const ManifestWithDescription& mwd) { if (mwd.manifest->hasAidlInstance(aname.package, aname.iface, aname.instance)) { - ALOGI("Found %s in %s VINTF manifest.", name.c_str(), mwd.description); + ALOGI("%s Found %s in %s VINTF manifest.", ctx.toDebugString().c_str(), name.c_str(), + mwd.description); return true; // break } return false; // continue @@ -161,8 +164,9 @@ static bool isVintfDeclared(const std::string& name) { } // Although it is tested, explicitly rebuilding qualified name, in case it // becomes something unexpected. - ALOGI("Could not find %s.%s/%s in the VINTF manifest. %s.", aname.package.c_str(), - aname.iface.c_str(), aname.instance.c_str(), available.c_str()); + ALOGI("%s Could not find %s.%s/%s in the VINTF manifest. %s.", ctx.toDebugString().c_str(), + aname.package.c_str(), aname.iface.c_str(), aname.instance.c_str(), + available.c_str()); } return found; @@ -290,12 +294,13 @@ static std::vector<std::string> getVintfInstances(const std::string& interface) return ret; } -static bool meetsDeclarationRequirements(const sp<IBinder>& binder, const std::string& name) { +static bool meetsDeclarationRequirements(const Access::CallingContext& ctx, + const sp<IBinder>& binder, const std::string& name) { if (!Stability::requiresVintfDeclaration(binder)) { return true; } - return isVintfDeclared(name); + return isVintfDeclared(ctx, name); } #endif // !VENDORSERVICEMANAGER @@ -307,7 +312,7 @@ ServiceManager::Service::~Service() { // clear this bit so that we can abort in other cases, where it would // mean inconsistent logic in servicemanager (unexpected and tested, but // the original lazy service impl here had that bug). - LOG(WARNING) << "a service was removed when there are clients"; + ALOGW("A service was removed when there are clients"); } } @@ -423,25 +428,26 @@ Status ServiceManager::addService(const std::string& name, const sp<IBinder>& bi } if (!isValidServiceName(name)) { - ALOGE("Invalid service name: %s", name.c_str()); + ALOGE("%s Invalid service name: %s", ctx.toDebugString().c_str(), name.c_str()); return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Invalid service name."); } #ifndef VENDORSERVICEMANAGER - if (!meetsDeclarationRequirements(binder, name)) { + if (!meetsDeclarationRequirements(ctx, binder, name)) { // already logged return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "VINTF declaration error."); } #endif // !VENDORSERVICEMANAGER if ((dumpPriority & DUMP_FLAG_PRIORITY_ALL) == 0) { - ALOGW("Dump flag priority is not set when adding %s", name.c_str()); + ALOGW("%s Dump flag priority is not set when adding %s", ctx.toDebugString().c_str(), + name.c_str()); } // implicitly unlinked when the binder is removed if (binder->remoteBinder() != nullptr && binder->linkToDeath(sp<ServiceManager>::fromExisting(this)) != OK) { - ALOGE("Could not linkToDeath when adding %s", name.c_str()); + ALOGE("%s Could not linkToDeath when adding %s", ctx.toDebugString().c_str(), name.c_str()); return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Couldn't linkToDeath."); } @@ -543,7 +549,7 @@ Status ServiceManager::registerForNotifications( } if (!isValidServiceName(name)) { - ALOGE("Invalid service name: %s", name.c_str()); + ALOGE("%s Invalid service name: %s", ctx.toDebugString().c_str(), name.c_str()); return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Invalid service name."); } @@ -554,7 +560,7 @@ Status ServiceManager::registerForNotifications( if (OK != IInterface::asBinder(callback)->linkToDeath( sp<ServiceManager>::fromExisting(this))) { - ALOGE("Could not linkToDeath when adding %s", name.c_str()); + ALOGE("%s Could not linkToDeath when adding %s", ctx.toDebugString().c_str(), name.c_str()); return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Couldn't link to death."); } @@ -586,7 +592,8 @@ Status ServiceManager::unregisterForNotifications( } if (!found) { - ALOGE("Trying to unregister callback, but none exists %s", name.c_str()); + ALOGE("%s Trying to unregister callback, but none exists %s", ctx.toDebugString().c_str(), + name.c_str()); return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Nothing to unregister."); } @@ -603,7 +610,7 @@ Status ServiceManager::isDeclared(const std::string& name, bool* outReturn) { *outReturn = false; #ifndef VENDORSERVICEMANAGER - *outReturn = isVintfDeclared(name); + *outReturn = isVintfDeclared(ctx, name); #endif return Status::ok(); } @@ -735,18 +742,16 @@ void ServiceManager::binderDied(const wp<IBinder>& who) { } void ServiceManager::tryStartService(const Access::CallingContext& ctx, const std::string& name) { - ALOGI("Since '%s' could not be found (requested by debug pid %d), trying to start it as a lazy " - "AIDL service. (if it's not configured to be a lazy service, it may be stuck starting or " - "still starting).", - name.c_str(), ctx.debugPid); + ALOGI("%s Since '%s' could not be found trying to start it as a lazy AIDL service. (if it's " + "not configured to be a lazy service, it may be stuck starting or still starting).", + ctx.toDebugString().c_str(), name.c_str()); std::thread([=] { if (!base::SetProperty("ctl.interface_start", "aidl/" + name)) { - ALOGI("Tried to start aidl service %s as a lazy service, but was unable to. Usually " - "this happens when a " - "service is not installed, but if the service is intended to be used as a " - "lazy service, then it may be configured incorrectly.", - name.c_str()); + ALOGI("%s Tried to start aidl service %s as a lazy service, but was unable to. Usually " + "this happens when a service is not installed, but if the service is intended to " + "be used as a lazy service, then it may be configured incorrectly.", + ctx.toDebugString().c_str(), name.c_str()); } }).detach(); } @@ -764,26 +769,28 @@ Status ServiceManager::registerClientCallback(const std::string& name, const sp< auto serviceIt = mNameToService.find(name); if (serviceIt == mNameToService.end()) { - ALOGE("Could not add callback for nonexistent service: %s", name.c_str()); + ALOGE("%s Could not add callback for nonexistent service: %s", ctx.toDebugString().c_str(), + name.c_str()); return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Service doesn't exist."); } if (serviceIt->second.ctx.debugPid != IPCThreadState::self()->getCallingPid()) { - ALOGW("Only a server can register for client callbacks (for %s)", name.c_str()); + ALOGW("%s Only a server can register for client callbacks (for %s)", + ctx.toDebugString().c_str(), name.c_str()); return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION, "Only service can register client callback for itself."); } if (serviceIt->second.binder != service) { - ALOGW("Tried to register client callback for %s but a different service is registered " + ALOGW("%s Tried to register client callback for %s but a different service is registered " "under this name.", - name.c_str()); + ctx.toDebugString().c_str(), name.c_str()); return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Service mismatch."); } if (OK != IInterface::asBinder(cb)->linkToDeath(sp<ServiceManager>::fromExisting(this))) { - ALOGE("Could not linkToDeath when adding client callback for %s", name.c_str()); + ALOGE("%s Could not linkToDeath when adding client callback for %s", name.c_str()); return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Couldn't linkToDeath."); } @@ -921,13 +928,14 @@ Status ServiceManager::tryUnregisterService(const std::string& name, const sp<IB auto serviceIt = mNameToService.find(name); if (serviceIt == mNameToService.end()) { - ALOGW("Tried to unregister %s, but that service wasn't registered to begin with.", - name.c_str()); + ALOGW("%s Tried to unregister %s, but that service wasn't registered to begin with.", + ctx.toDebugString().c_str(), name.c_str()); return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Service not registered."); } if (serviceIt->second.ctx.debugPid != IPCThreadState::self()->getCallingPid()) { - ALOGW("Only a server can unregister itself (for %s)", name.c_str()); + ALOGW("%s Only a server can unregister itself (for %s)", ctx.toDebugString().c_str(), + name.c_str()); return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION, "Service can only unregister itself."); } @@ -935,8 +943,8 @@ Status ServiceManager::tryUnregisterService(const std::string& name, const sp<IB sp<IBinder> storedBinder = serviceIt->second.binder; if (binder != storedBinder) { - ALOGW("Tried to unregister %s, but a different service is registered under this name.", - name.c_str()); + ALOGW("%s Tried to unregister %s, but a different service is registered under this name.", + ctx.toDebugString().c_str(), name.c_str()); return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Different service registered under this name."); } @@ -944,7 +952,8 @@ Status ServiceManager::tryUnregisterService(const std::string& name, const sp<IB // important because we don't have timer-based guarantees, we don't want to clear // this if (serviceIt->second.guaranteeClient) { - ALOGI("Tried to unregister %s, but there is about to be a client.", name.c_str()); + ALOGI("%s Tried to unregister %s, but there is about to be a client.", + ctx.toDebugString().c_str(), name.c_str()); return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Can't unregister, pending client."); } @@ -954,7 +963,8 @@ Status ServiceManager::tryUnregisterService(const std::string& name, const sp<IB constexpr size_t kKnownClients = 2; if (handleServiceClientCallback(kKnownClients, name, false)) { - ALOGI("Tried to unregister %s, but there are clients.", name.c_str()); + ALOGI("%s Tried to unregister %s, but there are clients.", ctx.toDebugString().c_str(), + name.c_str()); // Since we had a failed registration attempt, and the HIDL implementation of // delaying service shutdown for multiple periods wasn't ported here... this may @@ -965,7 +975,7 @@ Status ServiceManager::tryUnregisterService(const std::string& name, const sp<IB "Can't unregister, known client."); } - ALOGI("Unregistering %s", name.c_str()); + ALOGI("%s Unregistering %s", ctx.toDebugString().c_str(), name.c_str()); mNameToService.erase(name); return Status::ok(); diff --git a/cmds/sfdo/Android.bp b/cmds/sfdo/Android.bp index c19c9da7bf..a91a7dc08a 100644 --- a/cmds/sfdo/Android.bp +++ b/cmds/sfdo/Android.bp @@ -1,17 +1,10 @@ -cc_binary { +rust_binary { name: "sfdo", + srcs: ["sfdo.rs"], - srcs: ["sfdo.cpp"], - - shared_libs: [ - "libutils", - "libgui", - ], - - cflags: [ - "-Wall", - "-Werror", - "-Wunused", - "-Wunreachable-code", + rustlibs: [ + "android.gui-rust", + "libclap", ], + edition: "2021", } diff --git a/cmds/sfdo/sfdo.cpp b/cmds/sfdo/sfdo.cpp deleted file mode 100644 index de0e1718ab..0000000000 --- a/cmds/sfdo/sfdo.cpp +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include <inttypes.h> -#include <stdint.h> -#include <any> -#include <map> - -#include <cutils/properties.h> -#include <sys/resource.h> -#include <utils/Log.h> - -#include <gui/ISurfaceComposer.h> -#include <gui/SurfaceComposerClient.h> -#include <gui/SurfaceControl.h> -#include <private/gui/ComposerServiceAIDL.h> - -using namespace android; - -std::map<std::string, std::any> g_functions; - -enum class ParseToggleResult { - kError, - kFalse, - kTrue, -}; - -const std::map<std::string, std::string> g_function_details = { - {"debugFlash", "[optional(delay)] Perform a debug flash."}, - {"frameRateIndicator", "[hide | show] displays the framerate in the top left corner."}, - {"scheduleComposite", "Force composite ahead of next VSYNC."}, - {"scheduleCommit", "Force commit ahead of next VSYNC."}, - {"scheduleComposite", "PENDING - if you have a good understanding let me know!"}, - {"forceClientComposition", - "[enabled | disabled] When enabled, it disables " - "Hardware Overlays, and routes all window composition to the GPU. This can " - "help check if there is a bug in HW Composer."}, -}; - -static void ShowUsage() { - std::cout << "usage: sfdo [help, frameRateIndicator show, debugFlash enabled, ...]\n\n"; - for (const auto& sf : g_functions) { - const std::string fn = sf.first; - std::string fdetails = "TODO"; - if (g_function_details.find(fn) != g_function_details.end()) - fdetails = g_function_details.find(fn)->second; - std::cout << " " << fn << ": " << fdetails << "\n"; - } -} - -// Returns 1 for positive keywords and 0 for negative keywords. -// If the string does not match any it will return -1. -ParseToggleResult parseToggle(const char* str) { - const std::unordered_set<std::string> positive{"1", "true", "y", "yes", - "on", "enabled", "show"}; - const std::unordered_set<std::string> negative{"0", "false", "n", "no", - "off", "disabled", "hide"}; - - const std::string word(str); - if (positive.count(word)) { - return ParseToggleResult::kTrue; - } - if (negative.count(word)) { - return ParseToggleResult::kFalse; - } - - return ParseToggleResult::kError; -} - -int frameRateIndicator(int argc, char** argv) { - bool hide = false, show = false; - if (argc == 3) { - show = strcmp(argv[2], "show") == 0; - hide = strcmp(argv[2], "hide") == 0; - } - - if (show || hide) { - ComposerServiceAIDL::getComposerService()->enableRefreshRateOverlay(show); - } else { - std::cerr << "Incorrect usage of frameRateIndicator. Missing [hide | show].\n"; - return -1; - } - return 0; -} - -int debugFlash(int argc, char** argv) { - int delay = 0; - if (argc == 3) { - delay = atoi(argv[2]) == 0; - } - - ComposerServiceAIDL::getComposerService()->setDebugFlash(delay); - return 0; -} - -int scheduleComposite([[maybe_unused]] int argc, [[maybe_unused]] char** argv) { - ComposerServiceAIDL::getComposerService()->scheduleComposite(); - return 0; -} - -int scheduleCommit([[maybe_unused]] int argc, [[maybe_unused]] char** argv) { - ComposerServiceAIDL::getComposerService()->scheduleCommit(); - return 0; -} - -int forceClientComposition(int argc, char** argv) { - bool enabled = true; - // A valid command looks like this: - // adb shell sfdo forceClientComposition enabled - if (argc >= 3) { - const ParseToggleResult toggle = parseToggle(argv[2]); - if (toggle == ParseToggleResult::kError) { - std::cerr << "Incorrect usage of forceClientComposition. " - "Missing [enabled | disabled].\n"; - return -1; - } - if (argc > 3) { - std::cerr << "Too many arguments after [enabled | disabled]. " - "Ignoring extra arguments.\n"; - } - enabled = (toggle == ParseToggleResult::kTrue); - } else { - std::cerr << "Incorrect usage of forceClientComposition. Missing [enabled | disabled].\n"; - return -1; - } - - ComposerServiceAIDL::getComposerService()->forceClientComposition(enabled); - return 0; -} - -int main(int argc, char** argv) { - std::cout << "Execute SurfaceFlinger internal commands.\n"; - std::cout << "sfdo requires to be run with root permissions..\n"; - - g_functions["frameRateIndicator"] = frameRateIndicator; - g_functions["debugFlash"] = debugFlash; - g_functions["scheduleComposite"] = scheduleComposite; - g_functions["scheduleCommit"] = scheduleCommit; - g_functions["forceClientComposition"] = forceClientComposition; - - if (argc > 1 && g_functions.find(argv[1]) != g_functions.end()) { - std::cout << "Running: " << argv[1] << "\n"; - const std::string key(argv[1]); - const auto fn = g_functions[key]; - int result = std::any_cast<int (*)(int, char**)>(fn)(argc, argv); - if (result == 0) { - std::cout << "Success.\n"; - } - return result; - } else { - ShowUsage(); - } - return 0; -}
\ No newline at end of file diff --git a/cmds/sfdo/sfdo.rs b/cmds/sfdo/sfdo.rs new file mode 100644 index 0000000000..863df6b7a0 --- /dev/null +++ b/cmds/sfdo/sfdo.rs @@ -0,0 +1,155 @@ +// 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. + +//! sfdo: Make surface flinger do things +use android_gui::{aidl::android::gui::ISurfaceComposer::ISurfaceComposer, binder}; +use clap::{Parser, Subcommand}; +use std::fmt::Debug; + +const SERVICE_IDENTIFIER: &str = "SurfaceFlingerAIDL"; + +fn print_result<T, E>(function_name: &str, res: Result<T, E>) +where + E: Debug, +{ + match res { + Ok(_) => println!("{}: Operation successful!", function_name), + Err(err) => println!("{}: Operation failed: {:?}", function_name, err), + } +} + +fn parse_toggle(toggle_value: &str) -> Option<bool> { + let positive = ["1", "true", "y", "yes", "on", "enabled", "show"]; + let negative = ["0", "false", "n", "no", "off", "disabled", "hide"]; + + let word = toggle_value.to_lowercase(); // Case-insensitive comparison + + if positive.contains(&word.as_str()) { + Some(true) + } else if negative.contains(&word.as_str()) { + Some(false) + } else { + None + } +} + +#[derive(Parser)] +#[command(version = "0.1", about = "Execute SurfaceFlinger internal commands.")] +#[command(propagate_version = true)] +struct Cli { + #[command(subcommand)] + command: Option<Commands>, +} + +#[derive(Subcommand, Debug)] +enum Commands { + #[command(about = "[optional(--delay)] Perform a debug flash.")] + DebugFlash { + #[arg(short, long, default_value_t = 0)] + delay: i32, + }, + + #[command( + about = "state = [enabled | disabled] When enabled, it disables Hardware Overlays, \ + and routes all window composition to the GPU. This can help check if \ + there is a bug in HW Composer." + )] + ForceClientComposition { state: Option<String> }, + + #[command(about = "state = [hide | show], displays the framerate in the top left corner.")] + FrameRateIndicator { state: Option<String> }, + + #[command(about = "Force composite ahead of next VSYNC.")] + ScheduleComposite, + + #[command(about = "Force commit ahead of next VSYNC.")] + ScheduleCommit, +} + +/// sfdo command line tool +/// +/// sfdo allows you to call different functions from the SurfaceComposer using +/// the adb shell. +fn main() { + binder::ProcessState::start_thread_pool(); + let composer_service = match binder::get_interface::<dyn ISurfaceComposer>(SERVICE_IDENTIFIER) { + Ok(service) => service, + Err(err) => { + eprintln!("Unable to connect to ISurfaceComposer: {}", err); + return; + } + }; + + let cli = Cli::parse(); + + match &cli.command { + Some(Commands::FrameRateIndicator { state }) => { + if let Some(op_state) = state { + let toggle = parse_toggle(op_state); + match toggle { + Some(true) => { + let res = composer_service.enableRefreshRateOverlay(true); + print_result("enableRefreshRateOverlay", res); + } + Some(false) => { + let res = composer_service.enableRefreshRateOverlay(false); + print_result("enableRefreshRateOverlay", res); + } + None => { + eprintln!("Invalid state: {}, choices are [hide | show]", op_state); + } + } + } else { + eprintln!("No state, choices are [hide | show]"); + } + } + Some(Commands::DebugFlash { delay }) => { + let res = composer_service.setDebugFlash(*delay); + print_result("setDebugFlash", res); + } + Some(Commands::ScheduleComposite) => { + let res = composer_service.scheduleComposite(); + print_result("scheduleComposite", res); + } + Some(Commands::ScheduleCommit) => { + let res = composer_service.scheduleCommit(); + print_result("scheduleCommit", res); + } + Some(Commands::ForceClientComposition { state }) => { + if let Some(op_state) = state { + let toggle = parse_toggle(op_state); + match toggle { + Some(true) => { + let res = composer_service.forceClientComposition(true); + print_result("forceClientComposition", res); + } + Some(false) => { + let res = composer_service.forceClientComposition(false); + print_result("forceClientComposition", res); + } + None => { + eprintln!("Invalid state: {}, choices are [enabled | disabled]", op_state); + } + } + } else { + eprintln!("No state, choices are [enabled | disabled]"); + } + } + None => { + println!("Execute SurfaceFlinger internal commands."); + println!("run `adb shell sfdo help` for more to view the commands."); + println!("run `adb shell sfdo [COMMAND] --help` for more info on the command."); + } + } +} diff --git a/data/etc/input/motion_predictor_config.xml b/data/etc/input/motion_predictor_config.xml index 39772aece2..c3f2fedc71 100644 --- a/data/etc/input/motion_predictor_config.xml +++ b/data/etc/input/motion_predictor_config.xml @@ -31,5 +31,11 @@ the UX issue mentioned above. --> <distance-noise-floor>0.2</distance-noise-floor> + <!-- The low and high jerk thresholds for prediction pruning. + + The jerk thresholds are based on normalized dt = 1 calculations. + --> + <low-jerk>1.0</low-jerk> + <high-jerk>1.1</high-jerk> </motion-predictor> diff --git a/include/android/surface_control.h b/include/android/surface_control.h index fb1419f0b2..dc9383afd7 100644 --- a/include/android/surface_control.h +++ b/include/android/surface_control.h @@ -307,7 +307,7 @@ void ASurfaceTransaction_reparent(ASurfaceTransaction* transaction, /** * Parameter for ASurfaceTransaction_setVisibility(). */ -enum { +enum ASurfaceTransactionVisibility : int8_t { ASURFACE_TRANSACTION_VISIBILITY_HIDE = 0, ASURFACE_TRANSACTION_VISIBILITY_SHOW = 1, }; @@ -319,7 +319,8 @@ enum { * Available since API level 29. */ void ASurfaceTransaction_setVisibility(ASurfaceTransaction* transaction, - ASurfaceControl* surface_control, int8_t visibility) + ASurfaceControl* surface_control, + enum ASurfaceTransactionVisibility visibility) __INTRODUCED_IN(29); /** @@ -362,7 +363,7 @@ void ASurfaceTransaction_setBuffer(ASurfaceTransaction* transaction, */ void ASurfaceTransaction_setColor(ASurfaceTransaction* transaction, ASurfaceControl* surface_control, float r, float g, float b, - float alpha, ADataSpace dataspace) + float alpha, enum ADataSpace dataspace) __INTRODUCED_IN(29); /** @@ -440,7 +441,7 @@ void ASurfaceTransaction_setScale(ASurfaceTransaction* transaction, /** * Parameter for ASurfaceTransaction_setBufferTransparency(). */ -enum { +enum ASurfaceTransactionTransparency : int8_t { ASURFACE_TRANSACTION_TRANSPARENCY_TRANSPARENT = 0, ASURFACE_TRANSACTION_TRANSPARENCY_TRANSLUCENT = 1, ASURFACE_TRANSACTION_TRANSPARENCY_OPAQUE = 2, @@ -454,7 +455,7 @@ enum { */ void ASurfaceTransaction_setBufferTransparency(ASurfaceTransaction* transaction, ASurfaceControl* surface_control, - int8_t transparency) + enum ASurfaceTransactionTransparency transparency) __INTRODUCED_IN(29); /** @@ -501,7 +502,7 @@ void ASurfaceTransaction_setBufferAlpha(ASurfaceTransaction* transaction, * Available since API level 29. */ void ASurfaceTransaction_setBufferDataSpace(ASurfaceTransaction* transaction, - ASurfaceControl* surface_control, ADataSpace data_space) + ASurfaceControl* surface_control, enum ADataSpace data_space) __INTRODUCED_IN(29); /** diff --git a/include/input/InputConsumer.h b/include/input/InputConsumer.h index 560e804c68..611478cbeb 100644 --- a/include/input/InputConsumer.h +++ b/include/input/InputConsumer.h @@ -111,6 +111,11 @@ private: std::shared_ptr<InputChannel> mChannel; + // TODO(b/311142655): delete this temporary tracing after the ANR bug is fixed + const std::string mProcessingTraceTag; + const std::string mLifetimeTraceTag; + const int32_t mLifetimeTraceCookie; + // The current input message. InputMessage mMsg; diff --git a/include/input/MotionPredictor.h b/include/input/MotionPredictor.h index 3b6e40183f..f71503988f 100644 --- a/include/input/MotionPredictor.h +++ b/include/input/MotionPredictor.h @@ -16,6 +16,7 @@ #pragma once +#include <array> #include <cstdint> #include <memory> #include <mutex> @@ -28,6 +29,7 @@ #include <android/sysprop/InputProperties.sysprop.h> #include <input/Input.h> #include <input/MotionPredictorMetricsManager.h> +#include <input/RingBuffer.h> #include <input/TfLiteMotionPredictor.h> #include <utils/Timers.h> // for nsecs_t @@ -37,6 +39,31 @@ static inline bool isMotionPredictionEnabled() { return sysprop::InputProperties::enable_motion_prediction().value_or(true); } +// Tracker to calculate jerk from motion position samples. +class JerkTracker { +public: + // Initialize the tracker. If normalizedDt is true, assume that each sample pushed has dt=1. + JerkTracker(bool normalizedDt); + + // Add a position to the tracker and update derivative estimates. + void pushSample(int64_t timestamp, float xPos, float yPos); + + // Reset JerkTracker for a new motion input. + void reset(); + + // Return last jerk calculation, if enough samples have been collected. + // Jerk is defined as the 3rd derivative of position (change in + // acceleration) and has the units of d^3p/dt^3. + std::optional<float> jerkMagnitude() const; + +private: + const bool mNormalizedDt; + + RingBuffer<int64_t> mTimestamps{4}; + std::array<float, 4> mXDerivatives{}; // [x, x', x'', x'''] + std::array<float, 4> mYDerivatives{}; // [y, y', y'', y'''] +}; + /** * Given a set of MotionEvents for the current gesture, predict the motion. The returned MotionEvent * contains a set of samples in the future. @@ -97,6 +124,11 @@ private: std::unique_ptr<TfLiteMotionPredictorBuffers> mBuffers; std::optional<MotionEvent> mLastEvent; + // mJerkTracker assumes normalized dt = 1 between recorded samples because + // the underlying mModel input also assumes fixed-interval samples. + // Normalized dt as 1 is also used to correspond with the similar Jank + // implementation from the JetPack MotionPredictor implementation. + JerkTracker mJerkTracker{true}; std::optional<MotionPredictorMetricsManager> mMetricsManager; diff --git a/include/input/TfLiteMotionPredictor.h b/include/input/TfLiteMotionPredictor.h index 2edc138f67..728a8e1e39 100644 --- a/include/input/TfLiteMotionPredictor.h +++ b/include/input/TfLiteMotionPredictor.h @@ -105,6 +105,11 @@ public: // The noise floor for predictions. // Distances (r) less than this should be discarded as noise. float distanceNoiseFloor = 0; + + // Low and high jerk thresholds (with normalized dt = 1) for predictions. + // High jerk means more predictions will be pruned, vice versa for low. + float lowJerk = 0; + float highJerk = 0; }; // Creates a model from an encoded Flatbuffer model. diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp index ca9b08f8ad..eec12e4cd5 100644 --- a/libs/binder/Android.bp +++ b/libs/binder/Android.bp @@ -161,7 +161,7 @@ cc_defaults { }, android: { lto: { - thin: true, + thin: true, }, }, }, diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp index 42dd6916c7..54457fc568 100644 --- a/libs/binder/BpBinder.cpp +++ b/libs/binder/BpBinder.cpp @@ -44,6 +44,7 @@ std::unordered_map<int32_t, uint32_t> BpBinder::sLastLimitCallbackMap; int BpBinder::sNumTrackedUids = 0; std::atomic_bool BpBinder::sCountByUidEnabled(false); binder_proxy_limit_callback BpBinder::sLimitCallback; +binder_proxy_warning_callback BpBinder::sWarningCallback; bool BpBinder::sBinderProxyThrottleCreate = false; static StaticString16 kDescriptorUninit(u""); @@ -52,6 +53,9 @@ static StaticString16 kDescriptorUninit(u""); uint32_t BpBinder::sBinderProxyCountHighWatermark = 2500; // Another arbitrary value a binder count needs to drop below before another callback will be called uint32_t BpBinder::sBinderProxyCountLowWatermark = 2000; +// Arbitrary value between low and high watermark on a bad behaving app to +// trigger a warning callback. +uint32_t BpBinder::sBinderProxyCountWarningWatermark = 2250; std::atomic<uint32_t> BpBinder::sBinderProxyCount(0); std::atomic<uint32_t> BpBinder::sBinderProxyCountWarned(0); @@ -63,7 +67,8 @@ static constexpr uint32_t kBinderProxyCountWarnInterval = 5000; enum { LIMIT_REACHED_MASK = 0x80000000, // A flag denoting that the limit has been reached - COUNTING_VALUE_MASK = 0x7FFFFFFF, // A mask of the remaining bits for the count value + WARNING_REACHED_MASK = 0x40000000, // A flag denoting that the warning has been reached + COUNTING_VALUE_MASK = 0x3FFFFFFF, // A mask of the remaining bits for the count value }; BpBinder::ObjectManager::ObjectManager() @@ -181,7 +186,13 @@ sp<BpBinder> BpBinder::create(int32_t handle) { sLastLimitCallbackMap[trackedUid] = trackedValue; } } else { - if ((trackedValue & COUNTING_VALUE_MASK) >= sBinderProxyCountHighWatermark) { + uint32_t currentValue = trackedValue & COUNTING_VALUE_MASK; + if (currentValue >= sBinderProxyCountWarningWatermark + && currentValue < sBinderProxyCountHighWatermark + && ((trackedValue & WARNING_REACHED_MASK) == 0)) [[unlikely]] { + sTrackingMap[trackedUid] |= WARNING_REACHED_MASK; + if (sWarningCallback) sWarningCallback(trackedUid); + } else if (currentValue >= sBinderProxyCountHighWatermark) { ALOGE("Too many binder proxy objects sent to uid %d from uid %d (%d proxies held)", getuid(), trackedUid, trackedValue); sTrackingMap[trackedUid] |= LIMIT_REACHED_MASK; @@ -609,11 +620,11 @@ BpBinder::~BpBinder() { binderHandle()); } else { auto countingValue = trackedValue & COUNTING_VALUE_MASK; - if ((trackedValue & LIMIT_REACHED_MASK) && + if ((trackedValue & (LIMIT_REACHED_MASK | WARNING_REACHED_MASK)) && (countingValue <= sBinderProxyCountLowWatermark)) [[unlikely]] { ALOGI("Limit reached bit reset for uid %d (fewer than %d proxies from uid %d held)", getuid(), sBinderProxyCountLowWatermark, mTrackedUid); - sTrackingMap[mTrackedUid] &= ~LIMIT_REACHED_MASK; + sTrackingMap[mTrackedUid] &= ~(LIMIT_REACHED_MASK | WARNING_REACHED_MASK); sLastLimitCallbackMap.erase(mTrackedUid); } if (--sTrackingMap[mTrackedUid] == 0) { @@ -730,15 +741,18 @@ void BpBinder::enableCountByUid() { sCountByUidEnabled.store(true); } void BpBinder::disableCountByUid() { sCountByUidEnabled.store(false); } void BpBinder::setCountByUidEnabled(bool enable) { sCountByUidEnabled.store(enable); } -void BpBinder::setLimitCallback(binder_proxy_limit_callback cb) { +void BpBinder::setBinderProxyCountEventCallback(binder_proxy_limit_callback cbl, + binder_proxy_warning_callback cbw) { RpcMutexUniqueLock _l(sTrackingLock); - sLimitCallback = cb; + sLimitCallback = std::move(cbl); + sWarningCallback = std::move(cbw); } -void BpBinder::setBinderProxyCountWatermarks(int high, int low) { +void BpBinder::setBinderProxyCountWatermarks(int high, int low, int warning) { RpcMutexUniqueLock _l(sTrackingLock); sBinderProxyCountHighWatermark = high; sBinderProxyCountLowWatermark = low; + sBinderProxyCountWarningWatermark = warning; } // --------------------------------------------------------------------------- diff --git a/libs/binder/include/binder/BpBinder.h b/libs/binder/include/binder/BpBinder.h index 89a4d273ad..9f0390769b 100644 --- a/libs/binder/include/binder/BpBinder.h +++ b/libs/binder/include/binder/BpBinder.h @@ -35,7 +35,8 @@ class Stability; } class ProcessState; -using binder_proxy_limit_callback = void(*)(int); +using binder_proxy_limit_callback = std::function<void(int)>; +using binder_proxy_warning_callback = std::function<void(int)>; class BpBinder : public IBinder { @@ -86,8 +87,9 @@ public: static void enableCountByUid(); static void disableCountByUid(); static void setCountByUidEnabled(bool enable); - static void setLimitCallback(binder_proxy_limit_callback cb); - static void setBinderProxyCountWatermarks(int high, int low); + static void setBinderProxyCountEventCallback(binder_proxy_limit_callback cbl, + binder_proxy_warning_callback cbw); + static void setBinderProxyCountWatermarks(int high, int low, int warning); static uint32_t getBinderProxyCount(); std::optional<int32_t> getDebugBinderHandle() const; @@ -212,6 +214,8 @@ private: static std::unordered_map<int32_t,uint32_t> sLastLimitCallbackMap; static std::atomic<uint32_t> sBinderProxyCount; static std::atomic<uint32_t> sBinderProxyCountWarned; + static binder_proxy_warning_callback sWarningCallback; + static uint32_t sBinderProxyCountWarningWatermark; }; } // namespace android diff --git a/libs/binder/ndk/Android.bp b/libs/binder/ndk/Android.bp index 30dbdddc60..2a8a353518 100644 --- a/libs/binder/ndk/Android.bp +++ b/libs/binder/ndk/Android.bp @@ -50,6 +50,7 @@ cc_library { ], cflags: [ + "-DBINDER_WITH_KERNEL_IPC", "-Wall", "-Wextra", "-Werror", diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp index e2ede3f52a..34a86f2ea8 100644 --- a/libs/binder/ndk/ibinder.cpp +++ b/libs/binder/ndk/ibinder.cpp @@ -24,6 +24,7 @@ #include <private/android_filesystem_config.h> #endif +#include "../BuildFlags.h" #include "ibinder_internal.h" #include "parcel_internal.h" #include "status_internal.h" @@ -211,6 +212,12 @@ status_t ABBinder::onTransact(transaction_code_t code, const Parcel& data, Parce binder_status_t status = getClass()->onTransact(this, code, &in, &out); return PruneStatusT(status); } else if (code == SHELL_COMMAND_TRANSACTION && getClass()->handleShellCommand != nullptr) { + if constexpr (!android::kEnableKernelIpc) { + // Non-IPC builds do not have getCallingUid(), + // so we have no way of authenticating the caller + return STATUS_PERMISSION_DENIED; + } + int in = data.readFileDescriptor(); int out = data.readFileDescriptor(); int err = data.readFileDescriptor(); diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h index 52edae4a38..c665ad82ad 100644 --- a/libs/binder/ndk/include_platform/android/binder_manager.h +++ b/libs/binder/ndk/include_platform/android/binder_manager.h @@ -18,9 +18,12 @@ #include <android/binder_ibinder.h> #include <android/binder_status.h> -#include <android/llndk-versioning.h> #include <sys/cdefs.h> +#ifndef __TRUSTY__ +#include <android/llndk-versioning.h> +#endif + __BEGIN_DECLS enum AServiceManager_AddServiceFlag : uint32_t { diff --git a/libs/binder/ndk/stability.cpp b/libs/binder/ndk/stability.cpp index ca3d5e6cf3..39cf1c4124 100644 --- a/libs/binder/ndk/stability.cpp +++ b/libs/binder/ndk/stability.cpp @@ -23,7 +23,7 @@ using ::android::internal::Stability; -#ifdef __ANDROID_VNDK__ +#if defined(__ANDROID_VNDK__) && !defined(__TRUSTY__) #error libbinder_ndk should only be built in a system context #endif diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs index 16049f28c5..0540ed379b 100644 --- a/libs/binder/rust/src/lib.rs +++ b/libs/binder/rust/src/lib.rs @@ -101,6 +101,8 @@ mod native; mod parcel; mod proxy; #[cfg(not(target_os = "trusty"))] +mod service; +#[cfg(not(target_os = "trusty"))] mod state; use binder_ndk_sys as sys; @@ -108,14 +110,13 @@ use binder_ndk_sys as sys; pub use crate::binder_async::{BinderAsyncPool, BoxFuture}; pub use binder::{BinderFeatures, FromIBinder, IBinder, Interface, Strong, Weak}; pub use error::{ExceptionCode, IntoBinderResult, Status, StatusCode}; -pub use native::{ - add_service, force_lazy_services_persist, is_handling_transaction, register_lazy_service, - LazyServiceGuard, -}; pub use parcel::{ParcelFileDescriptor, Parcelable, ParcelableHolder}; -pub use proxy::{ - get_declared_instances, get_interface, get_service, is_declared, wait_for_interface, - wait_for_service, DeathRecipient, SpIBinder, WpIBinder, +pub use proxy::{DeathRecipient, SpIBinder, WpIBinder}; +#[cfg(not(target_os = "trusty"))] +pub use service::{ + add_service, force_lazy_services_persist, get_declared_instances, get_interface, get_service, + is_declared, is_handling_transaction, register_lazy_service, wait_for_interface, + wait_for_service, LazyServiceGuard, }; #[cfg(not(target_os = "trusty"))] pub use state::{ProcessState, ThreadState}; diff --git a/libs/binder/rust/src/native.rs b/libs/binder/rust/src/native.rs index 8ae010ea88..da9d7dc948 100644 --- a/libs/binder/rust/src/native.rs +++ b/libs/binder/rust/src/native.rs @@ -23,12 +23,11 @@ use crate::proxy::SpIBinder; use crate::sys; use std::convert::TryFrom; -use std::ffi::{c_void, CStr, CString}; +use std::ffi::{c_void, CStr}; use std::io::Write; use std::mem::ManuallyDrop; use std::ops::Deref; use std::os::raw::c_char; -use std::sync::Mutex; /// Rust wrapper around Binder remotable objects. /// @@ -462,110 +461,6 @@ unsafe impl<B: Remotable> AsNative<sys::AIBinder> for Binder<B> { } } -/// Register a new service with the default service manager. -/// -/// Registers the given binder object with the given identifier. If successful, -/// this service can then be retrieved using that identifier. -/// -/// This function will panic if the identifier contains a 0 byte (NUL). -pub fn add_service(identifier: &str, mut binder: SpIBinder) -> Result<()> { - let instance = CString::new(identifier).unwrap(); - let status = - // Safety: `AServiceManager_addService` expects valid `AIBinder` and C - // string pointers. Caller retains ownership of both pointers. - // `AServiceManager_addService` creates a new strong reference and copies - // the string, so both pointers need only be valid until the call returns. - unsafe { sys::AServiceManager_addService(binder.as_native_mut(), instance.as_ptr()) }; - status_result(status) -} - -/// Register a dynamic service via the LazyServiceRegistrar. -/// -/// Registers the given binder object with the given identifier. If successful, -/// this service can then be retrieved using that identifier. The service process -/// will be shut down once all registered services are no longer in use. -/// -/// If any service in the process is registered as lazy, all should be, otherwise -/// the process may be shut down while a service is in use. -/// -/// This function will panic if the identifier contains a 0 byte (NUL). -pub fn register_lazy_service(identifier: &str, mut binder: SpIBinder) -> Result<()> { - let instance = CString::new(identifier).unwrap(); - // Safety: `AServiceManager_registerLazyService` expects valid `AIBinder` and C - // string pointers. Caller retains ownership of both - // pointers. `AServiceManager_registerLazyService` creates a new strong reference - // and copies the string, so both pointers need only be valid until the - // call returns. - let status = unsafe { - sys::AServiceManager_registerLazyService(binder.as_native_mut(), instance.as_ptr()) - }; - status_result(status) -} - -/// Prevent a process which registers lazy services from being shut down even when none -/// of the services is in use. -/// -/// If persist is true then shut down will be blocked until this function is called again with -/// persist false. If this is to be the initial state, call this function before calling -/// register_lazy_service. -/// -/// Consider using [`LazyServiceGuard`] rather than calling this directly. -pub fn force_lazy_services_persist(persist: bool) { - // Safety: No borrowing or transfer of ownership occurs here. - unsafe { sys::AServiceManager_forceLazyServicesPersist(persist) } -} - -/// An RAII object to ensure a process which registers lazy services is not killed. During the -/// lifetime of any of these objects the service manager will not not kill the process even if none -/// of its lazy services are in use. -#[must_use] -#[derive(Debug)] -pub struct LazyServiceGuard { - // Prevent construction outside this module. - _private: (), -} - -// Count of how many LazyServiceGuard objects are in existence. -static GUARD_COUNT: Mutex<u64> = Mutex::new(0); - -impl LazyServiceGuard { - /// Create a new LazyServiceGuard to prevent the service manager prematurely killing this - /// process. - pub fn new() -> Self { - let mut count = GUARD_COUNT.lock().unwrap(); - *count += 1; - if *count == 1 { - // It's important that we make this call with the mutex held, to make sure - // that multiple calls (e.g. if the count goes 1 -> 0 -> 1) are correctly - // sequenced. (That also means we can't just use an AtomicU64.) - force_lazy_services_persist(true); - } - Self { _private: () } - } -} - -impl Drop for LazyServiceGuard { - fn drop(&mut self) { - let mut count = GUARD_COUNT.lock().unwrap(); - *count -= 1; - if *count == 0 { - force_lazy_services_persist(false); - } - } -} - -impl Clone for LazyServiceGuard { - fn clone(&self) -> Self { - Self::new() - } -} - -impl Default for LazyServiceGuard { - fn default() -> Self { - Self::new() - } -} - /// Tests often create a base BBinder instance; so allowing the unit /// type to be remotable translates nicely to Binder::new(()). impl Remotable for () { @@ -590,10 +485,3 @@ impl Remotable for () { } impl Interface for () {} - -/// Determine whether the current thread is currently executing an incoming -/// transaction. -pub fn is_handling_transaction() -> bool { - // Safety: This method is always safe to call. - unsafe { sys::AIBinder_isHandlingTransaction() } -} diff --git a/libs/binder/rust/src/proxy.rs b/libs/binder/rust/src/proxy.rs index 7434e9ddbd..340014aeaa 100644 --- a/libs/binder/rust/src/proxy.rs +++ b/libs/binder/rust/src/proxy.rs @@ -29,11 +29,10 @@ use crate::sys; use std::cmp::Ordering; use std::convert::TryInto; -use std::ffi::{c_void, CStr, CString}; +use std::ffi::{c_void, CString}; use std::fmt; use std::mem; use std::os::fd::AsRawFd; -use std::os::raw::c_char; use std::ptr; use std::sync::Arc; @@ -129,14 +128,6 @@ impl SpIBinder { } } -fn interface_cast<T: FromIBinder + ?Sized>(service: Option<SpIBinder>) -> Result<Strong<T>> { - if let Some(service) = service { - FromIBinder::try_from(service) - } else { - Err(StatusCode::NAME_NOT_FOUND) - } -} - pub mod unstable_api { use super::{sys, SpIBinder}; @@ -739,93 +730,6 @@ unsafe impl<T: Proxy> AsNative<sys::AIBinder> for T { } } -/// Retrieve an existing service, blocking for a few seconds if it doesn't yet -/// exist. -pub fn get_service(name: &str) -> Option<SpIBinder> { - let name = CString::new(name).ok()?; - // Safety: `AServiceManager_getService` returns either a null pointer or a - // valid pointer to an owned `AIBinder`. Either of these values is safe to - // pass to `SpIBinder::from_raw`. - unsafe { SpIBinder::from_raw(sys::AServiceManager_getService(name.as_ptr())) } -} - -/// Retrieve an existing service, or start it if it is configured as a dynamic -/// service and isn't yet started. -pub fn wait_for_service(name: &str) -> Option<SpIBinder> { - let name = CString::new(name).ok()?; - // Safety: `AServiceManager_waitforService` returns either a null pointer or - // a valid pointer to an owned `AIBinder`. Either of these values is safe to - // pass to `SpIBinder::from_raw`. - unsafe { SpIBinder::from_raw(sys::AServiceManager_waitForService(name.as_ptr())) } -} - -/// Retrieve an existing service for a particular interface, blocking for a few -/// seconds if it doesn't yet exist. -pub fn get_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> { - interface_cast(get_service(name)) -} - -/// Retrieve an existing service for a particular interface, or start it if it -/// is configured as a dynamic service and isn't yet started. -pub fn wait_for_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> { - interface_cast(wait_for_service(name)) -} - -/// Check if a service is declared (e.g. in a VINTF manifest) -pub fn is_declared(interface: &str) -> Result<bool> { - let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?; - - // Safety: `interface` is a valid null-terminated C-style string and is only - // borrowed for the lifetime of the call. The `interface` local outlives - // this call as it lives for the function scope. - unsafe { Ok(sys::AServiceManager_isDeclared(interface.as_ptr())) } -} - -/// Retrieve all declared instances for a particular interface -/// -/// For instance, if 'android.foo.IFoo/foo' is declared, and 'android.foo.IFoo' -/// is passed here, then ["foo"] would be returned. -pub fn get_declared_instances(interface: &str) -> Result<Vec<String>> { - unsafe extern "C" fn callback(instance: *const c_char, opaque: *mut c_void) { - // Safety: opaque was a mutable pointer created below from a Vec of - // CString, and outlives this callback. The null handling here is just - // to avoid the possibility of unwinding across C code if this crate is - // ever compiled with panic=unwind. - if let Some(instances) = unsafe { opaque.cast::<Vec<CString>>().as_mut() } { - // Safety: instance is a valid null-terminated C string with a - // lifetime at least as long as this function, and we immediately - // copy it into an owned CString. - unsafe { - instances.push(CStr::from_ptr(instance).to_owned()); - } - } else { - eprintln!("Opaque pointer was null in get_declared_instances callback!"); - } - } - - let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?; - let mut instances: Vec<CString> = vec![]; - // Safety: `interface` and `instances` are borrowed for the length of this - // call and both outlive the call. `interface` is guaranteed to be a valid - // null-terminated C-style string. - unsafe { - sys::AServiceManager_forEachDeclaredInstance( - interface.as_ptr(), - &mut instances as *mut _ as *mut c_void, - Some(callback), - ); - } - - instances - .into_iter() - .map(CString::into_string) - .collect::<std::result::Result<Vec<String>, _>>() - .map_err(|e| { - eprintln!("An interface instance name was not a valid UTF-8 string: {}", e); - StatusCode::BAD_VALUE - }) -} - /// Safety: `SpIBinder` guarantees that `binder` always contains a valid pointer /// to an `AIBinder`, so we can trivially extract this pointer here. unsafe impl AsNative<sys::AIBinder> for SpIBinder { diff --git a/libs/binder/rust/src/service.rs b/libs/binder/rust/src/service.rs new file mode 100644 index 0000000000..3ca3b540c4 --- /dev/null +++ b/libs/binder/rust/src/service.rs @@ -0,0 +1,230 @@ +/* + * 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. + */ + +use crate::binder::{AsNative, FromIBinder, Strong}; +use crate::error::{status_result, Result, StatusCode}; +use crate::proxy::SpIBinder; +use crate::sys; + +use std::ffi::{c_void, CStr, CString}; +use std::os::raw::c_char; +use std::sync::Mutex; + +/// Register a new service with the default service manager. +/// +/// Registers the given binder object with the given identifier. If successful, +/// this service can then be retrieved using that identifier. +/// +/// This function will panic if the identifier contains a 0 byte (NUL). +pub fn add_service(identifier: &str, mut binder: SpIBinder) -> Result<()> { + let instance = CString::new(identifier).unwrap(); + let status = + // Safety: `AServiceManager_addService` expects valid `AIBinder` and C + // string pointers. Caller retains ownership of both pointers. + // `AServiceManager_addService` creates a new strong reference and copies + // the string, so both pointers need only be valid until the call returns. + unsafe { sys::AServiceManager_addService(binder.as_native_mut(), instance.as_ptr()) }; + status_result(status) +} + +/// Register a dynamic service via the LazyServiceRegistrar. +/// +/// Registers the given binder object with the given identifier. If successful, +/// this service can then be retrieved using that identifier. The service process +/// will be shut down once all registered services are no longer in use. +/// +/// If any service in the process is registered as lazy, all should be, otherwise +/// the process may be shut down while a service is in use. +/// +/// This function will panic if the identifier contains a 0 byte (NUL). +pub fn register_lazy_service(identifier: &str, mut binder: SpIBinder) -> Result<()> { + let instance = CString::new(identifier).unwrap(); + // Safety: `AServiceManager_registerLazyService` expects valid `AIBinder` and C + // string pointers. Caller retains ownership of both + // pointers. `AServiceManager_registerLazyService` creates a new strong reference + // and copies the string, so both pointers need only be valid until the + // call returns. + let status = unsafe { + sys::AServiceManager_registerLazyService(binder.as_native_mut(), instance.as_ptr()) + }; + status_result(status) +} + +/// Prevent a process which registers lazy services from being shut down even when none +/// of the services is in use. +/// +/// If persist is true then shut down will be blocked until this function is called again with +/// persist false. If this is to be the initial state, call this function before calling +/// register_lazy_service. +/// +/// Consider using [`LazyServiceGuard`] rather than calling this directly. +pub fn force_lazy_services_persist(persist: bool) { + // Safety: No borrowing or transfer of ownership occurs here. + unsafe { sys::AServiceManager_forceLazyServicesPersist(persist) } +} + +/// An RAII object to ensure a process which registers lazy services is not killed. During the +/// lifetime of any of these objects the service manager will not kill the process even if none +/// of its lazy services are in use. +#[must_use] +#[derive(Debug)] +pub struct LazyServiceGuard { + // Prevent construction outside this module. + _private: (), +} + +// Count of how many LazyServiceGuard objects are in existence. +static GUARD_COUNT: Mutex<u64> = Mutex::new(0); + +impl LazyServiceGuard { + /// Create a new LazyServiceGuard to prevent the service manager prematurely killing this + /// process. + pub fn new() -> Self { + let mut count = GUARD_COUNT.lock().unwrap(); + *count += 1; + if *count == 1 { + // It's important that we make this call with the mutex held, to make sure + // that multiple calls (e.g. if the count goes 1 -> 0 -> 1) are correctly + // sequenced. (That also means we can't just use an AtomicU64.) + force_lazy_services_persist(true); + } + Self { _private: () } + } +} + +impl Drop for LazyServiceGuard { + fn drop(&mut self) { + let mut count = GUARD_COUNT.lock().unwrap(); + *count -= 1; + if *count == 0 { + force_lazy_services_persist(false); + } + } +} + +impl Clone for LazyServiceGuard { + fn clone(&self) -> Self { + Self::new() + } +} + +impl Default for LazyServiceGuard { + fn default() -> Self { + Self::new() + } +} + +/// Determine whether the current thread is currently executing an incoming +/// transaction. +pub fn is_handling_transaction() -> bool { + // Safety: This method is always safe to call. + unsafe { sys::AIBinder_isHandlingTransaction() } +} + +fn interface_cast<T: FromIBinder + ?Sized>(service: Option<SpIBinder>) -> Result<Strong<T>> { + if let Some(service) = service { + FromIBinder::try_from(service) + } else { + Err(StatusCode::NAME_NOT_FOUND) + } +} + +/// Retrieve an existing service, blocking for a few seconds if it doesn't yet +/// exist. +pub fn get_service(name: &str) -> Option<SpIBinder> { + let name = CString::new(name).ok()?; + // Safety: `AServiceManager_getService` returns either a null pointer or a + // valid pointer to an owned `AIBinder`. Either of these values is safe to + // pass to `SpIBinder::from_raw`. + unsafe { SpIBinder::from_raw(sys::AServiceManager_getService(name.as_ptr())) } +} + +/// Retrieve an existing service, or start it if it is configured as a dynamic +/// service and isn't yet started. +pub fn wait_for_service(name: &str) -> Option<SpIBinder> { + let name = CString::new(name).ok()?; + // Safety: `AServiceManager_waitforService` returns either a null pointer or + // a valid pointer to an owned `AIBinder`. Either of these values is safe to + // pass to `SpIBinder::from_raw`. + unsafe { SpIBinder::from_raw(sys::AServiceManager_waitForService(name.as_ptr())) } +} + +/// Retrieve an existing service for a particular interface, blocking for a few +/// seconds if it doesn't yet exist. +pub fn get_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> { + interface_cast(get_service(name)) +} + +/// Retrieve an existing service for a particular interface, or start it if it +/// is configured as a dynamic service and isn't yet started. +pub fn wait_for_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> { + interface_cast(wait_for_service(name)) +} + +/// Check if a service is declared (e.g. in a VINTF manifest) +pub fn is_declared(interface: &str) -> Result<bool> { + let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?; + + // Safety: `interface` is a valid null-terminated C-style string and is only + // borrowed for the lifetime of the call. The `interface` local outlives + // this call as it lives for the function scope. + unsafe { Ok(sys::AServiceManager_isDeclared(interface.as_ptr())) } +} + +/// Retrieve all declared instances for a particular interface +/// +/// For instance, if 'android.foo.IFoo/foo' is declared, and 'android.foo.IFoo' +/// is passed here, then ["foo"] would be returned. +pub fn get_declared_instances(interface: &str) -> Result<Vec<String>> { + unsafe extern "C" fn callback(instance: *const c_char, opaque: *mut c_void) { + // Safety: opaque was a mutable pointer created below from a Vec of + // CString, and outlives this callback. The null handling here is just + // to avoid the possibility of unwinding across C code if this crate is + // ever compiled with panic=unwind. + if let Some(instances) = unsafe { opaque.cast::<Vec<CString>>().as_mut() } { + // Safety: instance is a valid null-terminated C string with a + // lifetime at least as long as this function, and we immediately + // copy it into an owned CString. + unsafe { + instances.push(CStr::from_ptr(instance).to_owned()); + } + } else { + eprintln!("Opaque pointer was null in get_declared_instances callback!"); + } + } + + let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?; + let mut instances: Vec<CString> = vec![]; + // Safety: `interface` and `instances` are borrowed for the length of this + // call and both outlive the call. `interface` is guaranteed to be a valid + // null-terminated C-style string. + unsafe { + sys::AServiceManager_forEachDeclaredInstance( + interface.as_ptr(), + &mut instances as *mut _ as *mut c_void, + Some(callback), + ); + } + + instances + .into_iter() + .map(CString::into_string) + .collect::<std::result::Result<Vec<String>, _>>() + .map_err(|e| { + eprintln!("An interface instance name was not a valid UTF-8 string: {}", e); + StatusCode::BAD_VALUE + }) +} diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp index 0ee96e7317..2cea14f631 100644 --- a/libs/binder/tests/binderLibTest.cpp +++ b/libs/binder/tests/binderLibTest.cpp @@ -115,6 +115,7 @@ enum BinderLibTestTranscationCode { BINDER_LIB_TEST_GET_SCHEDULING_POLICY, BINDER_LIB_TEST_NOP_TRANSACTION_WAIT, BINDER_LIB_TEST_GETPID, + BINDER_LIB_TEST_GETUID, BINDER_LIB_TEST_ECHO_VECTOR, BINDER_LIB_TEST_GET_NON_BLOCKING_FD, BINDER_LIB_TEST_REJECT_OBJECTS, @@ -1477,6 +1478,86 @@ TEST_F(BinderLibTest, BinderProxyCount) { EXPECT_EQ(BpBinder::getBinderProxyCount(), initialCount); } +static constexpr int kBpCountHighWatermark = 20; +static constexpr int kBpCountLowWatermark = 10; +static constexpr int kBpCountWarningWatermark = 15; +static constexpr int kInvalidUid = -1; + +TEST_F(BinderLibTest, BinderProxyCountCallback) { + Parcel data, reply; + sp<IBinder> server = addServer(); + ASSERT_NE(server, nullptr); + + BpBinder::enableCountByUid(); + EXPECT_THAT(m_server->transact(BINDER_LIB_TEST_GETUID, data, &reply), StatusEq(NO_ERROR)); + int32_t uid = reply.readInt32(); + ASSERT_NE(uid, kInvalidUid); + + uint32_t initialCount = BpBinder::getBinderProxyCount(); + { + uint32_t count = initialCount; + BpBinder::setBinderProxyCountWatermarks(kBpCountHighWatermark, + kBpCountLowWatermark, + kBpCountWarningWatermark); + int limitCallbackUid = kInvalidUid; + int warningCallbackUid = kInvalidUid; + BpBinder::setBinderProxyCountEventCallback([&](int uid) { limitCallbackUid = uid; }, + [&](int uid) { warningCallbackUid = uid; }); + + std::vector<sp<IBinder> > proxies; + auto createProxyOnce = [&](int expectedWarningCallbackUid, int expectedLimitCallbackUid) { + warningCallbackUid = limitCallbackUid = kInvalidUid; + ASSERT_THAT(server->transact(BINDER_LIB_TEST_CREATE_BINDER_TRANSACTION, data, &reply), + StatusEq(NO_ERROR)); + proxies.push_back(reply.readStrongBinder()); + EXPECT_EQ(BpBinder::getBinderProxyCount(), ++count); + EXPECT_EQ(warningCallbackUid, expectedWarningCallbackUid); + EXPECT_EQ(limitCallbackUid, expectedLimitCallbackUid); + }; + auto removeProxyOnce = [&](int expectedWarningCallbackUid, int expectedLimitCallbackUid) { + warningCallbackUid = limitCallbackUid = kInvalidUid; + proxies.pop_back(); + EXPECT_EQ(BpBinder::getBinderProxyCount(), --count); + EXPECT_EQ(warningCallbackUid, expectedWarningCallbackUid); + EXPECT_EQ(limitCallbackUid, expectedLimitCallbackUid); + }; + + // Test the increment/decrement of the binder proxies. + for (int i = 1; i <= kBpCountWarningWatermark; i++) { + createProxyOnce(kInvalidUid, kInvalidUid); + } + createProxyOnce(uid, kInvalidUid); // Warning callback should have been triggered. + for (int i = kBpCountWarningWatermark + 2; i <= kBpCountHighWatermark; i++) { + createProxyOnce(kInvalidUid, kInvalidUid); + } + createProxyOnce(kInvalidUid, uid); // Limit callback should have been triggered. + createProxyOnce(kInvalidUid, kInvalidUid); + for (int i = kBpCountHighWatermark + 2; i >= kBpCountHighWatermark; i--) { + removeProxyOnce(kInvalidUid, kInvalidUid); + } + createProxyOnce(kInvalidUid, kInvalidUid); + + // Go down below the low watermark. + for (int i = kBpCountHighWatermark; i >= kBpCountLowWatermark; i--) { + removeProxyOnce(kInvalidUid, kInvalidUid); + } + for (int i = kBpCountLowWatermark; i <= kBpCountWarningWatermark; i++) { + createProxyOnce(kInvalidUid, kInvalidUid); + } + createProxyOnce(uid, kInvalidUid); // Warning callback should have been triggered. + for (int i = kBpCountWarningWatermark + 2; i <= kBpCountHighWatermark; i++) { + createProxyOnce(kInvalidUid, kInvalidUid); + } + createProxyOnce(kInvalidUid, uid); // Limit callback should have been triggered. + createProxyOnce(kInvalidUid, kInvalidUid); + for (int i = kBpCountHighWatermark + 2; i >= kBpCountHighWatermark; i--) { + removeProxyOnce(kInvalidUid, kInvalidUid); + } + createProxyOnce(kInvalidUid, kInvalidUid); + } + EXPECT_EQ(BpBinder::getBinderProxyCount(), initialCount); +} + class BinderLibRpcTestBase : public BinderLibTest { public: void SetUp() override { @@ -1680,6 +1761,9 @@ public: case BINDER_LIB_TEST_GETPID: reply->writeInt32(getpid()); return NO_ERROR; + case BINDER_LIB_TEST_GETUID: + reply->writeInt32(getuid()); + return NO_ERROR; case BINDER_LIB_TEST_NOP_TRANSACTION_WAIT: usleep(5000); [[fallthrough]]; diff --git a/libs/binder/tests/binderRpcTestCommon.h b/libs/binder/tests/binderRpcTestCommon.h index 62fe9e56f6..8832f1a6ba 100644 --- a/libs/binder/tests/binderRpcTestCommon.h +++ b/libs/binder/tests/binderRpcTestCommon.h @@ -74,6 +74,12 @@ static inline std::vector<RpcSecurity> RpcSecurityValues() { } static inline bool hasExperimentalRpc() { +#ifdef BINDER_RPC_TO_TRUSTY_TEST + // Trusty services do not support the experimental version, + // so that we can update the prebuilts separately. + // This covers the binderRpcToTrustyTest case on Android. + return false; +#endif #ifdef __ANDROID__ return base::GetProperty("ro.build.version.codename", "") != "REL"; #else diff --git a/libs/binder/tests/binderRpcUniversalTests.cpp b/libs/binder/tests/binderRpcUniversalTests.cpp index 885bb45d82..f4807806ad 100644 --- a/libs/binder/tests/binderRpcUniversalTests.cpp +++ b/libs/binder/tests/binderRpcUniversalTests.cpp @@ -48,11 +48,13 @@ TEST(BinderRpc, CannotUseNextWireVersion) { EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT + 15)); } +#ifndef BINDER_RPC_TO_TRUSTY_TEST TEST(BinderRpc, CanUseExperimentalWireVersion) { auto session = RpcSession::make(); EXPECT_EQ(hasExperimentalRpc(), session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL)); } +#endif TEST_P(BinderRpc, Ping) { auto proc = createRpcTestSocketServerProcess({}); diff --git a/libs/binder/tests/parcel_fuzzer/main.cpp b/libs/binder/tests/parcel_fuzzer/main.cpp index 5b1e9eac23..a57d07fb24 100644 --- a/libs/binder/tests/parcel_fuzzer/main.cpp +++ b/libs/binder/tests/parcel_fuzzer/main.cpp @@ -46,7 +46,18 @@ void fillRandomParcel(::android::hardware::Parcel* p, FuzzedDataProvider&& provi (void)options; std::vector<uint8_t> input = provider.ConsumeRemainingBytes<uint8_t>(); - p->setData(input.data(), input.size()); + + if (input.size() % 4 != 0) { + input.resize(input.size() + (sizeof(uint32_t) - input.size() % sizeof(uint32_t))); + } + CHECK_EQ(0, input.size() % 4); + + p->setDataCapacity(input.size()); + for (size_t i = 0; i < input.size(); i += 4) { + p->writeInt32(*((int32_t*)(input.data() + i))); + } + + CHECK_EQ(0, memcmp(input.data(), p->data(), p->dataSize())); } static void fillRandomParcel(NdkParcelAdapter* p, FuzzedDataProvider&& provider, RandomParcelOptions* options) { diff --git a/libs/binder/tests/unit_fuzzers/BpBinderFuzzFunctions.h b/libs/binder/tests/unit_fuzzers/BpBinderFuzzFunctions.h index 0a584bfe56..83d0ca745f 100644 --- a/libs/binder/tests/unit_fuzzers/BpBinderFuzzFunctions.h +++ b/libs/binder/tests/unit_fuzzers/BpBinderFuzzFunctions.h @@ -95,14 +95,16 @@ static const std::vector<std::function<void(FuzzedDataProvider*, const sp<BpBind }, [](FuzzedDataProvider*, const sp<BpBinder>& bpbinder, const sp<IBinder::DeathRecipient>&) -> void { - binder_proxy_limit_callback cb = binder_proxy_limit_callback(); - bpbinder->setLimitCallback(cb); + binder_proxy_limit_callback cbl = binder_proxy_limit_callback(); + binder_proxy_warning_callback cbw = binder_proxy_warning_callback(); + bpbinder->setBinderProxyCountEventCallback(cbl, cbw); }, [](FuzzedDataProvider* fdp, const sp<BpBinder>& bpbinder, const sp<IBinder::DeathRecipient>&) -> void { int high = fdp->ConsumeIntegral<int>(); int low = fdp->ConsumeIntegral<int>(); - bpbinder->setBinderProxyCountWatermarks(high, low); + int warning = fdp->ConsumeIntegral<int>(); + bpbinder->setBinderProxyCountWatermarks(high, low, warning); }}; } // namespace android diff --git a/libs/binder/trusty/ndk/include/sys/cdefs.h b/libs/binder/trusty/ndk/include/sys/cdefs.h index 6a48d2b776..eabfe603bd 100644 --- a/libs/binder/trusty/ndk/include/sys/cdefs.h +++ b/libs/binder/trusty/ndk/include/sys/cdefs.h @@ -22,3 +22,4 @@ #define __END_DECLS __END_CDECLS #define __INTRODUCED_IN(x) /* nothing on Trusty */ +#define __INTRODUCED_IN_LLNDK(x) /* nothing on Trusty */ diff --git a/libs/binder/trusty/ndk/rules.mk b/libs/binder/trusty/ndk/rules.mk index 03fd006fd2..7a275c2696 100644 --- a/libs/binder/trusty/ndk/rules.mk +++ b/libs/binder/trusty/ndk/rules.mk @@ -23,6 +23,7 @@ MODULE_SRCS := \ $(LIBBINDER_NDK_DIR)/ibinder.cpp \ $(LIBBINDER_NDK_DIR)/libbinder.cpp \ $(LIBBINDER_NDK_DIR)/parcel.cpp \ + $(LIBBINDER_NDK_DIR)/stability.cpp \ $(LIBBINDER_NDK_DIR)/status.cpp \ MODULE_EXPORT_INCLUDES += \ diff --git a/libs/binderdebug/BinderDebug.cpp b/libs/binderdebug/BinderDebug.cpp index a8f2cbfb5b..19f3aad04f 100644 --- a/libs/binderdebug/BinderDebug.cpp +++ b/libs/binderdebug/BinderDebug.cpp @@ -199,4 +199,31 @@ status_t getBinderClientPids(BinderDebugContext context, pid_t pid, pid_t servic return ret; } +status_t getBinderTransactions(pid_t pid, std::string& transactionsOutput) { + std::ifstream ifs("/dev/binderfs/binder_logs/transactions"); + if (!ifs.is_open()) { + ifs.open("/d/binder/transactions"); + if (!ifs.is_open()) { + LOG(ERROR) << "Could not open /dev/binderfs/binder_logs/transactions. " + << "Likely a permissions issue. errno: " << errno; + return -errno; + } + } + + std::string line; + while (getline(ifs, line)) { + // The section for this pid ends with another "proc <pid>" for another + // process. There is only one entry per pid so we can stop looking after + // we've grabbed the whole section + if (base::StartsWith(line, "proc " + std::to_string(pid))) { + do { + transactionsOutput += line + '\n'; + } while (getline(ifs, line) && !base::StartsWith(line, "proc ")); + return OK; + } + } + + return NAME_NOT_FOUND; +} + } // namespace android diff --git a/libs/binderdebug/include/binderdebug/BinderDebug.h b/libs/binderdebug/include/binderdebug/BinderDebug.h index 6ce8edfc7c..018393c128 100644 --- a/libs/binderdebug/include/binderdebug/BinderDebug.h +++ b/libs/binderdebug/include/binderdebug/BinderDebug.h @@ -44,4 +44,12 @@ status_t getBinderPidInfo(BinderDebugContext context, pid_t pid, BinderPidInfo* status_t getBinderClientPids(BinderDebugContext context, pid_t pid, pid_t servicePid, int32_t handle, std::vector<pid_t>* pids); +/** + * Get the transactions for a given process from /dev/binderfs/binder_logs/transactions + * Return: OK if the file was found and the pid was found in the file. + * -errno if there was an issue opening the file + * NAME_NOT_FOUND if the pid wasn't found in the file + */ +status_t getBinderTransactions(pid_t pid, std::string& transactionOutput); + } // namespace android diff --git a/libs/binderthreadstate/test.cpp b/libs/binderthreadstate/test.cpp index b5c4010c7a..e888b0aea8 100644 --- a/libs/binderthreadstate/test.cpp +++ b/libs/binderthreadstate/test.cpp @@ -22,6 +22,7 @@ #include <binderthreadstateutilstest/1.0/IHidlStuff.h> #include <gtest/gtest.h> #include <hidl/HidlTransportSupport.h> +#include <hidl/ServiceManagement.h> #include <hwbinder/IPCThreadState.h> #include <thread> @@ -37,6 +38,7 @@ using android::OK; using android::sp; using android::String16; using android::binder::Status; +using android::hardware::isHidlSupported; using android::hardware::Return; using binderthreadstateutilstest::V1_0::IHidlStuff; @@ -67,6 +69,7 @@ std::string id2name(size_t id) { // complicated calls are possible, but this should do here. static void callHidl(size_t id, int32_t idx) { + CHECK_EQ(true, isHidlSupported()) << "We shouldn't be calling HIDL if it's not supported"; auto stuff = IHidlStuff::getService(id2name(id)); CHECK(stuff->call(idx).isOk()); } @@ -174,6 +177,7 @@ TEST(BinderThreadState, DoesntInitializeBinderDriver) { } TEST(BindThreadState, RemoteHidlCall) { + if (!isHidlSupported()) GTEST_SKIP() << "No HIDL support on device"; auto stuff = IHidlStuff::getService(id2name(kP1Id)); ASSERT_NE(nullptr, stuff); ASSERT_TRUE(stuff->call(0).isOk()); @@ -186,11 +190,14 @@ TEST(BindThreadState, RemoteAidlCall) { } TEST(BindThreadState, RemoteNestedStartHidlCall) { + if (!isHidlSupported()) GTEST_SKIP() << "No HIDL support on device"; auto stuff = IHidlStuff::getService(id2name(kP1Id)); ASSERT_NE(nullptr, stuff); ASSERT_TRUE(stuff->call(100).isOk()); } TEST(BindThreadState, RemoteNestedStartAidlCall) { + // this test case is trying ot nest a HIDL call which requires HIDL support + if (!isHidlSupported()) GTEST_SKIP() << "No HIDL support on device"; sp<IAidlStuff> stuff; ASSERT_EQ(OK, android::getService<IAidlStuff>(String16(id2name(kP1Id).c_str()), &stuff)); ASSERT_NE(nullptr, stuff); @@ -205,11 +212,15 @@ int server(size_t thisId, size_t otherId) { defaultServiceManager()->addService(String16(id2name(thisId).c_str()), aidlServer)); android::ProcessState::self()->startThreadPool(); - // HIDL - android::hardware::configureRpcThreadpool(1, true /*callerWillJoin*/); - sp<IHidlStuff> hidlServer = new HidlServer(thisId, otherId); - CHECK_EQ(OK, hidlServer->registerAsService(id2name(thisId).c_str())); - android::hardware::joinRpcThreadpool(); + if (isHidlSupported()) { + // HIDL + android::hardware::configureRpcThreadpool(1, true /*callerWillJoin*/); + sp<IHidlStuff> hidlServer = new HidlServer(thisId, otherId); + CHECK_EQ(OK, hidlServer->registerAsService(id2name(thisId).c_str())); + android::hardware::joinRpcThreadpool(); + } else { + android::IPCThreadState::self()->joinThreadPool(true); + } return EXIT_FAILURE; } @@ -227,9 +238,15 @@ int main(int argc, char** argv) { } android::waitForService<IAidlStuff>(String16(id2name(kP1Id).c_str())); - android::hardware::details::waitForHwService(IHidlStuff::descriptor, id2name(kP1Id).c_str()); + if (isHidlSupported()) { + android::hardware::details::waitForHwService(IHidlStuff::descriptor, + id2name(kP1Id).c_str()); + } android::waitForService<IAidlStuff>(String16(id2name(kP2Id).c_str())); - android::hardware::details::waitForHwService(IHidlStuff::descriptor, id2name(kP2Id).c_str()); + if (isHidlSupported()) { + android::hardware::details::waitForHwService(IHidlStuff::descriptor, + id2name(kP2Id).c_str()); + } return RUN_ALL_TESTS(); } diff --git a/libs/debugstore/OWNERS b/libs/debugstore/OWNERS new file mode 100644 index 0000000000..428a1a2215 --- /dev/null +++ b/libs/debugstore/OWNERS @@ -0,0 +1,3 @@ +benmiles@google.com +gaillard@google.com +mohamadmahmoud@google.com diff --git a/libs/debugstore/rust/Android.bp b/libs/debugstore/rust/Android.bp new file mode 100644 index 0000000000..55ba3c32d1 --- /dev/null +++ b/libs/debugstore/rust/Android.bp @@ -0,0 +1,71 @@ +// 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 { + default_team: "trendy_team_android_telemetry_infra", + default_applicable_licenses: ["frameworks_native_license"], +} + +rust_defaults { + name: "libdebugstore_defaults", + srcs: ["src/lib.rs"], + rustlibs: [ + "libcrossbeam_queue", + "libparking_lot", + "libonce_cell", + "libcxx", + ], + shared_libs: ["libutils"], + edition: "2021", +} + +rust_ffi_static { + name: "libdebugstore_rust_ffi", + crate_name: "debugstore", + defaults: ["libdebugstore_defaults"], +} + +cc_library { + name: "libdebugstore_cxx", + generated_headers: ["libdebugstore_cxx_bridge_header"], + generated_sources: ["libdebugstore_cxx_bridge_code"], + export_generated_headers: ["libdebugstore_cxx_bridge_header"], + shared_libs: ["libutils"], + whole_static_libs: ["libdebugstore_rust_ffi"], +} + +rust_test { + name: "libdebugstore_tests", + defaults: ["libdebugstore_defaults"], + test_options: { + unit_test: true, + }, + shared_libs: ["libdebugstore_cxx"], +} + +genrule { + name: "libdebugstore_cxx_bridge_header", + tools: ["cxxbridge"], + cmd: "$(location cxxbridge) $(in) --header >> $(out)", + srcs: ["src/lib.rs"], + out: ["debugstore/debugstore_cxx_bridge.rs.h"], +} + +genrule { + name: "libdebugstore_cxx_bridge_code", + tools: ["cxxbridge"], + cmd: "$(location cxxbridge) $(in) >> $(out)", + srcs: ["src/lib.rs"], + out: ["debugstore/debugstore_cxx_bridge.rs.cpp"], +} diff --git a/libs/debugstore/rust/Cargo.toml b/libs/debugstore/rust/Cargo.toml new file mode 100644 index 0000000000..23a8d24647 --- /dev/null +++ b/libs/debugstore/rust/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "debugstore" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies]
\ No newline at end of file diff --git a/libs/debugstore/rust/src/core.rs b/libs/debugstore/rust/src/core.rs new file mode 100644 index 0000000000..1dfa512151 --- /dev/null +++ b/libs/debugstore/rust/src/core.rs @@ -0,0 +1,198 @@ +/* + * 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. + */ +use super::event::Event; +use super::event_type::EventType; +use super::storage::Storage; +use crate::cxxffi::uptimeMillis; +use once_cell::sync::Lazy; +use std::fmt; +use std::sync::atomic::{AtomicU64, Ordering}; + +// Lazily initialized static instance of DebugStore. +static INSTANCE: Lazy<DebugStore> = Lazy::new(DebugStore::new); + +/// The `DebugStore` struct is responsible for managing debug events and data. +pub struct DebugStore { + /// Atomic counter for generating unique event IDs. + id_generator: AtomicU64, + /// Non-blocking storage for debug events. + event_store: Storage<Event, { DebugStore::DEFAULT_EVENT_LIMIT }>, +} + +impl DebugStore { + /// The default limit for the number of events that can be stored. + /// + /// This limit is used to initialize the storage for debug events. + const DEFAULT_EVENT_LIMIT: usize = 16; + /// A designated identifier used for events that cannot be closed. + /// + /// This ID is used for point/instantaneous events, or events do not have + /// a distinct end. + const NON_CLOSABLE_ID: u64 = 0; + /// The version number for the encoding of debug store data. + /// + /// This constant is used as a part of the debug store's data format, + /// allowing for version tracking and compatibility checks. + const ENCODE_VERSION: u32 = 1; + + /// Creates a new instance of `DebugStore` with specified event limit and maximum delay. + fn new() -> Self { + Self { id_generator: AtomicU64::new(1), event_store: Storage::new() } + } + + /// Returns a shared instance of `DebugStore`. + /// + /// This method provides a singleton pattern access to `DebugStore`. + pub fn get_instance() -> &'static DebugStore { + &INSTANCE + } + + /// Begins a new debug event with the given name and data. + /// + /// This method logs the start of a debug event, assigning it a unique ID and marking its start time. + /// - `name`: The name of the debug event. + /// - `data`: Associated data as key-value pairs. + /// - Returns: A unique ID for the debug event. + pub fn begin(&self, name: String, data: Vec<(String, String)>) -> u64 { + let id = self.generate_id(); + self.event_store.insert(Event::new( + id, + Some(name), + uptimeMillis(), + EventType::DurationStart, + data, + )); + id + } + + /// Records a debug event without a specific duration, with the given name and data. + /// + /// This method logs an instantaneous debug event, useful for events that don't have a duration but are significant. + /// - `name`: The name of the debug event. + /// - `data`: Associated data as key-value pairs. + pub fn record(&self, name: String, data: Vec<(String, String)>) { + self.event_store.insert(Event::new( + Self::NON_CLOSABLE_ID, + Some(name), + uptimeMillis(), + EventType::Point, + data, + )); + } + + /// Ends a debug event that was previously started with the given ID. + /// + /// This method marks the end of a debug event, completing its lifecycle. + /// - `id`: The unique ID of the debug event to end. + /// - `data`: Additional data to log at the end of the event. + pub fn end(&self, id: u64, data: Vec<(String, String)>) { + if id != Self::NON_CLOSABLE_ID { + self.event_store.insert(Event::new( + id, + None, + uptimeMillis(), + EventType::DurationEnd, + data, + )); + } + } + + fn generate_id(&self) -> u64 { + let mut id = self.id_generator.fetch_add(1, Ordering::Relaxed); + while id == Self::NON_CLOSABLE_ID { + id = self.id_generator.fetch_add(1, Ordering::Relaxed); + } + id + } +} + +impl fmt::Display for DebugStore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let uptime_now = uptimeMillis(); + write!(f, "{},{},{}::", Self::ENCODE_VERSION, self.event_store.len(), uptime_now)?; + + write!( + f, + "{}", + self.event_store.fold(String::new(), |mut acc, event| { + if !acc.is_empty() { + acc.push_str("||"); + } + acc.push_str(&event.to_string()); + acc + }) + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_begin_event() { + let debug_store = DebugStore::new(); + let _event_id = debug_store.begin("test_event".to_string(), vec![]); + let output = debug_store.to_string(); + assert!( + output.contains("test_event"), + "The output should contain the event name 'test_event'" + ); + } + + #[test] + fn test_unique_event_ids() { + let debug_store = DebugStore::new(); + let event_id1 = debug_store.begin("event1".to_string(), vec![]); + let event_id2 = debug_store.begin("event2".to_string(), vec![]); + assert_ne!(event_id1, event_id2, "Event IDs should be unique"); + } + + #[test] + fn test_end_event() { + let debug_store = DebugStore::new(); + let event_id = debug_store.begin("test_event".to_string(), vec![]); + debug_store.end(event_id, vec![]); + let output = debug_store.to_string(); + + let id_pattern = format!("ID:{},", event_id); + assert!( + output.contains("test_event"), + "The output should contain the event name 'test_event'" + ); + assert_eq!( + output.matches(&id_pattern).count(), + 2, + "The output should contain two events (start and end) associated with the given ID" + ); + } + + #[test] + fn test_event_data_handling() { + let debug_store = DebugStore::new(); + debug_store + .record("data_event".to_string(), vec![("key".to_string(), "value".to_string())]); + let output = debug_store.to_string(); + assert!( + output.contains("data_event"), + "The output should contain the event name 'data_event'" + ); + assert!( + output.contains("key=value"), + "The output should contain the event data 'key=value'" + ); + } +} diff --git a/libs/debugstore/rust/src/event.rs b/libs/debugstore/rust/src/event.rs new file mode 100644 index 0000000000..0c16665df4 --- /dev/null +++ b/libs/debugstore/rust/src/event.rs @@ -0,0 +1,72 @@ +/* + * 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. + */ + +use super::event_type::EventType; +use std::fmt; + +/// Represents a single debug event within the Debug Store system. +/// +/// It contains all the necessary information for a debug event. +#[derive(Clone)] +pub struct Event { + /// The unique identifier for this event. + pub id: u64, + /// The optional name of the event. + pub name: Option<String>, + /// The system uptime when the event occurred. + pub timestamp: i64, + /// The type of the event. + pub event_type: EventType, + /// Additional data associated with the event, stored in the given order as key-value pairs. + data: Vec<(String, String)>, +} + +impl Event { + /// Constructs a new `Event`. + /// + /// - `id`: The unique identifier for the event. + /// - `name`: An optional name for the event. + /// - `timestamp`: The system uptime when the event occurred. + /// - `event_type`: The type of the event. + /// - `data`: Additional data for the event, represented as ordered key-value pairs. + pub fn new( + id: u64, + name: Option<String>, + timestamp: i64, + event_type: EventType, + data: Vec<(String, String)>, + ) -> Self { + Self { id, name, timestamp, event_type, data } + } +} + +impl fmt::Display for Event { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "ID:{},C:{},T:{}", self.id, self.event_type, self.timestamp)?; + + if let Some(ref name) = self.name { + write!(f, ",N:{}", name)?; + } + + if !self.data.is_empty() { + let data_str = + self.data.iter().map(|(k, v)| format!("{}={}", k, v)).collect::<Vec<_>>().join(";"); + write!(f, ",D:{}", data_str)?; + } + + Ok(()) + } +} diff --git a/libs/debugstore/rust/src/event_type.rs b/libs/debugstore/rust/src/event_type.rs new file mode 100644 index 0000000000..6f4bafb58a --- /dev/null +++ b/libs/debugstore/rust/src/event_type.rs @@ -0,0 +1,43 @@ +/* + * 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. + */ +use std::fmt; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum EventType { + /// Marks the an unknown or invalid event, for convenient mapping to a protobuf enum. + Invalid, + /// Marks the beginning of a duration-based event, indicating the start of a timed operation. + DurationStart, + /// Marks the end of a duration-based event, indicating the end of a timed operation. + DurationEnd, + /// Represents a single, instantaneous event with no duration. + Point, +} + +impl fmt::Display for EventType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + EventType::Invalid => "I", + EventType::DurationStart => "S", + EventType::DurationEnd => "E", + EventType::Point => "P", + } + ) + } +} diff --git a/libs/debugstore/rust/src/lib.rs b/libs/debugstore/rust/src/lib.rs new file mode 100644 index 0000000000..f2195c0529 --- /dev/null +++ b/libs/debugstore/rust/src/lib.rs @@ -0,0 +1,73 @@ +/* + * 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. + */ + +//! # Debug Store Crate +/// The Debug Store Crate provides functionalities for storing debug events. +/// It allows logging and retrieval of debug events, and associated data. +mod core; +mod event; +mod event_type; +mod storage; + +pub use core::*; +pub use event::*; + +use cxx::{CxxString, CxxVector}; + +#[cxx::bridge(namespace = "android::debugstore")] +#[allow(unsafe_op_in_unsafe_fn)] +mod cxxffi { + extern "Rust" { + fn debug_store_to_string() -> String; + fn debug_store_record(name: &CxxString, data: &CxxVector<CxxString>); + fn debug_store_begin(name: &CxxString, data: &CxxVector<CxxString>) -> u64; + fn debug_store_end(id: u64, data: &CxxVector<CxxString>); + } + + #[namespace = "android"] + unsafe extern "C++" { + include!("utils/SystemClock.h"); + fn uptimeMillis() -> i64; + } +} + +fn debug_store_to_string() -> String { + DebugStore::get_instance().to_string() +} + +fn debug_store_record(name: &CxxString, data: &CxxVector<CxxString>) { + DebugStore::get_instance().record(name.to_string_lossy().into_owned(), cxx_vec_to_pairs(data)); +} + +fn debug_store_begin(name: &CxxString, data: &CxxVector<CxxString>) -> u64 { + DebugStore::get_instance().begin(name.to_string_lossy().into_owned(), cxx_vec_to_pairs(data)) +} + +fn debug_store_end(id: u64, data: &CxxVector<CxxString>) { + DebugStore::get_instance().end(id, cxx_vec_to_pairs(data)); +} + +fn cxx_vec_to_pairs(vec: &CxxVector<CxxString>) -> Vec<(String, String)> { + vec.iter() + .map(|s| s.to_string_lossy().into_owned()) + .collect::<Vec<_>>() + .chunks(2) + .filter_map(|chunk| match chunk { + [k, v] => Some((k.clone(), v.clone())), + _ => None, + }) + .collect() +} diff --git a/libs/debugstore/rust/src/storage.rs b/libs/debugstore/rust/src/storage.rs new file mode 100644 index 0000000000..2ad7f4e0b4 --- /dev/null +++ b/libs/debugstore/rust/src/storage.rs @@ -0,0 +1,134 @@ +/* + * 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. + */ + +use crossbeam_queue::ArrayQueue; + +/// A thread-safe storage that allows non-blocking attempts to store and visit elements. +pub struct Storage<T, const N: usize> { + insertion_buffer: ArrayQueue<T>, +} + +impl<T, const N: usize> Storage<T, N> { + /// Creates a new Storage with the specified size. + pub fn new() -> Self { + Self { insertion_buffer: ArrayQueue::new(N) } + } + + /// Inserts a value into the storage, returning an error if the lock cannot be acquired. + pub fn insert(&self, value: T) { + self.insertion_buffer.force_push(value); + } + + /// Folds over the elements in the storage using the provided function. + pub fn fold<U, F>(&self, init: U, mut func: F) -> U + where + F: FnMut(U, &T) -> U, + { + let mut acc = init; + while let Some(value) = self.insertion_buffer.pop() { + acc = func(acc, &value); + } + acc + } + + /// Returns the number of elements that have been inserted into the storage. + pub fn len(&self) -> usize { + self.insertion_buffer.len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_insert_and_retrieve() { + let storage = Storage::<i32, 10>::new(); + storage.insert(7); + + let sum = storage.fold(0, |acc, &x| acc + x); + assert_eq!(sum, 7, "The sum of the elements should be equal to the inserted value."); + } + + #[test] + fn test_fold_functionality() { + let storage = Storage::<i32, 5>::new(); + storage.insert(1); + storage.insert(2); + storage.insert(3); + + let sum = storage.fold(0, |acc, &x| acc + x); + assert_eq!( + sum, 6, + "The sum of the elements should be equal to the sum of inserted values." + ); + } + + #[test] + fn test_insert_and_retrieve_multiple_values() { + let storage = Storage::<i32, 10>::new(); + storage.insert(1); + storage.insert(2); + storage.insert(5); + + let first_sum = storage.fold(0, |acc, &x| acc + x); + assert_eq!(first_sum, 8, "The sum of the elements should be equal to the inserted values."); + + storage.insert(30); + storage.insert(22); + + let second_sum = storage.fold(0, |acc, &x| acc + x); + assert_eq!( + second_sum, 52, + "The sum of the elements should be equal to the inserted values." + ); + } + + #[test] + fn test_storage_limit() { + let storage = Storage::<i32, 1>::new(); + storage.insert(1); + // This value should overwrite the previously inserted value (1). + storage.insert(4); + let sum = storage.fold(0, |acc, &x| acc + x); + assert_eq!(sum, 4, "The sum of the elements should be equal to the inserted values."); + } + + #[test] + fn test_concurrent_insertions() { + use std::sync::Arc; + use std::thread; + + let storage = Arc::new(Storage::<i32, 100>::new()); + let threads: Vec<_> = (0..10) + .map(|_| { + let storage_clone = Arc::clone(&storage); + thread::spawn(move || { + for i in 0..10 { + storage_clone.insert(i); + } + }) + }) + .collect(); + + for thread in threads { + thread.join().expect("Thread should finish without panicking"); + } + + let count = storage.fold(0, |acc, _| acc + 1); + assert_eq!(count, 100, "Storage should be filled to its limit with concurrent insertions."); + } +} diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index 70cb36bad3..d1b2c5f025 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -155,9 +155,9 @@ cc_library_static { }, } -aidl_library { - name: "libgui_aidl_hdrs", - hdrs: [ +filegroup { + name: "libgui_extra_aidl_files", + srcs: [ "android/gui/DisplayInfo.aidl", "android/gui/FocusRequest.aidl", "android/gui/InputApplicationInfo.aidl", @@ -170,11 +170,34 @@ aidl_library { ], } +filegroup { + name: "libgui_extra_unstructured_aidl_files", + srcs: [ + "android/gui/DisplayInfo.aidl", + "android/gui/InputApplicationInfo.aidl", + "android/gui/WindowInfo.aidl", + "android/gui/WindowInfosUpdate.aidl", + ], +} + +aidl_library { + name: "libgui_aidl_hdrs", + hdrs: [":libgui_extra_aidl_files"], +} + +aidl_library { + name: "libgui_extra_unstructured_aidl_hdrs", + hdrs: [":libgui_extra_unstructured_aidl_files"], +} + aidl_library { name: "libgui_aidl", srcs: ["aidl/**/*.aidl"], strip_import_prefix: "aidl", - deps: ["libgui_aidl_hdrs"], + deps: [ + "libgui_aidl_hdrs", + "libgui_extra_unstructured_aidl_hdrs", + ], } filegroup { @@ -241,7 +264,6 @@ filegroup { "IProducerListener.cpp", "ISurfaceComposer.cpp", "ITransactionCompletedListener.cpp", - "LayerDebugInfo.cpp", "LayerMetadata.cpp", "LayerStatePermissions.cpp", "LayerState.cpp", diff --git a/libs/gui/LayerDebugInfo.cpp b/libs/gui/LayerDebugInfo.cpp deleted file mode 100644 index 15b2221464..0000000000 --- a/libs/gui/LayerDebugInfo.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2017 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 <gui/LayerDebugInfo.h> - -#include <android-base/stringprintf.h> - -#include <ui/DebugUtils.h> - -#include <binder/Parcel.h> - -using namespace android; -using android::base::StringAppendF; - -#define RETURN_ON_ERROR(X) do {status_t res = (X); if (res != NO_ERROR) return res;} while(false) - -namespace android::gui { - -status_t LayerDebugInfo::writeToParcel(Parcel* parcel) const { - RETURN_ON_ERROR(parcel->writeCString(mName.c_str())); - RETURN_ON_ERROR(parcel->writeCString(mParentName.c_str())); - RETURN_ON_ERROR(parcel->writeCString(mType.c_str())); - RETURN_ON_ERROR(parcel->write(mTransparentRegion)); - RETURN_ON_ERROR(parcel->write(mVisibleRegion)); - RETURN_ON_ERROR(parcel->write(mSurfaceDamageRegion)); - RETURN_ON_ERROR(parcel->writeUint32(mLayerStack)); - RETURN_ON_ERROR(parcel->writeFloat(mX)); - RETURN_ON_ERROR(parcel->writeFloat(mY)); - RETURN_ON_ERROR(parcel->writeUint32(mZ)); - RETURN_ON_ERROR(parcel->writeInt32(mWidth)); - RETURN_ON_ERROR(parcel->writeInt32(mHeight)); - RETURN_ON_ERROR(parcel->write(mCrop)); - RETURN_ON_ERROR(parcel->writeFloat(mColor.r)); - RETURN_ON_ERROR(parcel->writeFloat(mColor.g)); - RETURN_ON_ERROR(parcel->writeFloat(mColor.b)); - RETURN_ON_ERROR(parcel->writeFloat(mColor.a)); - RETURN_ON_ERROR(parcel->writeUint32(mFlags)); - RETURN_ON_ERROR(parcel->writeInt32(mPixelFormat)); - RETURN_ON_ERROR(parcel->writeUint32(static_cast<uint32_t>(mDataSpace))); - for (size_t index = 0; index < 4; index++) { - RETURN_ON_ERROR(parcel->writeFloat(mMatrix[index / 2][index % 2])); - } - RETURN_ON_ERROR(parcel->writeInt32(mActiveBufferWidth)); - RETURN_ON_ERROR(parcel->writeInt32(mActiveBufferHeight)); - RETURN_ON_ERROR(parcel->writeInt32(mActiveBufferStride)); - RETURN_ON_ERROR(parcel->writeInt32(mActiveBufferFormat)); - RETURN_ON_ERROR(parcel->writeInt32(mNumQueuedFrames)); - RETURN_ON_ERROR(parcel->writeBool(mIsOpaque)); - RETURN_ON_ERROR(parcel->writeBool(mContentDirty)); - RETURN_ON_ERROR(parcel->write(mStretchEffect)); - return NO_ERROR; -} - -status_t LayerDebugInfo::readFromParcel(const Parcel* parcel) { - mName = parcel->readCString(); - RETURN_ON_ERROR(parcel->errorCheck()); - mParentName = parcel->readCString(); - RETURN_ON_ERROR(parcel->errorCheck()); - mType = parcel->readCString(); - RETURN_ON_ERROR(parcel->errorCheck()); - RETURN_ON_ERROR(parcel->read(mTransparentRegion)); - RETURN_ON_ERROR(parcel->read(mVisibleRegion)); - RETURN_ON_ERROR(parcel->read(mSurfaceDamageRegion)); - RETURN_ON_ERROR(parcel->readUint32(&mLayerStack)); - RETURN_ON_ERROR(parcel->readFloat(&mX)); - RETURN_ON_ERROR(parcel->readFloat(&mY)); - RETURN_ON_ERROR(parcel->readUint32(&mZ)); - RETURN_ON_ERROR(parcel->readInt32(&mWidth)); - RETURN_ON_ERROR(parcel->readInt32(&mHeight)); - RETURN_ON_ERROR(parcel->read(mCrop)); - mColor.r = parcel->readFloat(); - RETURN_ON_ERROR(parcel->errorCheck()); - mColor.g = parcel->readFloat(); - RETURN_ON_ERROR(parcel->errorCheck()); - mColor.b = parcel->readFloat(); - RETURN_ON_ERROR(parcel->errorCheck()); - mColor.a = parcel->readFloat(); - RETURN_ON_ERROR(parcel->errorCheck()); - RETURN_ON_ERROR(parcel->readUint32(&mFlags)); - RETURN_ON_ERROR(parcel->readInt32(&mPixelFormat)); - // \todo [2017-07-25 kraita]: Static casting mDataSpace pointer to an uint32 does work. Better ways? - mDataSpace = static_cast<android_dataspace>(parcel->readUint32()); - RETURN_ON_ERROR(parcel->errorCheck()); - for (size_t index = 0; index < 4; index++) { - RETURN_ON_ERROR(parcel->readFloat(&mMatrix[index / 2][index % 2])); - } - RETURN_ON_ERROR(parcel->readInt32(&mActiveBufferWidth)); - RETURN_ON_ERROR(parcel->readInt32(&mActiveBufferHeight)); - RETURN_ON_ERROR(parcel->readInt32(&mActiveBufferStride)); - RETURN_ON_ERROR(parcel->readInt32(&mActiveBufferFormat)); - RETURN_ON_ERROR(parcel->readInt32(&mNumQueuedFrames)); - RETURN_ON_ERROR(parcel->readBool(&mIsOpaque)); - RETURN_ON_ERROR(parcel->readBool(&mContentDirty)); - RETURN_ON_ERROR(parcel->read(mStretchEffect)); - return NO_ERROR; -} - -std::string to_string(const LayerDebugInfo& info) { - std::string result; - - StringAppendF(&result, "+ %s (%s)\n", info.mType.c_str(), info.mName.c_str()); - info.mTransparentRegion.dump(result, "TransparentRegion"); - info.mVisibleRegion.dump(result, "VisibleRegion"); - info.mSurfaceDamageRegion.dump(result, "SurfaceDamageRegion"); - if (info.mStretchEffect.hasEffect()) { - const auto& se = info.mStretchEffect; - StringAppendF(&result, - " StretchEffect width = %f, height = %f vec=(%f, %f) " - "maxAmount=(%f, %f)\n", - se.width, se.height, - se.vectorX, se.vectorY, se.maxAmountX, se.maxAmountY); - } - - StringAppendF(&result, " layerStack=%4d, z=%9d, pos=(%g,%g), size=(%4d,%4d), ", - info.mLayerStack, info.mZ, static_cast<double>(info.mX), - static_cast<double>(info.mY), info.mWidth, info.mHeight); - - StringAppendF(&result, "crop=%s, ", to_string(info.mCrop).c_str()); - StringAppendF(&result, "isOpaque=%1d, invalidate=%1d, ", info.mIsOpaque, info.mContentDirty); - StringAppendF(&result, "dataspace=%s, ", dataspaceDetails(info.mDataSpace).c_str()); - StringAppendF(&result, "pixelformat=%s, ", decodePixelFormat(info.mPixelFormat).c_str()); - StringAppendF(&result, "color=(%.3f,%.3f,%.3f,%.3f), flags=0x%08x, ", - static_cast<double>(info.mColor.r), static_cast<double>(info.mColor.g), - static_cast<double>(info.mColor.b), static_cast<double>(info.mColor.a), - info.mFlags); - StringAppendF(&result, "tr=[%.2f, %.2f][%.2f, %.2f]", static_cast<double>(info.mMatrix[0][0]), - static_cast<double>(info.mMatrix[0][1]), static_cast<double>(info.mMatrix[1][0]), - static_cast<double>(info.mMatrix[1][1])); - result.append("\n"); - StringAppendF(&result, " parent=%s\n", info.mParentName.c_str()); - StringAppendF(&result, " activeBuffer=[%4ux%4u:%4u,%s],", info.mActiveBufferWidth, - info.mActiveBufferHeight, info.mActiveBufferStride, - decodePixelFormat(info.mActiveBufferFormat).c_str()); - StringAppendF(&result, " queued-frames=%d", info.mNumQueuedFrames); - result.append("\n"); - return result; -} - -} // namespace android::gui diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 1d2ea3ea02..eb945ccce0 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2443,7 +2443,6 @@ status_t SurfaceComposerClient::createSurfaceChecked(const String8& name, uint32 const sp<IBinder>& parentHandle, LayerMetadata metadata, uint32_t* outTransformHint) { - sp<SurfaceControl> sur; status_t err = mStatus; if (mStatus == NO_ERROR) { diff --git a/libs/gui/aidl/Android.bp b/libs/gui/aidl/Android.bp new file mode 100644 index 0000000000..8ed08c2644 --- /dev/null +++ b/libs/gui/aidl/Android.bp @@ -0,0 +1,85 @@ +// 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_native_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_native_license"], + default_team: "trendy_team_android_core_graphics_stack", +} + +filegroup { + name: "libgui_unstructured_aidl_files", + srcs: [ + ":libgui_extra_unstructured_aidl_files", + + "android/gui/BitTube.aidl", + "android/gui/CaptureArgs.aidl", + "android/gui/DisplayCaptureArgs.aidl", + "android/gui/LayerCaptureArgs.aidl", + "android/gui/LayerMetadata.aidl", + "android/gui/ParcelableVsyncEventData.aidl", + "android/gui/ScreenCaptureResults.aidl", + ], +} + +aidl_library { + name: "libgui_unstructured_aidl", + hdrs: [":libgui_unstructured_aidl_files"], +} + +filegroup { + name: "libgui_interface_aidl_files", + srcs: [ + ":libgui_extra_aidl_files", + "**/*.aidl", + ], + exclude_srcs: [":libgui_unstructured_aidl_files"], +} + +aidl_interface { + name: "android.gui", + unstable: true, + srcs: [ + ":libgui_interface_aidl_files", + ], + include_dirs: [ + "frameworks/native/libs/gui", + "frameworks/native/libs/gui/aidl", + ], + headers: [ + "libgui_aidl_hdrs", + "libgui_extra_unstructured_aidl_hdrs", + ], + backend: { + rust: { + enabled: true, + additional_rustlibs: [ + "libgui_aidl_types_rs", + ], + }, + java: { + enabled: false, + }, + cpp: { + enabled: false, + }, + ndk: { + enabled: false, + }, + }, +} diff --git a/libs/gui/aidl/android/gui/BitTube.aidl b/libs/gui/aidl/android/gui/BitTube.aidl index 6b0595ec66..eb231c1c9f 100644 --- a/libs/gui/aidl/android/gui/BitTube.aidl +++ b/libs/gui/aidl/android/gui/BitTube.aidl @@ -16,4 +16,4 @@ package android.gui; -parcelable BitTube cpp_header "private/gui/BitTube.h"; +parcelable BitTube cpp_header "private/gui/BitTube.h" rust_type "gui_aidl_types_rs::BitTube"; diff --git a/libs/gui/aidl/android/gui/CaptureArgs.aidl b/libs/gui/aidl/android/gui/CaptureArgs.aidl index 920d94980a..9f198cae10 100644 --- a/libs/gui/aidl/android/gui/CaptureArgs.aidl +++ b/libs/gui/aidl/android/gui/CaptureArgs.aidl @@ -16,4 +16,4 @@ package android.gui; -parcelable CaptureArgs cpp_header "gui/DisplayCaptureArgs.h"; +parcelable CaptureArgs cpp_header "gui/DisplayCaptureArgs.h" rust_type "gui_aidl_types_rs::CaptureArgs"; diff --git a/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl b/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl index 2caa2b9f61..fc97dbf03d 100644 --- a/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl +++ b/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl @@ -16,4 +16,5 @@ package android.gui; -parcelable DisplayCaptureArgs cpp_header "gui/DisplayCaptureArgs.h"; +parcelable DisplayCaptureArgs cpp_header "gui/DisplayCaptureArgs.h" rust_type "gui_aidl_types_rs::DisplayCaptureArgs"; + diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 51e01930d3..a2549e7e3f 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -43,7 +43,6 @@ import android.gui.ITunnelModeEnabledListener; import android.gui.IWindowInfosListener; import android.gui.IWindowInfosPublisher; import android.gui.LayerCaptureArgs; -import android.gui.LayerDebugInfo; import android.gui.OverlayProperties; import android.gui.PullAtomData; import android.gui.ScreenCaptureResults; @@ -289,13 +288,6 @@ interface ISurfaceComposer { PullAtomData onPullAtom(int atomId); /** - * Gets the list of active layers in Z order for debugging purposes - * - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - List<LayerDebugInfo> getLayerDebugInfo(); - - /** * Gets the composition preference of the default data space and default pixel format, * as well as the wide color gamut data space and wide color gamut pixel format. * If the wide color gamut data space is V0_SRGB, then it implies that the platform diff --git a/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl b/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl index f0def5019a..18d293f211 100644 --- a/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl +++ b/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl @@ -16,4 +16,4 @@ package android.gui; -parcelable LayerCaptureArgs cpp_header "gui/LayerCaptureArgs.h"; +parcelable LayerCaptureArgs cpp_header "gui/LayerCaptureArgs.h" rust_type "gui_aidl_types_rs::LayerCaptureArgs"; diff --git a/libs/gui/aidl/android/gui/LayerDebugInfo.aidl b/libs/gui/aidl/android/gui/LayerDebugInfo.aidl deleted file mode 100644 index faca980f3c..0000000000 --- a/libs/gui/aidl/android/gui/LayerDebugInfo.aidl +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.gui; - -parcelable LayerDebugInfo cpp_header "gui/LayerDebugInfo.h"; diff --git a/libs/gui/aidl/android/gui/LayerMetadata.aidl b/libs/gui/aidl/android/gui/LayerMetadata.aidl index 1368ac512f..d8121bedb0 100644 --- a/libs/gui/aidl/android/gui/LayerMetadata.aidl +++ b/libs/gui/aidl/android/gui/LayerMetadata.aidl @@ -16,4 +16,4 @@ package android.gui; -parcelable LayerMetadata cpp_header "gui/LayerMetadata.h"; +parcelable LayerMetadata cpp_header "gui/LayerMetadata.h" rust_type "gui_aidl_types_rs::LayerMetadata"; diff --git a/libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl b/libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl index ba76671f8f..53f443aa59 100644 --- a/libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl +++ b/libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl @@ -16,4 +16,4 @@ package android.gui; -parcelable ParcelableVsyncEventData cpp_header "gui/VsyncEventData.h"; +parcelable ParcelableVsyncEventData cpp_header "gui/VsyncEventData.h" rust_type "gui_aidl_types_rs::VsyncEventData"; diff --git a/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl b/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl index 9908edd2ef..97a903515b 100644 --- a/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl +++ b/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl @@ -16,4 +16,4 @@ package android.gui; -parcelable ScreenCaptureResults cpp_header "gui/ScreenCaptureResults.h";
\ No newline at end of file +parcelable ScreenCaptureResults cpp_header "gui/ScreenCaptureResults.h" rust_type "gui_aidl_types_rs::ScreenCaptureResults";
\ No newline at end of file diff --git a/libs/gui/android/gui/DisplayInfo.aidl b/libs/gui/android/gui/DisplayInfo.aidl index 30c088525d..3b16724e7f 100644 --- a/libs/gui/android/gui/DisplayInfo.aidl +++ b/libs/gui/android/gui/DisplayInfo.aidl @@ -16,4 +16,4 @@ package android.gui; -parcelable DisplayInfo cpp_header "gui/DisplayInfo.h"; +parcelable DisplayInfo cpp_header "gui/DisplayInfo.h" rust_type "gui_aidl_types_rs::DisplayInfo"; diff --git a/libs/gui/android/gui/WindowInfo.aidl b/libs/gui/android/gui/WindowInfo.aidl index 2c85d155a8..b9d5ccf753 100644 --- a/libs/gui/android/gui/WindowInfo.aidl +++ b/libs/gui/android/gui/WindowInfo.aidl @@ -16,4 +16,4 @@ package android.gui; -parcelable WindowInfo cpp_header "gui/WindowInfo.h"; +parcelable WindowInfo cpp_header "gui/WindowInfo.h" rust_type "gui_aidl_types_rs::WindowInfo"; diff --git a/libs/gui/android/gui/WindowInfosUpdate.aidl b/libs/gui/android/gui/WindowInfosUpdate.aidl index 0c6109da8f..5c23e088ba 100644 --- a/libs/gui/android/gui/WindowInfosUpdate.aidl +++ b/libs/gui/android/gui/WindowInfosUpdate.aidl @@ -19,4 +19,4 @@ package android.gui; import android.gui.DisplayInfo; import android.gui.WindowInfo; -parcelable WindowInfosUpdate cpp_header "gui/WindowInfosUpdate.h"; +parcelable WindowInfosUpdate cpp_header "gui/WindowInfosUpdate.h" rust_type "gui_aidl_types_rs::WindowInfosUpdate"; diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index a836f4642a..738c73a24b 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -74,7 +74,6 @@ namespace gui { struct DisplayCaptureArgs; struct LayerCaptureArgs; -class LayerDebugInfo; } // namespace gui diff --git a/libs/gui/include/gui/LayerDebugInfo.h b/libs/gui/include/gui/LayerDebugInfo.h deleted file mode 100644 index dbb80e583c..0000000000 --- a/libs/gui/include/gui/LayerDebugInfo.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <binder/Parcelable.h> - -#include <ui/PixelFormat.h> -#include <ui/Region.h> -#include <ui/StretchEffect.h> - -#include <string> -#include <math/vec4.h> - -namespace android::gui { - -/* Class for transporting debug info from SurfaceFlinger to authorized - * recipients. The class is intended to be a data container. There are - * no getters or setters. - */ -class LayerDebugInfo : public Parcelable { -public: - LayerDebugInfo() = default; - LayerDebugInfo(const LayerDebugInfo&) = default; - virtual ~LayerDebugInfo() = default; - - virtual status_t writeToParcel(Parcel* parcel) const; - virtual status_t readFromParcel(const Parcel* parcel); - - std::string mName = std::string("NOT FILLED"); - std::string mParentName = std::string("NOT FILLED"); - std::string mType = std::string("NOT FILLED"); - Region mTransparentRegion = Region::INVALID_REGION; - Region mVisibleRegion = Region::INVALID_REGION; - Region mSurfaceDamageRegion = Region::INVALID_REGION; - uint32_t mLayerStack = 0; - float mX = 0.f; - float mY = 0.f; - uint32_t mZ = 0 ; - int32_t mWidth = -1; - int32_t mHeight = -1; - android::Rect mCrop = android::Rect::INVALID_RECT; - half4 mColor = half4(1.0_hf, 1.0_hf, 1.0_hf, 0.0_hf); - uint32_t mFlags = 0; - PixelFormat mPixelFormat = PIXEL_FORMAT_NONE; - android_dataspace mDataSpace = HAL_DATASPACE_UNKNOWN; - // Row-major transform matrix (SurfaceControl::setMatrix()) - float mMatrix[2][2] = {{0.f, 0.f}, {0.f, 0.f}}; - int32_t mActiveBufferWidth = -1; - int32_t mActiveBufferHeight = -1; - int32_t mActiveBufferStride = 0; - PixelFormat mActiveBufferFormat = PIXEL_FORMAT_NONE; - int32_t mNumQueuedFrames = -1; - bool mIsOpaque = false; - bool mContentDirty = false; - StretchEffect mStretchEffect = {}; -}; - -std::string to_string(const LayerDebugInfo& info); - -} // namespace android::gui diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h index 2d1b51a418..e4f1890c76 100644 --- a/libs/gui/include/gui/WindowInfo.h +++ b/libs/gui/include/gui/WindowInfo.h @@ -178,6 +178,8 @@ struct WindowInfo : public Parcelable { static_cast<uint32_t>(os::InputConfig::CLONE), GLOBAL_STYLUS_BLOCKS_TOUCH = static_cast<uint32_t>(os::InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH), + SENSITIVE_FOR_TRACING = + static_cast<uint32_t>(os::InputConfig::SENSITIVE_FOR_TRACING), // clang-format on }; diff --git a/libs/gui/rust/aidl_types/Android.bp b/libs/gui/rust/aidl_types/Android.bp new file mode 100644 index 0000000000..794f69e1e6 --- /dev/null +++ b/libs/gui/rust/aidl_types/Android.bp @@ -0,0 +1,23 @@ +rust_defaults { + name: "libgui_aidl_types_defaults", + srcs: ["src/lib.rs"], + rustlibs: [ + "libbinder_rs", + ], +} + +rust_library { + name: "libgui_aidl_types_rs", + crate_name: "gui_aidl_types_rs", + defaults: ["libgui_aidl_types_defaults"], + + // Currently necessary for host builds + // TODO(b/31559095): bionic on host should define this + target: { + darwin: { + enabled: false, + }, + }, + min_sdk_version: "VanillaIceCream", + vendor_available: true, +} diff --git a/libs/gui/rust/aidl_types/src/lib.rs b/libs/gui/rust/aidl_types/src/lib.rs new file mode 100644 index 0000000000..3d29529d23 --- /dev/null +++ b/libs/gui/rust/aidl_types/src/lib.rs @@ -0,0 +1,55 @@ +// 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. + +//! Rust wrapper for libgui AIDL types. + +use binder::{ + binder_impl::{BorrowedParcel, UnstructuredParcelable}, + impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable, + StatusCode, +}; + +macro_rules! stub_unstructured_parcelable { + ($name:ident) => { + /// Unimplemented stub parcelable. + #[derive(Debug, Default)] + pub struct $name(Option<()>); + + impl UnstructuredParcelable for $name { + fn write_to_parcel(&self, _parcel: &mut BorrowedParcel) -> Result<(), StatusCode> { + todo!() + } + + fn from_parcel(_parcel: &BorrowedParcel) -> Result<Self, StatusCode> { + todo!() + } + } + + impl_deserialize_for_unstructured_parcelable!($name); + impl_serialize_for_unstructured_parcelable!($name); + }; +} + +stub_unstructured_parcelable!(BitTube); +stub_unstructured_parcelable!(CaptureArgs); +stub_unstructured_parcelable!(DisplayCaptureArgs); +stub_unstructured_parcelable!(DisplayInfo); +stub_unstructured_parcelable!(LayerCaptureArgs); +stub_unstructured_parcelable!(LayerDebugInfo); +stub_unstructured_parcelable!(LayerMetadata); +stub_unstructured_parcelable!(ParcelableVsyncEventData); +stub_unstructured_parcelable!(ScreenCaptureResults); +stub_unstructured_parcelable!(VsyncEventData); +stub_unstructured_parcelable!(WindowInfo); +stub_unstructured_parcelable!(WindowInfosUpdate); diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp index 0f16f714dc..2faa330426 100644 --- a/libs/gui/tests/Android.bp +++ b/libs/gui/tests/Android.bp @@ -21,7 +21,8 @@ cc_test { cppflags: [ "-Wall", "-Werror", - "-Wno-extra", + "-Wextra", + "-Wthread-safety", "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_BQ_SETFRAMERATE=true", ], diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp index ea7078dd05..946ff058cf 100644 --- a/libs/gui/tests/BLASTBufferQueue_test.cpp +++ b/libs/gui/tests/BLASTBufferQueue_test.cpp @@ -18,6 +18,7 @@ #include <gui/BLASTBufferQueue.h> +#include <android-base/thread_annotations.h> #include <android/hardware/graphics/common/1.2/types.h> #include <gui/AidlStatusUtil.h> #include <gui/BufferQueueCore.h> @@ -61,7 +62,8 @@ public: } void waitOnNumberReleased(int32_t expectedNumReleased) { - std::unique_lock<std::mutex> lock(mMutex); + std::unique_lock lock{mMutex}; + base::ScopedLockAssertion assumeLocked(mMutex); while (mNumReleased < expectedNumReleased) { ASSERT_NE(mReleaseCallback.wait_for(lock, std::chrono::seconds(3)), std::cv_status::timeout) @@ -134,11 +136,18 @@ public: void clearSyncTransaction() { mBlastBufferQueueAdapter->clearSyncTransaction(); } - int getWidth() { return mBlastBufferQueueAdapter->mSize.width; } + int getWidth() { + std::scoped_lock lock(mBlastBufferQueueAdapter->mMutex); + return mBlastBufferQueueAdapter->mSize.width; + } - int getHeight() { return mBlastBufferQueueAdapter->mSize.height; } + int getHeight() { + std::scoped_lock lock(mBlastBufferQueueAdapter->mMutex); + return mBlastBufferQueueAdapter->mSize.height; + } std::function<void(Transaction*)> getTransactionReadyCallback() { + std::scoped_lock lock(mBlastBufferQueueAdapter->mMutex); return mBlastBufferQueueAdapter->mTransactionReadyCallback; } @@ -147,6 +156,7 @@ public: } const sp<SurfaceControl> getSurfaceControl() { + std::scoped_lock lock(mBlastBufferQueueAdapter->mMutex); return mBlastBufferQueueAdapter->mSurfaceControl; } @@ -156,6 +166,7 @@ public: void waitForCallbacks() { std::unique_lock lock{mBlastBufferQueueAdapter->mMutex}; + base::ScopedLockAssertion assumeLocked(mBlastBufferQueueAdapter->mMutex); // Wait until all but one of the submitted buffers have been released. while (mBlastBufferQueueAdapter->mSubmitted.size() > 1) { mBlastBufferQueueAdapter->mCallbackCV.wait(lock); @@ -166,8 +177,8 @@ public: mBlastBufferQueueAdapter->waitForCallback(frameNumber); } - void validateNumFramesSubmitted(int64_t numFramesSubmitted) { - std::unique_lock lock{mBlastBufferQueueAdapter->mMutex}; + void validateNumFramesSubmitted(size_t numFramesSubmitted) { + std::scoped_lock lock{mBlastBufferQueueAdapter->mMutex}; ASSERT_EQ(numFramesSubmitted, mBlastBufferQueueAdapter->mSubmitted.size()); } @@ -201,7 +212,7 @@ protected: mDisplayWidth = resolution.getWidth(); mDisplayHeight = resolution.getHeight(); ALOGD("Display: %dx%d orientation:%d", mDisplayWidth, mDisplayHeight, - displayState.orientation); + static_cast<int32_t>(displayState.orientation)); mRootSurfaceControl = mClient->createSurface(String8("RootTestSurface"), mDisplayWidth, mDisplayHeight, PIXEL_FORMAT_RGBA_8888, @@ -240,8 +251,8 @@ protected: void fillBuffer(uint32_t* bufData, Rect rect, uint32_t stride, uint8_t r, uint8_t g, uint8_t b) { - for (uint32_t row = rect.top; row < rect.bottom; row++) { - for (uint32_t col = rect.left; col < rect.right; col++) { + for (int32_t row = rect.top; row < rect.bottom; row++) { + for (int32_t col = rect.left; col < rect.right; col++) { uint8_t* pixel = (uint8_t*)(bufData + (row * stride) + col); *pixel = r; *(pixel + 1) = g; @@ -271,16 +282,16 @@ protected: bool outsideRegion = false) { sp<GraphicBuffer>& captureBuf = mCaptureResults.buffer; const auto epsilon = 3; - const auto width = captureBuf->getWidth(); - const auto height = captureBuf->getHeight(); + const int32_t width = static_cast<int32_t>(captureBuf->getWidth()); + const int32_t height = static_cast<int32_t>(captureBuf->getHeight()); const auto stride = captureBuf->getStride(); uint32_t* bufData; captureBuf->lock(static_cast<uint32_t>(GraphicBuffer::USAGE_SW_READ_OFTEN), reinterpret_cast<void**>(&bufData)); - for (uint32_t row = 0; row < height; row++) { - for (uint32_t col = 0; col < width; col++) { + for (int32_t row = 0; row < height; row++) { + for (int32_t col = 0; col < width; col++) { uint8_t* pixel = (uint8_t*)(bufData + (row * stride) + col); ASSERT_NE(nullptr, pixel); bool inRegion; @@ -352,8 +363,8 @@ TEST_F(BLASTBufferQueueTest, CreateBLASTBufferQueue) { // create BLASTBufferQueue adapter associated with this surface BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight); ASSERT_EQ(mSurfaceControl, adapter.getSurfaceControl()); - ASSERT_EQ(mDisplayWidth, adapter.getWidth()); - ASSERT_EQ(mDisplayHeight, adapter.getHeight()); + ASSERT_EQ(static_cast<int32_t>(mDisplayWidth), adapter.getWidth()); + ASSERT_EQ(static_cast<int32_t>(mDisplayHeight), adapter.getHeight()); ASSERT_EQ(nullptr, adapter.getTransactionReadyCallback()); } @@ -371,10 +382,10 @@ TEST_F(BLASTBufferQueueTest, Update) { int32_t width; igbProducer->query(NATIVE_WINDOW_WIDTH, &width); - ASSERT_EQ(mDisplayWidth / 2, width); + ASSERT_EQ(static_cast<int32_t>(mDisplayWidth) / 2, width); int32_t height; igbProducer->query(NATIVE_WINDOW_HEIGHT, &height); - ASSERT_EQ(mDisplayHeight / 2, height); + ASSERT_EQ(static_cast<int32_t>(mDisplayHeight) / 2, height); } TEST_F(BLASTBufferQueueTest, SyncNextTransaction) { @@ -476,7 +487,7 @@ TEST_F(BLASTBufferQueueTest, TripleBuffering) { ASSERT_EQ(OK, igbProducer->requestBuffer(slot, &buf)); allocated.push_back({slot, fence}); } - for (int i = 0; i < allocated.size(); i++) { + for (size_t i = 0; i < allocated.size(); i++) { igbProducer->cancelBuffer(allocated[i].first, allocated[i].second); } @@ -1313,14 +1324,14 @@ TEST_F(BLASTBufferQueueTest, TransformHint) { // Before connecting to the surface, we do not get a valid transform hint int transformHint; surface->query(NATIVE_WINDOW_TRANSFORM_HINT, &transformHint); - ASSERT_EQ(ui::Transform::ROT_0, transformHint); + ASSERT_EQ(ui::Transform::ROT_0, static_cast<ui::Transform::RotationFlags>(transformHint)); ASSERT_EQ(NO_ERROR, surface->connect(NATIVE_WINDOW_API_CPU, new TestProducerListener(igbProducer))); // After connecting to the surface, we should get the correct hint. surface->query(NATIVE_WINDOW_TRANSFORM_HINT, &transformHint); - ASSERT_EQ(ui::Transform::ROT_90, transformHint); + ASSERT_EQ(ui::Transform::ROT_90, static_cast<ui::Transform::RotationFlags>(transformHint)); ANativeWindow_Buffer buffer; surface->lock(&buffer, nullptr /* inOutDirtyBounds */); @@ -1331,13 +1342,13 @@ TEST_F(BLASTBufferQueueTest, TransformHint) { // The hint does not change and matches the value used when dequeueing the buffer. surface->query(NATIVE_WINDOW_TRANSFORM_HINT, &transformHint); - ASSERT_EQ(ui::Transform::ROT_90, transformHint); + ASSERT_EQ(ui::Transform::ROT_90, static_cast<ui::Transform::RotationFlags>(transformHint)); surface->unlockAndPost(); // After queuing the buffer, we get the updated transform hint surface->query(NATIVE_WINDOW_TRANSFORM_HINT, &transformHint); - ASSERT_EQ(ui::Transform::ROT_0, transformHint); + ASSERT_EQ(ui::Transform::ROT_0, static_cast<ui::Transform::RotationFlags>(transformHint)); adapter.waitForCallbacks(); } @@ -1573,7 +1584,7 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_Basic) { FrameEvents* events = nullptr; events = history.getFrame(1); ASSERT_NE(nullptr, events); - ASSERT_EQ(1, events->frameNumber); + ASSERT_EQ(1u, events->frameNumber); ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime); ASSERT_GE(events->postedTime, postedTimeA); @@ -1591,7 +1602,7 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_Basic) { ASSERT_NE(nullptr, events); // frame number, requestedPresentTime, and postTime should not have changed - ASSERT_EQ(1, events->frameNumber); + ASSERT_EQ(1u, events->frameNumber); ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime); ASSERT_GE(events->postedTime, postedTimeA); @@ -1606,7 +1617,7 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_Basic) { // we should also have gotten the initial values for the next frame events = history.getFrame(2); ASSERT_NE(nullptr, events); - ASSERT_EQ(2, events->frameNumber); + ASSERT_EQ(2u, events->frameNumber); ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime); ASSERT_GE(events->postedTime, postedTimeB); @@ -1622,7 +1633,7 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_Basic) { // Check the first frame... events = history.getFrame(1); ASSERT_NE(nullptr, events); - ASSERT_EQ(1, events->frameNumber); + ASSERT_EQ(1u, events->frameNumber); ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime); ASSERT_GE(events->postedTime, postedTimeA); ASSERT_GE(events->latchTime, postedTimeA); @@ -1636,7 +1647,7 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_Basic) { // ...and the second events = history.getFrame(2); ASSERT_NE(nullptr, events); - ASSERT_EQ(2, events->frameNumber); + ASSERT_EQ(2u, events->frameNumber); ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime); ASSERT_GE(events->postedTime, postedTimeB); ASSERT_GE(events->latchTime, postedTimeB); @@ -1650,7 +1661,7 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_Basic) { // ...and finally the third! events = history.getFrame(3); ASSERT_NE(nullptr, events); - ASSERT_EQ(3, events->frameNumber); + ASSERT_EQ(3u, events->frameNumber); ASSERT_EQ(requestedPresentTimeC, events->requestedPresentTime); ASSERT_GE(events->postedTime, postedTimeC); @@ -1677,7 +1688,7 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_DroppedFrame) { FrameEvents* events = nullptr; events = history.getFrame(1); ASSERT_NE(nullptr, events); - ASSERT_EQ(1, events->frameNumber); + ASSERT_EQ(1u, events->frameNumber); ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime); ASSERT_GE(events->postedTime, postedTimeA); @@ -1692,7 +1703,7 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_DroppedFrame) { ASSERT_NE(nullptr, events); // frame number, requestedPresentTime, and postTime should not have changed - ASSERT_EQ(1, events->frameNumber); + ASSERT_EQ(1u, events->frameNumber); ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime); ASSERT_GE(events->postedTime, postedTimeA); @@ -1715,7 +1726,7 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_DroppedFrame) { adapter.waitForCallback(3); // frame number, requestedPresentTime, and postTime should not have changed - ASSERT_EQ(1, events->frameNumber); + ASSERT_EQ(1u, events->frameNumber); ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime); ASSERT_GE(events->postedTime, postedTimeA); @@ -1729,7 +1740,7 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_DroppedFrame) { // we should also have gotten values for the presented frame events = history.getFrame(2); ASSERT_NE(nullptr, events); - ASSERT_EQ(2, events->frameNumber); + ASSERT_EQ(2u, events->frameNumber); ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime); ASSERT_GE(events->postedTime, postedTimeB); ASSERT_GE(events->latchTime, postedTimeB); @@ -1751,7 +1762,7 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_DroppedFrame) { // frame number, requestedPresentTime, and postTime should not have changed events = history.getFrame(1); - ASSERT_EQ(1, events->frameNumber); + ASSERT_EQ(1u, events->frameNumber); ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime); ASSERT_GE(events->postedTime, postedTimeA); @@ -1765,7 +1776,7 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_DroppedFrame) { // we should also have gotten values for the presented frame events = history.getFrame(2); ASSERT_NE(nullptr, events); - ASSERT_EQ(2, events->frameNumber); + ASSERT_EQ(2u, events->frameNumber); ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime); ASSERT_GE(events->postedTime, postedTimeB); ASSERT_GE(events->latchTime, postedTimeB); diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp index df7739c3fb..1ec6f915f7 100644 --- a/libs/gui/tests/BufferQueue_test.cpp +++ b/libs/gui/tests/BufferQueue_test.cpp @@ -119,8 +119,7 @@ TEST_F(BufferQueueTest, DISABLED_BufferQueueInAnotherProcess) { } sp<IServiceManager> serviceManager = defaultServiceManager(); - sp<IBinder> binderProducer = - serviceManager->getService(PRODUCER_NAME); + sp<IBinder> binderProducer = serviceManager->waitForService(PRODUCER_NAME); mProducer = interface_cast<IGraphicBufferProducer>(binderProducer); EXPECT_TRUE(mProducer != nullptr); sp<IBinder> binderConsumer = @@ -1114,7 +1113,7 @@ TEST_F(BufferQueueTest, TestDiscardFreeBuffers) { // Check onBuffersDiscarded is called with correct slots auto buffersDiscarded = pl->getDiscardedSlots(); - ASSERT_EQ(buffersDiscarded.size(), 1); + ASSERT_EQ(buffersDiscarded.size(), 1u); ASSERT_EQ(buffersDiscarded[0], releasedSlot); // Check no free buffers in dump @@ -1239,7 +1238,7 @@ TEST_F(BufferQueueTest, TestConsumerDetachProducerListener) { ASSERT_EQ(OK, mConsumer->detachBuffer(item.mSlot)); // Check whether the slot from IProducerListener is same to the detached slot. - ASSERT_EQ(pl->getDetachedSlots().size(), 1); + ASSERT_EQ(pl->getDetachedSlots().size(), 1u); ASSERT_EQ(pl->getDetachedSlots()[0], slots[1]); // Dequeue another buffer. diff --git a/libs/gui/tests/DisplayedContentSampling_test.cpp b/libs/gui/tests/DisplayedContentSampling_test.cpp index 0a2750a4dd..bffb3f0430 100644 --- a/libs/gui/tests/DisplayedContentSampling_test.cpp +++ b/libs/gui/tests/DisplayedContentSampling_test.cpp @@ -116,10 +116,10 @@ TEST_F(DisplayedContentSamplingTest, SampleCollectionCoherentWithSupportMask) { EXPECT_EQ(OK, status); if (stats.numFrames <= 0) return; - if (componentMask & (0x1 << 0)) EXPECT_NE(0, stats.component_0_sample.size()); - if (componentMask & (0x1 << 1)) EXPECT_NE(0, stats.component_1_sample.size()); - if (componentMask & (0x1 << 2)) EXPECT_NE(0, stats.component_2_sample.size()); - if (componentMask & (0x1 << 3)) EXPECT_NE(0, stats.component_3_sample.size()); + if (componentMask & (0x1 << 0)) EXPECT_NE(0u, stats.component_0_sample.size()); + if (componentMask & (0x1 << 1)) EXPECT_NE(0u, stats.component_1_sample.size()); + if (componentMask & (0x1 << 2)) EXPECT_NE(0u, stats.component_2_sample.size()); + if (componentMask & (0x1 << 3)) EXPECT_NE(0u, stats.component_3_sample.size()); } } // namespace android diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp index 9791212e07..f441eaa95a 100644 --- a/libs/gui/tests/EndToEndNativeInputTest.cpp +++ b/libs/gui/tests/EndToEndNativeInputTest.cpp @@ -24,6 +24,7 @@ #include <memory> +#include <android-base/thread_annotations.h> #include <android/gui/BnWindowInfosReportedListener.h> #include <android/keycodes.h> #include <android/native_window.h> @@ -78,21 +79,22 @@ static constexpr std::chrono::nanoseconds DISPATCHING_TIMEOUT = 5s; class SynchronousWindowInfosReportedListener : public gui::BnWindowInfosReportedListener { public: binder::Status onWindowInfosReported() override { - std::lock_guard<std::mutex> lock{mMutex}; + std::scoped_lock lock{mLock}; mWindowInfosReported = true; mConditionVariable.notify_one(); return binder::Status::ok(); } void wait() { - std::unique_lock<std::mutex> lock{mMutex}; - mConditionVariable.wait(lock, [&] { return mWindowInfosReported; }); + std::unique_lock lock{mLock}; + android::base::ScopedLockAssertion assumeLocked(mLock); + mConditionVariable.wait(lock, [&]() REQUIRES(mLock) { return mWindowInfosReported; }); } private: - std::mutex mMutex; + std::mutex mLock; std::condition_variable mConditionVariable; - bool mWindowInfosReported{false}; + bool mWindowInfosReported GUARDED_BY(mLock){false}; }; class InputSurface { @@ -195,7 +197,7 @@ public: EXPECT_EQ(hasFocus, focusEvent->getHasFocus()); } - void expectTap(int x, int y) { + void expectTap(float x, float y) { InputEvent* ev = consumeEvent(); ASSERT_NE(ev, nullptr); ASSERT_EQ(InputEventType::MOTION, ev->getType()); @@ -250,7 +252,7 @@ public: EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS); } - void expectKey(uint32_t keycode) { + void expectKey(int32_t keycode) { InputEvent *ev = consumeEvent(); ASSERT_NE(ev, nullptr); ASSERT_EQ(InputEventType::KEY, ev->getType()); @@ -268,6 +270,11 @@ public: EXPECT_EQ(0, keyEvent->getFlags() & VERIFIED_KEY_EVENT_FLAGS); } + void assertNoEvent() { + InputEvent* event = consumeEvent(/*timeout=*/100ms); + ASSERT_EQ(event, nullptr) << "Expected no event, but got " << *event; + } + virtual ~InputSurface() { if (mClientChannel) { mInputFlinger->removeInputChannel(mClientChannel->getConnectionToken()); @@ -937,9 +944,7 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_unobscured_window) { surface->showAt(100, 100); injectTap(101, 101); - - EXPECT_NE(surface->consumeEvent(), nullptr); - EXPECT_NE(surface->consumeEvent(), nullptr); + surface->expectTap(1, 1); surface->requestFocus(); surface->assertFocusChange(true); @@ -956,9 +961,7 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_scaled_without_crop_window) { surface->showAt(100, 100); injectTap(101, 101); - - EXPECT_NE(surface->consumeEvent(), nullptr); - EXPECT_NE(surface->consumeEvent(), nullptr); + surface->expectTap(.5, .5); surface->requestFocus(); surface->assertFocusChange(true); @@ -977,12 +980,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(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); surface->requestFocus(); surface->assertFocusChange(true); injectKey(AKEYCODE_V); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); } TEST_F(InputSurfacesTest, strict_unobscured_input_partially_obscured_window) { @@ -998,12 +1001,12 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_partially_obscured_window) { injectTap(101, 101); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); surface->requestFocus(); surface->assertFocusChange(true); injectKey(AKEYCODE_V); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); } TEST_F(InputSurfacesTest, strict_unobscured_input_alpha_window) { @@ -1020,12 +1023,12 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_alpha_window) { injectTap(101, 101); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); surface->requestFocus(); surface->assertFocusChange(true); injectKey(AKEYCODE_V); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); } TEST_F(InputSurfacesTest, strict_unobscured_input_cropped_window) { @@ -1042,12 +1045,12 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_cropped_window) { injectTap(111, 111); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); surface->requestFocus(); surface->assertFocusChange(true); injectKey(AKEYCODE_V); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); } TEST_F(InputSurfacesTest, ignore_touch_region_with_zero_sized_blast) { @@ -1071,13 +1074,12 @@ TEST_F(InputSurfacesTest, drop_input_policy) { surface->showAt(100, 100); injectTap(101, 101); - - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); surface->requestFocus(); surface->assertFocusChange(true); injectKey(AKEYCODE_V); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); } TEST_F(InputSurfacesTest, layer_with_valid_crop_can_be_focused) { @@ -1112,7 +1114,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(/*timeout=*/100ms), nullptr); + containerSurface->assertNoEvent(); } /** @@ -1137,7 +1139,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(/*timeout=*/100ms), nullptr); + containerSurface->assertNoEvent(); } /** @@ -1163,7 +1165,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(/*timeout=*/100ms), nullptr); + containerSurface->assertNoEvent(); } TEST_F(InputSurfacesTest, child_container_with_no_input_channel_blocks_parent) { @@ -1180,7 +1182,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(/*timeout=*/100ms), nullptr); + parent->assertNoEvent(); } class MultiDisplayTests : public InputSurfacesTest { @@ -1229,7 +1231,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(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); // However, we still let the window be focused and receive keys. surface->requestFocus(layerStack.id); @@ -1267,12 +1269,12 @@ TEST_F(MultiDisplayTests, drop_input_for_secure_layer_on_nonsecure_display) { injectTapOnDisplay(101, 101, layerStack.id); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); surface->requestFocus(layerStack.id); surface->assertFocusChange(true); injectKeyOnDisplay(AKEYCODE_V, layerStack.id); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr); + surface->assertNoEvent(); } TEST_F(MultiDisplayTests, dont_drop_input_for_secure_layer_on_secure_display) { @@ -1292,8 +1294,7 @@ TEST_F(MultiDisplayTests, dont_drop_input_for_secure_layer_on_secure_display) { surface->showAt(100, 100); injectTapOnDisplay(101, 101, layerStack.id); - EXPECT_NE(surface->consumeEvent(), nullptr); - EXPECT_NE(surface->consumeEvent(), nullptr); + surface->expectTap(1, 1); surface->requestFocus(layerStack.id); surface->assertFocusChange(true); diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 577d2394c6..f4b059c39b 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -173,7 +173,7 @@ protected: // Acquire and free 1+extraDiscardedBuffers buffer, check onBufferReleased is called. std::vector<BufferItem> releasedItems; releasedItems.resize(1+extraDiscardedBuffers); - for (int i = 0; i < releasedItems.size(); i++) { + for (size_t i = 0; i < releasedItems.size(); i++) { ASSERT_EQ(NO_ERROR, consumer->acquireBuffer(&releasedItems[i], 0)); ASSERT_EQ(NO_ERROR, consumer->releaseBuffer(releasedItems[i].mSlot, releasedItems[i].mFrameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, @@ -197,7 +197,7 @@ protected: // Check onBufferDiscarded is called with correct buffer auto discardedBuffers = listener->getDiscardedBuffers(); ASSERT_EQ(discardedBuffers.size(), releasedItems.size()); - for (int i = 0; i < releasedItems.size(); i++) { + for (size_t i = 0; i < releasedItems.size(); i++) { ASSERT_EQ(discardedBuffers[i], releasedItems[i].mGraphicBuffer); } @@ -815,10 +815,6 @@ public: return binder::Status::ok(); } - binder::Status getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* /*outLayers*/) override { - return binder::Status::ok(); - } - binder::Status getCompositionPreference(gui::CompositionPreference* /*outPref*/) override { return binder::Status::ok(); } diff --git a/libs/input/InputConsumer.cpp b/libs/input/InputConsumer.cpp index e0d874ef76..be2110e42b 100644 --- a/libs/input/InputConsumer.cpp +++ b/libs/input/InputConsumer.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <cstdint> #define LOG_TAG "InputTransport" #define ATRACE_TAG ATRACE_TAG_INPUT @@ -194,9 +195,21 @@ InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel) InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel, bool enableTouchResampling) - : mResampleTouch(enableTouchResampling), mChannel(channel), mMsgDeferred(false) {} + : mResampleTouch(enableTouchResampling), + mChannel(channel), + mProcessingTraceTag(StringPrintf("InputConsumer processing on %s (%p)", + mChannel->getName().c_str(), this)), + mLifetimeTraceTag(StringPrintf("InputConsumer lifetime on %s (%p)", + mChannel->getName().c_str(), this)), + mLifetimeTraceCookie( + static_cast<int32_t>(reinterpret_cast<std::uintptr_t>(this) & 0xFFFFFFFF)), + mMsgDeferred(false) { + ATRACE_ASYNC_BEGIN(mLifetimeTraceTag.c_str(), /*cookie=*/mLifetimeTraceCookie); +} -InputConsumer::~InputConsumer() {} +InputConsumer::~InputConsumer() { + ATRACE_ASYNC_END(mLifetimeTraceTag.c_str(), /*cookie=*/mLifetimeTraceCookie); +} bool InputConsumer::isTouchResamplingEnabled() { return property_get_bool(PROPERTY_RESAMPLING_ENABLED, true); @@ -228,7 +241,7 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum mMsg.header.seq); // Trace the event processing timeline - event was just read from the socket - ATRACE_ASYNC_BEGIN("InputConsumer processing", /*cookie=*/mMsg.header.seq); + ATRACE_ASYNC_BEGIN(mProcessingTraceTag.c_str(), /*cookie=*/mMsg.header.seq); } if (result) { // Consume the next batched event unless batches are being held for later. @@ -325,9 +338,10 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum case InputMessage::Type::FINISHED: case InputMessage::Type::TIMELINE: { - LOG_ALWAYS_FATAL("Consumed a %s message, which should never be seen by " - "InputConsumer!", - ftl::enum_string(mMsg.header.type).c_str()); + LOG(FATAL) << "Consumed a " << ftl::enum_string(mMsg.header.type) + << " message, which should never be seen by " + "InputConsumer on " + << mChannel->getName(); break; } @@ -768,7 +782,7 @@ status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) popConsumeTime(seq); // Trace the event processing timeline - event was just finished - ATRACE_ASYNC_END("InputConsumer processing", /*cookie=*/seq); + ATRACE_ASYNC_END(mProcessingTraceTag.c_str(), /*cookie=*/seq); } return result; } diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp index 52acb51910..76f2b4a4f8 100644 --- a/libs/input/InputConsumerNoResampling.cpp +++ b/libs/input/InputConsumerNoResampling.cpp @@ -413,10 +413,9 @@ void InputConsumerNoResampling::handleMessage(const InputMessage& msg) const { case InputMessage::Type::FINISHED: case InputMessage::Type::TIMELINE: { - LOG_ALWAYS_FATAL("Consumed a %s message, which should never be seen by " - "InputConsumer on %s", - ftl::enum_string(msg.header.type).c_str(), - mChannel->getName().c_str()); + LOG(FATAL) << "Consumed a " << ftl::enum_string(msg.header.type) + << " message, which should never be seen by InputConsumer on " + << mChannel->getName(); break; } diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp index 1df88dd497..5b61d3953f 100644 --- a/libs/input/MotionPredictor.cpp +++ b/libs/input/MotionPredictor.cpp @@ -18,12 +18,16 @@ #include <input/MotionPredictor.h> +#include <algorithm> +#include <array> #include <cinttypes> #include <cmath> #include <cstddef> #include <cstdint> #include <limits> +#include <optional> #include <string> +#include <utility> #include <vector> #include <android-base/logging.h> @@ -59,8 +63,73 @@ TfLiteMotionPredictorSample::Point convertPrediction( return {.x = axisTo.x + x_delta, .y = axisTo.y + y_delta}; } +float normalizeRange(float x, float min, float max) { + const float normalized = (x - min) / (max - min); + return std::min(1.0f, std::max(0.0f, normalized)); +} + } // namespace +// --- JerkTracker --- + +JerkTracker::JerkTracker(bool normalizedDt) : mNormalizedDt(normalizedDt) {} + +void JerkTracker::pushSample(int64_t timestamp, float xPos, float yPos) { + mTimestamps.pushBack(timestamp); + const int numSamples = mTimestamps.size(); + + std::array<float, 4> newXDerivatives; + std::array<float, 4> newYDerivatives; + + /** + * Diagram showing the calculation of higher order derivatives of sample x3 + * collected at time=t3. + * Terms in parentheses are not stored (and not needed for calculations) + * t0 ----- t1 ----- t2 ----- t3 + * (x0)-----(x1) ----- x2 ----- x3 + * (x'0) --- x'1 --- x'2 + * x''0 - x''1 + * x'''0 + * + * In this example: + * x'2 = (x3 - x2) / (t3 - t2) + * x''1 = (x'2 - x'1) / (t2 - t1) + * x'''0 = (x''1 - x''0) / (t1 - t0) + * Therefore, timestamp history is needed to calculate higher order derivatives, + * compared to just the last calculated derivative sample. + * + * If mNormalizedDt = true, then dt = 1 and the division is moot. + */ + for (int i = 0; i < numSamples; ++i) { + if (i == 0) { + newXDerivatives[i] = xPos; + newYDerivatives[i] = yPos; + } else { + newXDerivatives[i] = newXDerivatives[i - 1] - mXDerivatives[i - 1]; + newYDerivatives[i] = newYDerivatives[i - 1] - mYDerivatives[i - 1]; + if (!mNormalizedDt) { + const float dt = mTimestamps[numSamples - i] - mTimestamps[numSamples - i - 1]; + newXDerivatives[i] = newXDerivatives[i] / dt; + newYDerivatives[i] = newYDerivatives[i] / dt; + } + } + } + + std::swap(newXDerivatives, mXDerivatives); + std::swap(newYDerivatives, mYDerivatives); +} + +void JerkTracker::reset() { + mTimestamps.clear(); +} + +std::optional<float> JerkTracker::jerkMagnitude() const { + if (mTimestamps.size() == mTimestamps.capacity()) { + return std::hypot(mXDerivatives[3], mYDerivatives[3]); + } + return std::nullopt; +} + // --- MotionPredictor --- MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos, @@ -107,6 +176,7 @@ android::base::Result<void> MotionPredictor::record(const MotionEvent& event) { if (action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_CANCEL) { ALOGD_IF(isDebug(), "End of event stream"); mBuffers->reset(); + mJerkTracker.reset(); mLastEvent.reset(); return {}; } else if (action != AMOTION_EVENT_ACTION_DOWN && action != AMOTION_EVENT_ACTION_MOVE) { @@ -141,6 +211,9 @@ android::base::Result<void> MotionPredictor::record(const MotionEvent& event) { 0, i), .orientation = event.getHistoricalOrientation(0, i), }); + mJerkTracker.pushSample(event.getHistoricalEventTime(i), + coords->getAxisValue(AMOTION_EVENT_AXIS_X), + coords->getAxisValue(AMOTION_EVENT_AXIS_Y)); } if (!mLastEvent) { @@ -188,6 +261,17 @@ std::unique_ptr<MotionEvent> MotionPredictor::predict(nsecs_t timestamp) { int64_t predictionTime = mBuffers->lastTimestamp(); const int64_t futureTime = timestamp + mPredictionTimestampOffsetNanos; + const float jerkMagnitude = mJerkTracker.jerkMagnitude().value_or(0); + const float fractionKept = + 1 - normalizeRange(jerkMagnitude, mModel->config().lowJerk, mModel->config().highJerk); + // float to ensure proper division below. + const float predictionTimeWindow = futureTime - predictionTime; + const int maxNumPredictions = static_cast<int>( + std::ceil(predictionTimeWindow / mModel->config().predictionInterval * fractionKept)); + ALOGD_IF(isDebug(), + "jerk (d^3p/normalizedDt^3): %f, fraction of prediction window pruned: %f, max number " + "of predictions: %d", + jerkMagnitude, 1 - fractionKept, maxNumPredictions); for (size_t i = 0; i < static_cast<size_t>(predictedR.size()) && predictionTime <= futureTime; ++i) { if (predictedR[i] < mModel->config().distanceNoiseFloor) { @@ -202,13 +286,12 @@ std::unique_ptr<MotionEvent> MotionPredictor::predict(nsecs_t timestamp) { break; } if (input_flags::enable_prediction_pruning_via_jerk_thresholding()) { - // TODO(b/266747654): Stop predictions if confidence is < some threshold - // Arbitrarily high pruning index, will correct once jerk thresholding is implemented. - const size_t upperBoundPredictionIndex = std::numeric_limits<size_t>::max(); - if (i > upperBoundPredictionIndex) { + if (i >= static_cast<size_t>(maxNumPredictions)) { break; } } + // TODO(b/266747654): Stop predictions if confidence is < some + // threshold. Currently predictions are pruned via jerk thresholding. const TfLiteMotionPredictorSample::Point predictedPoint = convertPrediction(axisFrom, axisTo, predictedR[i], predictedPhi[i]); diff --git a/libs/input/TfLiteMotionPredictor.cpp b/libs/input/TfLiteMotionPredictor.cpp index d17476e216..b843a4bbf6 100644 --- a/libs/input/TfLiteMotionPredictor.cpp +++ b/libs/input/TfLiteMotionPredictor.cpp @@ -281,6 +281,8 @@ std::unique_ptr<TfLiteMotionPredictorModel> TfLiteMotionPredictorModel::create() Config config{ .predictionInterval = parseXMLInt64(*configRoot, "prediction-interval"), .distanceNoiseFloor = parseXMLFloat(*configRoot, "distance-noise-floor"), + .lowJerk = parseXMLFloat(*configRoot, "low-jerk"), + .highJerk = parseXMLFloat(*configRoot, "high-jerk"), }; return std::unique_ptr<TfLiteMotionPredictorModel>( diff --git a/libs/input/android/os/InputConfig.aidl b/libs/input/android/os/InputConfig.aidl index 5d391551c2..6b97cbbc59 100644 --- a/libs/input/android/os/InputConfig.aidl +++ b/libs/input/android/os/InputConfig.aidl @@ -157,4 +157,12 @@ enum InputConfig { * like StatusBar and TaskBar. */ GLOBAL_STYLUS_BLOCKS_TOUCH = 1 << 17, + + /** + * InputConfig used to indicate that this window is sensitive for tracing. + * This must be set on windows that use {@link WindowManager.LayoutParams#FLAG_SECURE}, + * but it may also be set without setting FLAG_SECURE. The tracing configuration will + * determine how these sensitive events are eventually traced. + */ + SENSITIVE_FOR_TRACING = 1 << 18, } diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig index e041c51cc9..e161c2afc1 100644 --- a/libs/input/input_flags.aconfig +++ b/libs/input/input_flags.aconfig @@ -137,3 +137,10 @@ flag { is_fixed_read_only: true } + +flag { + name: "enable_multi_device_same_window_stream" + namespace: "input" + description: "Allow multiple input devices to be active in the same window simultaneously" + bug: "330752824" +} diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index e67a65a114..ee140b72bd 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -36,6 +36,7 @@ cc_test { "tensorflow_headers", ], static_libs: [ + "libflagtest", "libgmock", "libgui_window_info_static", "libinput", diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp index 33431146ea..b8f1caa068 100644 --- a/libs/input/tests/MotionPredictor_test.cpp +++ b/libs/input/tests/MotionPredictor_test.cpp @@ -14,8 +14,12 @@ * limitations under the License. */ +// TODO(b/331815574): Decouple this test from assumed config values. #include <chrono> +#include <cmath> +#include <com_android_input_flags.h> +#include <flag_macros.h> #include <gmock/gmock.h> #include <gtest/gtest.h> #include <gui/constants.h> @@ -65,6 +69,108 @@ static MotionEvent getMotionEvent(int32_t action, float x, float y, return event; } +TEST(JerkTrackerTest, JerkReadiness) { + JerkTracker jerkTracker(true); + EXPECT_FALSE(jerkTracker.jerkMagnitude()); + jerkTracker.pushSample(/*timestamp=*/0, 20, 50); + EXPECT_FALSE(jerkTracker.jerkMagnitude()); + jerkTracker.pushSample(/*timestamp=*/1, 25, 53); + EXPECT_FALSE(jerkTracker.jerkMagnitude()); + jerkTracker.pushSample(/*timestamp=*/2, 30, 60); + EXPECT_FALSE(jerkTracker.jerkMagnitude()); + jerkTracker.pushSample(/*timestamp=*/3, 35, 70); + EXPECT_TRUE(jerkTracker.jerkMagnitude()); + jerkTracker.reset(); + EXPECT_FALSE(jerkTracker.jerkMagnitude()); + jerkTracker.pushSample(/*timestamp=*/4, 30, 60); + EXPECT_FALSE(jerkTracker.jerkMagnitude()); +} + +TEST(JerkTrackerTest, JerkCalculationNormalizedDtTrue) { + JerkTracker jerkTracker(true); + jerkTracker.pushSample(/*timestamp=*/0, 20, 50); + jerkTracker.pushSample(/*timestamp=*/1, 25, 53); + jerkTracker.pushSample(/*timestamp=*/2, 30, 60); + jerkTracker.pushSample(/*timestamp=*/3, 45, 70); + /** + * Jerk derivative table + * x: 20 25 30 45 + * x': 5 5 15 + * x'': 0 10 + * x''': 10 + * + * y: 50 53 60 70 + * y': 3 7 10 + * y'': 4 3 + * y''': -1 + */ + EXPECT_FLOAT_EQ(jerkTracker.jerkMagnitude().value(), std::hypot(10, -1)); + jerkTracker.pushSample(/*timestamp=*/4, 20, 65); + /** + * (continuing from above table) + * x: 45 -> 20 + * x': 15 -> -25 + * x'': 10 -> -40 + * x''': -50 + * + * y: 70 -> 65 + * y': 10 -> -5 + * y'': 3 -> -15 + * y''': -18 + */ + EXPECT_FLOAT_EQ(jerkTracker.jerkMagnitude().value(), std::hypot(-50, -18)); +} + +TEST(JerkTrackerTest, JerkCalculationNormalizedDtFalse) { + JerkTracker jerkTracker(false); + jerkTracker.pushSample(/*timestamp=*/0, 20, 50); + jerkTracker.pushSample(/*timestamp=*/10, 25, 53); + jerkTracker.pushSample(/*timestamp=*/20, 30, 60); + jerkTracker.pushSample(/*timestamp=*/30, 45, 70); + /** + * Jerk derivative table + * x: 20 25 30 45 + * x': .5 .5 1.5 + * x'': 0 .1 + * x''': .01 + * + * y: 50 53 60 70 + * y': .3 .7 1 + * y'': .04 .03 + * y''': -.001 + */ + EXPECT_FLOAT_EQ(jerkTracker.jerkMagnitude().value(), std::hypot(.01, -.001)); + jerkTracker.pushSample(/*timestamp=*/50, 20, 65); + /** + * (continuing from above table) + * x: 45 -> 20 + * x': 1.5 -> -1.25 (delta above, divide by 20) + * x'': .1 -> -.275 (delta above, divide by 10) + * x''': -.0375 (delta above, divide by 10) + * + * y: 70 -> 65 + * y': 1 -> -.25 (delta above, divide by 20) + * y'': .03 -> -.125 (delta above, divide by 10) + * y''': -.0155 (delta above, divide by 10) + */ + EXPECT_FLOAT_EQ(jerkTracker.jerkMagnitude().value(), std::hypot(-.0375, -.0155)); +} + +TEST(JerkTrackerTest, JerkCalculationAfterReset) { + JerkTracker jerkTracker(true); + jerkTracker.pushSample(/*timestamp=*/0, 20, 50); + jerkTracker.pushSample(/*timestamp=*/1, 25, 53); + jerkTracker.pushSample(/*timestamp=*/2, 30, 60); + jerkTracker.pushSample(/*timestamp=*/3, 45, 70); + jerkTracker.pushSample(/*timestamp=*/4, 20, 65); + jerkTracker.reset(); + jerkTracker.pushSample(/*timestamp=*/5, 20, 50); + jerkTracker.pushSample(/*timestamp=*/6, 25, 53); + jerkTracker.pushSample(/*timestamp=*/7, 30, 60); + jerkTracker.pushSample(/*timestamp=*/8, 45, 70); + EXPECT_FLOAT_EQ(jerkTracker.jerkMagnitude().value(), std::hypot(10, -1)); +} + TEST(MotionPredictorTest, IsPredictionAvailable) { MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, []() { return true /*enable prediction*/; }); @@ -94,18 +200,14 @@ TEST(MotionPredictorTest, Offset) { TEST(MotionPredictorTest, FollowsGesture) { MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, []() { return true /*enable prediction*/; }); + predictor.record(getMotionEvent(DOWN, 3.75, 3, 20ms)); + predictor.record(getMotionEvent(MOVE, 4.8, 3, 30ms)); + predictor.record(getMotionEvent(MOVE, 6.2, 3, 40ms)); + predictor.record(getMotionEvent(MOVE, 8, 3, 50ms)); + EXPECT_NE(nullptr, predictor.predict(90 * NSEC_PER_MSEC)); - // MOVE without a DOWN is ignored. - predictor.record(getMotionEvent(MOVE, 1, 3, 10ms)); - EXPECT_EQ(nullptr, predictor.predict(20 * NSEC_PER_MSEC)); - - predictor.record(getMotionEvent(DOWN, 2, 5, 20ms)); - predictor.record(getMotionEvent(MOVE, 2, 7, 30ms)); - predictor.record(getMotionEvent(MOVE, 3, 9, 40ms)); - EXPECT_NE(nullptr, predictor.predict(50 * NSEC_PER_MSEC)); - - predictor.record(getMotionEvent(UP, 4, 11, 50ms)); - EXPECT_EQ(nullptr, predictor.predict(20 * NSEC_PER_MSEC)); + predictor.record(getMotionEvent(UP, 10.25, 3, 60ms)); + EXPECT_EQ(nullptr, predictor.predict(100 * NSEC_PER_MSEC)); } TEST(MotionPredictorTest, MultipleDevicesNotSupported) { @@ -147,6 +249,63 @@ TEST(MotionPredictorTest, FlagDisablesPrediction) { ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN)); } +TEST_WITH_FLAGS( + MotionPredictorTest, LowJerkNoPruning, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + enable_prediction_pruning_via_jerk_thresholding))) { + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, + []() { return true /*enable prediction*/; }); + + // Jerk is low (0.05 normalized). + predictor.record(getMotionEvent(DOWN, 2, 7, 20ms)); + predictor.record(getMotionEvent(MOVE, 2.75, 7, 30ms)); + predictor.record(getMotionEvent(MOVE, 3.8, 7, 40ms)); + predictor.record(getMotionEvent(MOVE, 5.2, 7, 50ms)); + predictor.record(getMotionEvent(MOVE, 7, 7, 60ms)); + std::unique_ptr<MotionEvent> predicted = predictor.predict(90 * NSEC_PER_MSEC); + EXPECT_NE(nullptr, predicted); + EXPECT_EQ(static_cast<size_t>(5), predicted->getHistorySize() + 1); +} + +TEST_WITH_FLAGS( + MotionPredictorTest, HighJerkPredictionsPruned, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + enable_prediction_pruning_via_jerk_thresholding))) { + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, + []() { return true /*enable prediction*/; }); + + // Jerk is incredibly high. + predictor.record(getMotionEvent(DOWN, 0, 5, 20ms)); + predictor.record(getMotionEvent(MOVE, 0, 70, 30ms)); + predictor.record(getMotionEvent(MOVE, 0, 139, 40ms)); + predictor.record(getMotionEvent(MOVE, 0, 1421, 50ms)); + predictor.record(getMotionEvent(MOVE, 0, 41233, 60ms)); + std::unique_ptr<MotionEvent> predicted = predictor.predict(90 * NSEC_PER_MSEC); + EXPECT_EQ(nullptr, predicted); +} + +TEST_WITH_FLAGS( + MotionPredictorTest, MediumJerkPredictionsSomePruned, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + enable_prediction_pruning_via_jerk_thresholding))) { + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, + []() { return true /*enable prediction*/; }); + + // Jerk is medium (1.05 normalized, which is halfway between LOW_JANK and HIGH_JANK) + predictor.record(getMotionEvent(DOWN, 0, 5.2, 20ms)); + predictor.record(getMotionEvent(MOVE, 0, 11.5, 30ms)); + predictor.record(getMotionEvent(MOVE, 0, 22, 40ms)); + predictor.record(getMotionEvent(MOVE, 0, 37.75, 50ms)); + predictor.record(getMotionEvent(MOVE, 0, 59.8, 60ms)); + std::unique_ptr<MotionEvent> predicted = predictor.predict(82 * NSEC_PER_MSEC); + EXPECT_NE(nullptr, predicted); + // Halfway between LOW_JANK and HIGH_JANK means that half of the predictions + // will be pruned. If model prediction window is close enough to predict() + // call time window, then half of the model predictions (5/2 -> 2) will be + // ouputted. + EXPECT_EQ(static_cast<size_t>(3), predicted->getHistorySize() + 1); +} + using AtomFields = MotionPredictorMetricsManager::AtomFields; using ReportAtomFunction = MotionPredictorMetricsManager::ReportAtomFunction; diff --git a/libs/nativewindow/rust/Android.bp b/libs/nativewindow/rust/Android.bp index a3df4820ba..97740dbcd8 100644 --- a/libs/nativewindow/rust/Android.bp +++ b/libs/nativewindow/rust/Android.bp @@ -54,6 +54,10 @@ rust_bindgen { }, min_sdk_version: "VanillaIceCream", vendor_available: true, + apex_available: [ + "//apex_available:platform", + "com.android.virt", + ], } rust_library { @@ -78,6 +82,10 @@ rust_library { }, min_sdk_version: "VanillaIceCream", vendor_available: true, + apex_available: [ + "//apex_available:platform", + "com.android.virt", + ], } rust_test { @@ -116,6 +124,10 @@ rust_library { }, min_sdk_version: "VanillaIceCream", vendor_available: true, + apex_available: [ + "//apex_available:platform", + "com.android.virt", + ], } rust_test { diff --git a/libs/nativewindow/rust/src/lib.rs b/libs/nativewindow/rust/src/lib.rs index 22ad83463c..dc3f51f7fd 100644 --- a/libs/nativewindow/rust/src/lib.rs +++ b/libs/nativewindow/rust/src/lib.rs @@ -16,7 +16,8 @@ extern crate nativewindow_bindgen as ffi; -pub mod surface; +mod surface; +pub use surface::Surface; pub use ffi::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags}; diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp index 0defc7e0d3..c003111ebd 100644 --- a/libs/renderengine/Android.bp +++ b/libs/renderengine/Android.bp @@ -84,6 +84,7 @@ filegroup { "skia/Cache.cpp", "skia/ColorSpaces.cpp", "skia/GaneshVkRenderEngine.cpp", + "skia/GraphiteVkRenderEngine.cpp", "skia/GLExtensions.cpp", "skia/SkiaRenderEngine.cpp", "skia/SkiaGLRenderEngine.cpp", @@ -91,6 +92,8 @@ filegroup { "skia/VulkanInterface.cpp", "skia/compat/GaneshBackendTexture.cpp", "skia/compat/GaneshGpuContext.cpp", + "skia/compat/GraphiteBackendTexture.cpp", + "skia/compat/GraphiteGpuContext.cpp", "skia/debug/CaptureTimer.cpp", "skia/debug/CommonPool.cpp", "skia/debug/SkiaCapture.cpp", diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp index 233134d2db..1c60563b25 100644 --- a/libs/renderengine/RenderEngine.cpp +++ b/libs/renderengine/RenderEngine.cpp @@ -16,40 +16,45 @@ #include <renderengine/RenderEngine.h> -#include <cutils/properties.h> -#include <log/log.h> #include "renderengine/ExternalTexture.h" +#include "skia/GaneshVkRenderEngine.h" +#include "skia/GraphiteVkRenderEngine.h" +#include "skia/SkiaGLRenderEngine.h" #include "threaded/RenderEngineThreaded.h" -#include "skia/SkiaGLRenderEngine.h" -#include "skia/SkiaVkRenderEngine.h" +#include <cutils/properties.h> +#include <log/log.h> namespace android { namespace renderengine { std::unique_ptr<RenderEngine> RenderEngine::create(const RenderEngineCreationArgs& args) { - 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); - }); + threaded::CreateInstanceFactory createInstanceFactory; + + ALOGD("%sRenderEngine with %s Backend (%s)", args.threaded == Threaded::YES ? "Threaded " : "", + args.graphicsApi == GraphicsApi::GL ? "SkiaGL" : "SkiaVK", + args.skiaBackend == SkiaBackend::GANESH ? "Ganesh" : "Graphite"); + + if (args.skiaBackend == SkiaBackend::GRAPHITE) { + createInstanceFactory = [args]() { + return android::renderengine::skia::GraphiteVkRenderEngine::create(args); + }; + } else { // GANESH + if (args.graphicsApi == GraphicsApi::VK) { + createInstanceFactory = [args]() { + return android::renderengine::skia::GaneshVkRenderEngine::create(args); + }; + } else { // GL + createInstanceFactory = [args]() { + return android::renderengine::skia::SkiaGLRenderEngine::create(args); + }; } } - switch (args.graphicsApi) { - case GraphicsApi::GL: - ALOGD("RenderEngine with SkiaGL Backend"); - return renderengine::skia::SkiaGLRenderEngine::create(args); - case GraphicsApi::VK: - ALOGD("RenderEngine with SkiaVK Backend"); - return renderengine::skia::SkiaVkRenderEngine::create(args); + if (args.threaded == Threaded::YES) { + return renderengine::threaded::RenderEngineThreaded::create(createInstanceFactory); + } else { + return createInstanceFactory(); } } diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h index de05268a67..00a621372d 100644 --- a/libs/renderengine/include/renderengine/RenderEngine.h +++ b/libs/renderengine/include/renderengine/RenderEngine.h @@ -102,6 +102,11 @@ public: VK, }; + enum class SkiaBackend { + GANESH, + GRAPHITE, + }; + static std::unique_ptr<RenderEngine> create(const RenderEngineCreationArgs& args); static bool canSupport(GraphicsApi); @@ -257,6 +262,7 @@ struct RenderEngineCreationArgs { RenderEngine::ContextPriority contextPriority; RenderEngine::Threaded threaded; RenderEngine::GraphicsApi graphicsApi; + RenderEngine::SkiaBackend skiaBackend; struct Builder; @@ -267,7 +273,8 @@ private: bool _supportsBackgroundBlur, RenderEngine::ContextPriority _contextPriority, RenderEngine::Threaded _threaded, - RenderEngine::GraphicsApi _graphicsApi) + RenderEngine::GraphicsApi _graphicsApi, + RenderEngine::SkiaBackend _skiaBackend) : pixelFormat(_pixelFormat), imageCacheSize(_imageCacheSize), enableProtectedContext(_enableProtectedContext), @@ -275,7 +282,8 @@ private: supportsBackgroundBlur(_supportsBackgroundBlur), contextPriority(_contextPriority), threaded(_threaded), - graphicsApi(_graphicsApi) {} + graphicsApi(_graphicsApi), + skiaBackend(_skiaBackend) {} RenderEngineCreationArgs() = delete; }; @@ -314,10 +322,14 @@ struct RenderEngineCreationArgs::Builder { this->graphicsApi = graphicsApi; return *this; } + Builder& setSkiaBackend(RenderEngine::SkiaBackend skiaBackend) { + this->skiaBackend = skiaBackend; + return *this; + } RenderEngineCreationArgs build() const { return RenderEngineCreationArgs(pixelFormat, imageCacheSize, enableProtectedContext, precacheToneMapperShaderOnly, supportsBackgroundBlur, - contextPriority, threaded, graphicsApi); + contextPriority, threaded, graphicsApi, skiaBackend); } private: @@ -330,6 +342,7 @@ private: RenderEngine::ContextPriority contextPriority = RenderEngine::ContextPriority::MEDIUM; RenderEngine::Threaded threaded = RenderEngine::Threaded::YES; RenderEngine::GraphicsApi graphicsApi = RenderEngine::GraphicsApi::GL; + RenderEngine::SkiaBackend skiaBackend = RenderEngine::SkiaBackend::GANESH; }; } // namespace renderengine diff --git a/libs/renderengine/skia/GaneshVkRenderEngine.cpp b/libs/renderengine/skia/GaneshVkRenderEngine.cpp index aa18713295..68798bf8b4 100644 --- a/libs/renderengine/skia/GaneshVkRenderEngine.cpp +++ b/libs/renderengine/skia/GaneshVkRenderEngine.cpp @@ -27,6 +27,22 @@ namespace android::renderengine::skia { +std::unique_ptr<GaneshVkRenderEngine> GaneshVkRenderEngine::create( + const RenderEngineCreationArgs& args) { + std::unique_ptr<GaneshVkRenderEngine> engine(new GaneshVkRenderEngine(args)); + engine->ensureContextsCreated(); + + if (getVulkanInterface(false).isInitialized()) { + ALOGD("GaneshVkRenderEngine::%s: successfully initialized GaneshVkRenderEngine", __func__); + return engine; + } else { + ALOGE("GaneshVkRenderEngine::%s: could not create GaneshVkRenderEngine. " + "Likely insufficient Vulkan support", + __func__); + return {}; + } +} + // Ganesh-specific function signature for fFinishedProc callback. static void unref_semaphore(void* semaphore) { SkiaVkRenderEngine::DestroySemaphoreInfo* info = diff --git a/libs/renderengine/skia/GaneshVkRenderEngine.h b/libs/renderengine/skia/GaneshVkRenderEngine.h index 5940d04f51..e6123c21bf 100644 --- a/libs/renderengine/skia/GaneshVkRenderEngine.h +++ b/libs/renderengine/skia/GaneshVkRenderEngine.h @@ -21,15 +21,16 @@ namespace android::renderengine::skia { class GaneshVkRenderEngine : public SkiaVkRenderEngine { - friend std::unique_ptr<SkiaVkRenderEngine> SkiaVkRenderEngine::create( - const RenderEngineCreationArgs& args); +public: + static std::unique_ptr<GaneshVkRenderEngine> create(const RenderEngineCreationArgs& args); protected: - GaneshVkRenderEngine(const RenderEngineCreationArgs& args) : SkiaVkRenderEngine(args) {} - std::unique_ptr<SkiaGpuContext> createContext(VulkanInterface& vulkanInterface) override; void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override; base::unique_fd flushAndSubmit(SkiaGpuContext* context, sk_sp<SkSurface> dstSurface) override; + +private: + GaneshVkRenderEngine(const RenderEngineCreationArgs& args) : SkiaVkRenderEngine(args) {} }; } // namespace android::renderengine::skia diff --git a/libs/renderengine/skia/GraphiteVkRenderEngine.cpp b/libs/renderengine/skia/GraphiteVkRenderEngine.cpp new file mode 100644 index 0000000000..b5cb21b35d --- /dev/null +++ b/libs/renderengine/skia/GraphiteVkRenderEngine.cpp @@ -0,0 +1,140 @@ +/* + * 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 "GraphiteVkRenderEngine.h" + +#undef LOG_TAG +#define LOG_TAG "RenderEngine" + +#include <include/gpu/GpuTypes.h> +#include <include/gpu/graphite/BackendSemaphore.h> +#include <include/gpu/graphite/Context.h> +#include <include/gpu/graphite/Recording.h> + +#include <log/log_main.h> +#include <sync/sync.h> + +#include <memory> +#include <vector> + +namespace android::renderengine::skia { + +std::unique_ptr<GraphiteVkRenderEngine> GraphiteVkRenderEngine::create( + const RenderEngineCreationArgs& args) { + std::unique_ptr<GraphiteVkRenderEngine> engine(new GraphiteVkRenderEngine(args)); + engine->ensureContextsCreated(); + + if (getVulkanInterface(false).isInitialized()) { + ALOGD("GraphiteVkRenderEngine::%s: successfully initialized GraphiteVkRenderEngine", + __func__); + return engine; + } else { + ALOGE("GraphiteVkRenderEngine::%s: could not create GraphiteVkRenderEngine. " + "Likely insufficient Vulkan support", + __func__); + return {}; + } +} + +// Graphite-specific function signature for fFinishedProc callback. +static void unref_semaphore(void* semaphore, skgpu::CallbackResult result) { + if (result != skgpu::CallbackResult::kSuccess) { + ALOGE("Graphite submission of work to GPU failed, check for Skia errors"); + } + SkiaVkRenderEngine::DestroySemaphoreInfo* info = + reinterpret_cast<SkiaVkRenderEngine::DestroySemaphoreInfo*>(semaphore); + info->unref(); +} + +std::unique_ptr<SkiaGpuContext> GraphiteVkRenderEngine::createContext( + VulkanInterface& vulkanInterface) { + return SkiaGpuContext::MakeVulkan_Graphite(vulkanInterface.getGraphiteBackendContext()); +} + +void GraphiteVkRenderEngine::waitFence(SkiaGpuContext*, base::borrowed_fd fenceFd) { + if (fenceFd.get() < 0) return; + + int dupedFd = dup(fenceFd.get()); + if (dupedFd < 0) { + ALOGE("failed to create duplicate fence fd: %d", dupedFd); + sync_wait(fenceFd.get(), -1); + return; + } + + base::unique_fd fenceDup(dupedFd); + VkSemaphore waitSemaphore = + getVulkanInterface(isProtected()).importSemaphoreFromSyncFd(fenceDup.release()); + graphite::BackendSemaphore beSemaphore(waitSemaphore); + mStagedWaitSemaphores.push_back(beSemaphore); +} + +base::unique_fd GraphiteVkRenderEngine::flushAndSubmit(SkiaGpuContext* context, sk_sp<SkSurface>) { + // Minimal Recording setup. Required even if there are no incoming semaphores to wait on, and if + // creating the outgoing signaling semaphore fails. + std::unique_ptr<graphite::Recording> recording = context->graphiteRecorder()->snap(); + graphite::InsertRecordingInfo insertInfo; + insertInfo.fRecording = recording.get(); + + VulkanInterface& vulkanInterface = getVulkanInterface(isProtected()); + // This "signal" semaphore is called after rendering, but it is cleaned up in the same mechanism + // as "wait" semaphores from waitFence. + VkSemaphore vkSignalSemaphore = vulkanInterface.createExportableSemaphore(); + graphite::BackendSemaphore backendSignalSemaphore(vkSignalSemaphore); + + // Collect all Vk semaphores that DestroySemaphoreInfo needs to own and delete after GPU work. + std::vector<VkSemaphore> vkSemaphoresToCleanUp; + if (vkSignalSemaphore != VK_NULL_HANDLE) { + vkSemaphoresToCleanUp.push_back(vkSignalSemaphore); + } + for (auto backendWaitSemaphore : mStagedWaitSemaphores) { + vkSemaphoresToCleanUp.push_back(backendWaitSemaphore.getVkSemaphore()); + } + + DestroySemaphoreInfo* destroySemaphoreInfo = nullptr; + if (vkSemaphoresToCleanUp.size() > 0) { + destroySemaphoreInfo = + new DestroySemaphoreInfo(vulkanInterface, std::move(vkSemaphoresToCleanUp)); + + insertInfo.fNumWaitSemaphores = mStagedWaitSemaphores.size(); + insertInfo.fWaitSemaphores = mStagedWaitSemaphores.data(); + insertInfo.fNumSignalSemaphores = 1; + insertInfo.fSignalSemaphores = &backendSignalSemaphore; + insertInfo.fFinishedProc = unref_semaphore; + insertInfo.fFinishedContext = destroySemaphoreInfo; + } + + const bool inserted = context->graphiteContext()->insertRecording(insertInfo); + LOG_ALWAYS_FATAL_IF(!inserted, + "graphite::Context::insertRecording(...) failed, check for Skia errors"); + const bool submitted = context->graphiteContext()->submit(graphite::SyncToCpu::kNo); + LOG_ALWAYS_FATAL_IF(!submitted, "graphite::Context::submit(...) failed, check for Skia errors"); + // Skia's "backend" semaphores can be deleted immediately after inserting the recording; only + // the underlying VK semaphores need to be kept until GPU work is complete. + mStagedWaitSemaphores.clear(); + + base::unique_fd drawFenceFd(-1); + if (vkSignalSemaphore != VK_NULL_HANDLE) { + drawFenceFd.reset(vulkanInterface.exportSemaphoreSyncFd(vkSignalSemaphore)); + } + // Now that drawFenceFd has been created, we can delete RE's reference to this semaphore, as + // another reference is still held until fFinishedProc is called after completion of GPU work. + if (destroySemaphoreInfo) { + destroySemaphoreInfo->unref(); + } + return drawFenceFd; +} + +} // namespace android::renderengine::skia diff --git a/libs/renderengine/skia/GraphiteVkRenderEngine.h b/libs/renderengine/skia/GraphiteVkRenderEngine.h new file mode 100644 index 0000000000..cf24a3b756 --- /dev/null +++ b/libs/renderengine/skia/GraphiteVkRenderEngine.h @@ -0,0 +1,40 @@ +/* + * 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 "SkiaVkRenderEngine.h" + +#include <include/gpu/graphite/BackendSemaphore.h> + +namespace android::renderengine::skia { + +class GraphiteVkRenderEngine : public SkiaVkRenderEngine { +public: + static std::unique_ptr<GraphiteVkRenderEngine> create(const RenderEngineCreationArgs& args); + +protected: + std::unique_ptr<SkiaGpuContext> createContext(VulkanInterface& vulkanInterface) override; + void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override; + base::unique_fd flushAndSubmit(SkiaGpuContext* context, sk_sp<SkSurface> dstSurface) override; + +private: + GraphiteVkRenderEngine(const RenderEngineCreationArgs& args) : SkiaVkRenderEngine(args) {} + + std::vector<graphite::BackendSemaphore> mStagedWaitSemaphores; +}; + +} // namespace android::renderengine::skia diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp index 3715859e8a..fd71332089 100644 --- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp @@ -83,23 +83,6 @@ namespace skia { using base::StringAppendF; -std::unique_ptr<SkiaVkRenderEngine> SkiaVkRenderEngine::create( - const RenderEngineCreationArgs& args) { - // TODO: b/293371537 - Ganesh vs. Graphite subclass based on flag in RenderEngineCreationArgs - std::unique_ptr<SkiaVkRenderEngine> engine(new GaneshVkRenderEngine(args)); - engine->ensureContextsCreated(); - - if (sVulkanInterface.isInitialized()) { - ALOGD("SkiaVkRenderEngine::%s: successfully initialized SkiaVkRenderEngine", __func__); - return engine; - } else { - ALOGD("SkiaVkRenderEngine::%s: could not create SkiaVkRenderEngine. " - "Likely insufficient Vulkan support", - __func__); - return {}; - } -} - SkiaVkRenderEngine::SkiaVkRenderEngine(const RenderEngineCreationArgs& args) : SkiaRenderEngine(args.threaded, static_cast<PixelFormat>(args.pixelFormat), args.supportsBackgroundBlur) {} diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h index 371b812897..0a2f9b2228 100644 --- a/libs/renderengine/skia/SkiaVkRenderEngine.h +++ b/libs/renderengine/skia/SkiaVkRenderEngine.h @@ -29,7 +29,6 @@ namespace skia { class SkiaVkRenderEngine : public SkiaRenderEngine { public: - static std::unique_ptr<SkiaVkRenderEngine> create(const RenderEngineCreationArgs& args); ~SkiaVkRenderEngine() override; int getContextPriority() override; diff --git a/libs/renderengine/skia/VulkanInterface.cpp b/libs/renderengine/skia/VulkanInterface.cpp index 2f09a382d7..49b9f1ea62 100644 --- a/libs/renderengine/skia/VulkanInterface.cpp +++ b/libs/renderengine/skia/VulkanInterface.cpp @@ -20,6 +20,8 @@ #include "VulkanInterface.h" #include <include/gpu/GpuTypes.h> +#include <include/gpu/vk/VulkanBackendContext.h> + #include <log/log_main.h> #include <utils/Timers.h> @@ -47,6 +49,23 @@ GrVkBackendContext VulkanInterface::getGaneshBackendContext() { return backendContext; }; +VulkanBackendContext VulkanInterface::getGraphiteBackendContext() { + VulkanBackendContext backendContext; + backendContext.fInstance = mInstance; + backendContext.fPhysicalDevice = mPhysicalDevice; + backendContext.fDevice = mDevice; + backendContext.fQueue = mQueue; + backendContext.fGraphicsQueueIndex = mQueueIndex; + backendContext.fMaxAPIVersion = mApiVersion; + backendContext.fVkExtensions = &mGrExtensions; + backendContext.fDeviceFeatures2 = mPhysicalDeviceFeatures2; + backendContext.fGetProc = mGrGetProc; + backendContext.fProtectedContext = mIsProtected ? Protected::kYes : Protected::kNo; + backendContext.fDeviceLostContext = this; // VulkanInterface is long-lived + backendContext.fDeviceLostProc = onVkDeviceFault; + return backendContext; +}; + VkSemaphore VulkanInterface::createExportableSemaphore() { VkExportSemaphoreCreateInfo exportInfo; exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO; diff --git a/libs/renderengine/skia/VulkanInterface.h b/libs/renderengine/skia/VulkanInterface.h index 512e62c8f2..2824fcb17a 100644 --- a/libs/renderengine/skia/VulkanInterface.h +++ b/libs/renderengine/skia/VulkanInterface.h @@ -23,6 +23,10 @@ using namespace skgpu; +namespace skgpu { +struct VulkanBackendContext; +} // namespace skgpu + namespace android { namespace renderengine { namespace skia { @@ -39,8 +43,8 @@ public: void init(bool protectedContent = false); void teardown(); - // TODO: b/293371537 - Graphite variant (external/skia/include/gpu/vk/VulkanBackendContext.h) GrVkBackendContext getGaneshBackendContext(); + VulkanBackendContext getGraphiteBackendContext(); VkSemaphore createExportableSemaphore(); VkSemaphore importSemaphoreFromSyncFd(int syncFd); int exportSemaphoreSyncFd(VkSemaphore semaphore); diff --git a/libs/renderengine/skia/compat/GraphiteBackendTexture.cpp b/libs/renderengine/skia/compat/GraphiteBackendTexture.cpp new file mode 100644 index 0000000000..3dd9ed242e --- /dev/null +++ b/libs/renderengine/skia/compat/GraphiteBackendTexture.cpp @@ -0,0 +1,114 @@ +/* + * 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 "GraphiteBackendTexture.h" + +#undef LOG_TAG +#define LOG_TAG "RenderEngine" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + +#include <include/core/SkSurfaceProps.h> +#include <include/gpu/graphite/Image.h> +#include <include/gpu/graphite/Surface.h> +#include <include/gpu/graphite/TextureInfo.h> + +#include "skia/ColorSpaces.h" + +#include <android/hardware_buffer.h> +#include <inttypes.h> +#include <log/log_main.h> +#include <utils/Trace.h> + +namespace android::renderengine::skia { + +GraphiteBackendTexture::GraphiteBackendTexture(std::shared_ptr<skgpu::graphite::Recorder> recorder, + AHardwareBuffer* buffer, bool isOutputBuffer) + : SkiaBackendTexture(buffer, isOutputBuffer), mRecorder(std::move(recorder)) { + ATRACE_CALL(); + AHardwareBuffer_Desc desc; + AHardwareBuffer_describe(buffer, &desc); + const bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT); + + const SkISize dimensions = {static_cast<int32_t>(desc.width), + static_cast<int32_t>(desc.height)}; + LOG_ALWAYS_FATAL_IF(static_cast<uint32_t>(dimensions.width()) != desc.width || + static_cast<uint32_t>(dimensions.height()) != desc.height, + "Failed to create a valid texture, casting unsigned dimensions [%" PRIu32 + ",%" PRIu32 "] to signed [%" PRIo32 ",%" PRIo32 "] " + "is invalid", + desc.width, desc.height, dimensions.width(), dimensions.height()); + + mBackendTexture = mRecorder->createBackendTexture(buffer, isOutputBuffer, createProtectedImage, + dimensions, false); + if (!mBackendTexture.isValid() || !dimensions.width() || !dimensions.height()) { + LOG_ALWAYS_FATAL("Failed to create a valid texture. [%p]:[%d,%d] isProtected:%d " + "isWriteable:%d format:%d", + this, dimensions.width(), dimensions.height(), createProtectedImage, + isOutputBuffer, desc.format); + } +} + +GraphiteBackendTexture::~GraphiteBackendTexture() { + if (mBackendTexture.isValid()) { + mRecorder->deleteBackendTexture(mBackendTexture); + mBackendTexture = {}; + } +} + +sk_sp<SkImage> GraphiteBackendTexture::makeImage(SkAlphaType alphaType, ui::Dataspace dataspace, + TextureReleaseProc releaseImageProc, + ReleaseContext releaseContext) { + const SkColorType colorType = colorTypeForImage(alphaType); + sk_sp<SkImage> image = + SkImages::WrapTexture(mRecorder.get(), mBackendTexture, colorType, alphaType, + toSkColorSpace(dataspace), releaseImageProc, releaseContext); + if (!image) { + logFatalTexture("Unable to generate SkImage.", dataspace, colorType); + } + return image; +} + +sk_sp<SkSurface> GraphiteBackendTexture::makeSurface(ui::Dataspace dataspace, + TextureReleaseProc releaseSurfaceProc, + ReleaseContext releaseContext) { + const SkColorType colorType = internalColorType(); + SkSurfaceProps props; + sk_sp<SkSurface> surface = + SkSurfaces::WrapBackendTexture(mRecorder.get(), mBackendTexture, colorType, + toSkColorSpace(dataspace), &props, releaseSurfaceProc, + releaseContext); + if (!surface) { + logFatalTexture("Unable to generate SkSurface.", dataspace, colorType); + } + return surface; +} + +void GraphiteBackendTexture::logFatalTexture(const char* msg, ui::Dataspace dataspace, + SkColorType colorType) { + // TODO: b/293371537 - Iterate on this logging (validate failure cases, possibly check + // VulkanTextureInfo, etc.) + const skgpu::graphite::TextureInfo& textureInfo = mBackendTexture.info(); + LOG_ALWAYS_FATAL("%s isOutputBuffer:%d, dataspace:%d, colorType:%d" + "\n\tBackendTexture: isValid:%d, dimensions:%dx%d" + "\n\t\tTextureInfo: isValid:%d, numSamples:%d, mipmapped:%d, isProtected: %d", + msg, isOutputBuffer(), static_cast<int32_t>(dataspace), colorType, + mBackendTexture.isValid(), mBackendTexture.dimensions().width(), + mBackendTexture.dimensions().height(), textureInfo.isValid(), + textureInfo.numSamples(), static_cast<int32_t>(textureInfo.mipmapped()), + static_cast<int32_t>(textureInfo.isProtected())); +} + +} // namespace android::renderengine::skia diff --git a/libs/renderengine/skia/compat/GraphiteBackendTexture.h b/libs/renderengine/skia/compat/GraphiteBackendTexture.h new file mode 100644 index 0000000000..3bec3f7e4b --- /dev/null +++ b/libs/renderengine/skia/compat/GraphiteBackendTexture.h @@ -0,0 +1,59 @@ +/* + * 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 "SkiaBackendTexture.h" + +#include <include/core/SkColorSpace.h> +#include <include/core/SkImage.h> +#include <include/core/SkSurface.h> +#include <include/gpu/graphite/BackendTexture.h> +#include <include/gpu/graphite/Recorder.h> + +#include <android-base/macros.h> +#include <ui/GraphicTypes.h> + +#include <memory> + +namespace android::renderengine::skia { + +class GraphiteBackendTexture : public SkiaBackendTexture { +public: + // Creates an internal skgpu::graphite::BackendTexture whose contents come from the provided + // buffer. + GraphiteBackendTexture(std::shared_ptr<skgpu::graphite::Recorder> recorder, + AHardwareBuffer* buffer, bool isOutputBuffer); + + ~GraphiteBackendTexture() override; + + sk_sp<SkImage> makeImage(SkAlphaType alphaType, ui::Dataspace dataspace, + TextureReleaseProc releaseImageProc, + ReleaseContext releaseContext) override; + + sk_sp<SkSurface> makeSurface(ui::Dataspace dataspace, TextureReleaseProc releaseSurfaceProc, + ReleaseContext releaseContext) override; + +private: + DISALLOW_COPY_AND_ASSIGN(GraphiteBackendTexture); + + void logFatalTexture(const char* msg, ui::Dataspace dataspace, SkColorType colorType); + + const std::shared_ptr<skgpu::graphite::Recorder> mRecorder; + skgpu::graphite::BackendTexture mBackendTexture; +}; + +} // namespace android::renderengine::skia diff --git a/libs/renderengine/skia/compat/GraphiteGpuContext.cpp b/libs/renderengine/skia/compat/GraphiteGpuContext.cpp new file mode 100644 index 0000000000..e19d66f3c2 --- /dev/null +++ b/libs/renderengine/skia/compat/GraphiteGpuContext.cpp @@ -0,0 +1,106 @@ +/* + * 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 "GraphiteGpuContext.h" + +#include <include/core/SkImageInfo.h> +#include <include/core/SkSurface.h> +#include <include/core/SkTraceMemoryDump.h> +#include <include/gpu/graphite/GraphiteTypes.h> +#include <include/gpu/graphite/Surface.h> +#include <include/gpu/graphite/vk/VulkanGraphiteUtils.h> + +#include "GpuTypes.h" +#include "skia/compat/GraphiteBackendTexture.h" + +#include <android-base/macros.h> +#include <log/log_main.h> +#include <memory> + +namespace android::renderengine::skia { + +namespace { +static skgpu::graphite::ContextOptions graphiteOptions() { + skgpu::graphite::ContextOptions options; + options.fDisableDriverCorrectnessWorkarounds = true; + return options; +} +} // namespace + +std::unique_ptr<SkiaGpuContext> SkiaGpuContext::MakeVulkan_Graphite( + const skgpu::VulkanBackendContext& vulkanBackendContext) { + return std::make_unique<GraphiteGpuContext>( + skgpu::graphite::ContextFactory::MakeVulkan(vulkanBackendContext, graphiteOptions())); +} + +GraphiteGpuContext::GraphiteGpuContext(std::unique_ptr<skgpu::graphite::Context> context) + : mContext(std::move(context)) { + LOG_ALWAYS_FATAL_IF(mContext.get() == nullptr, "graphite::Context creation failed"); + LOG_ALWAYS_FATAL_IF(mContext->backend() != skgpu::BackendApi::kVulkan, + "graphite::Context::backend() == %d, but GraphiteBackendContext makes " + "assumptions that are only valid for Vulkan (%d)", + static_cast<int>(mContext->backend()), + static_cast<int>(skgpu::BackendApi::kVulkan)); + + // TODO: b/293371537 - Iterate on default cache limits (the Recorder should have the majority of + // the budget, and the Context should be given a smaller fraction.) + skgpu::graphite::RecorderOptions recorderOptions = skgpu::graphite::RecorderOptions(); + this->mRecorder = mContext->makeRecorder(recorderOptions); + LOG_ALWAYS_FATAL_IF(mRecorder.get() == nullptr, "graphite::Recorder creation failed"); +} + +std::shared_ptr<skgpu::graphite::Context> GraphiteGpuContext::graphiteContext() { + return mContext; +} + +std::shared_ptr<skgpu::graphite::Recorder> GraphiteGpuContext::graphiteRecorder() { + return mRecorder; +} + +std::unique_ptr<SkiaBackendTexture> GraphiteGpuContext::makeBackendTexture(AHardwareBuffer* buffer, + bool isOutputBuffer) { + return std::make_unique<GraphiteBackendTexture>(graphiteRecorder(), buffer, isOutputBuffer); +} + +sk_sp<SkSurface> GraphiteGpuContext::createRenderTarget(SkImageInfo imageInfo) { + constexpr SkSurfaceProps* kProps = nullptr; + return SkSurfaces::RenderTarget(mRecorder.get(), imageInfo, skgpu::Mipmapped::kNo, kProps); +} + +size_t GraphiteGpuContext::getMaxRenderTargetSize() const { + // maxRenderTargetSize only differs from maxTextureSize on GL, so as long as Graphite implies + // Vk, then the distinction is irrelevant. + return getMaxTextureSize(); +}; + +size_t GraphiteGpuContext::getMaxTextureSize() const { + return mContext->maxTextureSize(); +}; + +bool GraphiteGpuContext::isAbandonedOrDeviceLost() { + return mContext->isDeviceLost(); +} + +void GraphiteGpuContext::finishRenderingAndAbandonContext() { + // TODO: b/293371537 - Validate that nothing else needs to be explicitly abandoned. + mContext->submit(skgpu::graphite::SyncToCpu::kYes); +}; + +void GraphiteGpuContext::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const { + mContext->dumpMemoryStatistics(traceMemoryDump); +} + +} // namespace android::renderengine::skia diff --git a/libs/renderengine/skia/compat/GraphiteGpuContext.h b/libs/renderengine/skia/compat/GraphiteGpuContext.h new file mode 100644 index 0000000000..685f899310 --- /dev/null +++ b/libs/renderengine/skia/compat/GraphiteGpuContext.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 "SkiaGpuContext.h" +#include "graphite/Recorder.h" + +#include <android-base/macros.h> + +namespace android::renderengine::skia { + +class GraphiteGpuContext : public SkiaGpuContext { +public: + GraphiteGpuContext(std::unique_ptr<skgpu::graphite::Context> context); + ~GraphiteGpuContext() override = default; + + std::shared_ptr<skgpu::graphite::Context> graphiteContext() override; + std::shared_ptr<skgpu::graphite::Recorder> graphiteRecorder() override; + + std::unique_ptr<SkiaBackendTexture> makeBackendTexture(AHardwareBuffer* buffer, + bool isOutputBuffer) override; + + sk_sp<SkSurface> createRenderTarget(SkImageInfo imageInfo) override; + + size_t getMaxRenderTargetSize() const override; + size_t getMaxTextureSize() const override; + bool isAbandonedOrDeviceLost() override; + // No-op (large resources like textures, surfaces, images, etc. created by clients don't count + // towards Graphite's internal caching budgets, so adjusting its limits based on display change + // events should be unnecessary. Additionally, Graphite doesn't expose many cache tweaking + // functions yet, as its design may evolve.) + void setResourceCacheLimit(size_t maxResourceBytes) override{}; + + void finishRenderingAndAbandonContext() override; + // TODO: b/293371537 - Triple-check and validate that no cleanup is necessary when switching + // contexts. + // No-op (unnecessary during context switch for Graphite's client-budgeted memory model). + void purgeUnlockedScratchResources() override{}; + // No-op (only applicable to GL). + void resetContextIfApplicable() override{}; + + void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const override; + +private: + DISALLOW_COPY_AND_ASSIGN(GraphiteGpuContext); + + const std::shared_ptr<skgpu::graphite::Context> mContext; + std::shared_ptr<skgpu::graphite::Recorder> mRecorder; +}; + +} // namespace android::renderengine::skia diff --git a/libs/renderengine/skia/compat/SkiaGpuContext.h b/libs/renderengine/skia/compat/SkiaGpuContext.h index 8222e6281d..a2457e5c15 100644 --- a/libs/renderengine/skia/compat/SkiaGpuContext.h +++ b/libs/renderengine/skia/compat/SkiaGpuContext.h @@ -22,7 +22,9 @@ #include <include/core/SkSurface.h> #include <include/gpu/GrDirectContext.h> #include <include/gpu/gl/GrGLInterface.h> +#include <include/gpu/graphite/Context.h> #include <include/gpu/vk/GrVkBackendContext.h> +#include "include/gpu/vk/VulkanBackendContext.h" #include "SkiaBackendTexture.h" @@ -41,14 +43,16 @@ public: sk_sp<const GrGLInterface> glInterface, GrContextOptions::PersistentCache& skSLCacheMonitor); - // TODO: b/293371537 - Graphite variant. static std::unique_ptr<SkiaGpuContext> MakeVulkan_Ganesh( const GrVkBackendContext& grVkBackendContext, GrContextOptions::PersistentCache& skSLCacheMonitor); + // TODO: b/293371537 - Need shader / pipeline monitoring support in Graphite. + static std::unique_ptr<SkiaGpuContext> MakeVulkan_Graphite( + const skgpu::VulkanBackendContext& vulkanBackendContext); + virtual ~SkiaGpuContext() = default; - // TODO: b/293371537 - Maybe expose whether this SkiaGpuContext is using Ganesh or Graphite? /** * Only callable on Ganesh-backed instances of SkiaGpuContext, otherwise fatal. */ @@ -56,6 +60,20 @@ public: LOG_ALWAYS_FATAL("grDirectContext() called on a non-Ganesh instance of SkiaGpuContext!"); } + /** + * Only callable on Graphite-backed instances of SkiaGpuContext, otherwise fatal. + */ + virtual std::shared_ptr<skgpu::graphite::Context> graphiteContext() { + LOG_ALWAYS_FATAL("graphiteContext() called on a non-Graphite instance of SkiaGpuContext!"); + } + + /** + * Only callable on Graphite-backed instances of SkiaGpuContext, otherwise fatal. + */ + virtual std::shared_ptr<skgpu::graphite::Recorder> graphiteRecorder() { + LOG_ALWAYS_FATAL("graphiteRecorder() called on a non-Graphite instance of SkiaGpuContext!"); + } + virtual std::unique_ptr<SkiaBackendTexture> makeBackendTexture(AHardwareBuffer* buffer, bool isOutputBuffer) = 0; diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index 3a07205ea6..9f614bdd37 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -107,6 +107,7 @@ public: virtual std::string name() = 0; virtual renderengine::RenderEngine::GraphicsApi graphicsApi() = 0; + virtual renderengine::RenderEngine::SkiaBackend skiaBackend() = 0; bool apiSupported() { return renderengine::RenderEngine::canSupport(graphicsApi()); } std::unique_ptr<renderengine::RenderEngine> createRenderEngine() { renderengine::RenderEngineCreationArgs reCreationArgs = @@ -119,26 +120,48 @@ public: .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM) .setThreaded(renderengine::RenderEngine::Threaded::NO) .setGraphicsApi(graphicsApi()) + .setSkiaBackend(skiaBackend()) .build(); return renderengine::RenderEngine::create(reCreationArgs); } }; -class SkiaVkRenderEngineFactory : public RenderEngineFactory { +class SkiaGLESRenderEngineFactory : public RenderEngineFactory { public: - std::string name() override { return "SkiaVkRenderEngineFactory"; } + std::string name() override { return "SkiaGLRenderEngineFactory"; } + + renderengine::RenderEngine::GraphicsApi graphicsApi() { + return renderengine::RenderEngine::GraphicsApi::GL; + } + + renderengine::RenderEngine::SkiaBackend skiaBackend() override { + return renderengine::RenderEngine::SkiaBackend::GANESH; + } +}; + +class GaneshVkRenderEngineFactory : public RenderEngineFactory { +public: + std::string name() override { return "GaneshVkRenderEngineFactory"; } renderengine::RenderEngine::GraphicsApi graphicsApi() override { return renderengine::RenderEngine::GraphicsApi::VK; } + + renderengine::RenderEngine::SkiaBackend skiaBackend() override { + return renderengine::RenderEngine::SkiaBackend::GANESH; + } }; -class SkiaGLESRenderEngineFactory : public RenderEngineFactory { +class GraphiteVkRenderEngineFactory : public RenderEngineFactory { public: - std::string name() override { return "SkiaGLRenderEngineFactory"; } + std::string name() override { return "GraphiteVkRenderEngineFactory"; } - renderengine::RenderEngine::GraphicsApi graphicsApi() { - return renderengine::RenderEngine::GraphicsApi::GL; + renderengine::RenderEngine::GraphicsApi graphicsApi() override { + return renderengine::RenderEngine::GraphicsApi::VK; + } + + renderengine::RenderEngine::SkiaBackend skiaBackend() override { + return renderengine::RenderEngine::SkiaBackend::GRAPHITE; } }; @@ -1242,7 +1265,12 @@ void RenderEngineTest::fillRedBufferWithPremultiplyAlpha() { void RenderEngineTest::fillBufferWithPremultiplyAlpha() { fillRedBufferWithPremultiplyAlpha(); - expectBufferColor(fullscreenRect(), 128, 0, 0, 128); + // Different backends and GPUs may round 255 * 0.5 = 127.5 differently, but + // either 127 or 128 are acceptable. Checking both 127 and 128 with a + // tolerance of 1 allows either 127 or 128 to pass, while preventing 126 or + // 129 from erroneously passing. + expectBufferColor(fullscreenRect(), 127, 0, 0, 127, 1); + expectBufferColor(fullscreenRect(), 128, 0, 0, 128, 1); } void RenderEngineTest::fillRedBufferWithoutPremultiplyAlpha() { @@ -1471,7 +1499,8 @@ void RenderEngineTest::tonemap(ui::Dataspace sourceDataspace, std::function<vec3 INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest, testing::Values(std::make_shared<SkiaGLESRenderEngineFactory>(), - std::make_shared<SkiaVkRenderEngineFactory>())); + std::make_shared<GaneshVkRenderEngineFactory>(), + std::make_shared<GraphiteVkRenderEngineFactory>())); TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) { if (!GetParam()->apiSupported()) { @@ -3102,6 +3131,11 @@ TEST_P(RenderEngineTest, r8_respects_color_transform_when_device_handles) { } TEST_P(RenderEngineTest, primeShaderCache) { + // TODO: b/331447071 - Fix in Graphite and re-enable. + if (GetParam()->skiaBackend() == renderengine::RenderEngine::SkiaBackend::GRAPHITE) { + GTEST_SKIP(); + } + if (!GetParam()->apiSupported()) { GTEST_SKIP(); } diff --git a/libs/sensor/Android.bp b/libs/sensor/Android.bp index cc92bc30a0..7fa47b45f0 100644 --- a/libs/sensor/Android.bp +++ b/libs/sensor/Android.bp @@ -24,6 +24,7 @@ package { aconfig_declarations { name: "libsensor_flags", package: "com.android.hardware.libsensor.flags", + container: "system", srcs: ["libsensor_flags.aconfig"], } diff --git a/libs/sensor/libsensor_flags.aconfig b/libs/sensor/libsensor_flags.aconfig index ef4d7370b5..c511f4a72f 100644 --- a/libs/sensor/libsensor_flags.aconfig +++ b/libs/sensor/libsensor_flags.aconfig @@ -1,4 +1,5 @@ package: "com.android.hardware.libsensor.flags" +container: "system" flag { name: "sensormanager_ping_binder" @@ -6,4 +7,4 @@ flag { description: "Whether to pingBinder on SensorManager init" bug: "322228259" is_fixed_read_only: true -}
\ No newline at end of file +} diff --git a/libs/ui/include/ui/LayerStack.h b/libs/ui/include/ui/LayerStack.h index d6ffeb7fad..f4c8ba250e 100644 --- a/libs/ui/include/ui/LayerStack.h +++ b/libs/ui/include/ui/LayerStack.h @@ -55,6 +55,10 @@ inline bool operator>(LayerStack lhs, LayerStack rhs) { return lhs.id > rhs.id; } +inline bool operator<(LayerStack lhs, LayerStack rhs) { + return lhs.id < rhs.id; +} + // A LayerFilter determines if a layer is included for output to a display. struct LayerFilter { LayerStack layerStack; diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp index 1aa1077d54..10efea538c 100644 --- a/services/inputflinger/PointerChoreographer.cpp +++ b/services/inputflinger/PointerChoreographer.cpp @@ -147,6 +147,7 @@ NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotio << args.dump(); } + mMouseDevices.emplace(args.deviceId); auto [displayId, pc] = ensureMouseControllerLocked(args.displayId); NotifyMotionArgs newArgs(args); newArgs.displayId = displayId; @@ -178,6 +179,7 @@ NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotio } NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMotionArgs& args) { + mMouseDevices.emplace(args.deviceId); auto [displayId, pc] = ensureMouseControllerLocked(args.displayId); NotifyMotionArgs newArgs(args); @@ -405,8 +407,10 @@ std::pair<int32_t, PointerControllerInterface&> PointerChoreographer::ensureMous const int32_t displayId = getTargetMouseDisplayLocked(associatedDisplayId); auto it = mMousePointersByDisplay.find(displayId); - LOG_ALWAYS_FATAL_IF(it == mMousePointersByDisplay.end(), - "There is no mouse controller created for display %d", displayId); + if (it == mMousePointersByDisplay.end()) { + it = mMousePointersByDisplay.emplace(displayId, getMouseControllerConstructor(displayId)) + .first; + } return {displayId, *it->second}; } @@ -431,7 +435,9 @@ PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerCo // new PointerControllers if necessary. for (const auto& info : mInputDeviceInfos) { const uint32_t sources = info.getSources(); - if (isMouseOrTouchpad(sources)) { + const bool isKnownMouse = mMouseDevices.count(info.getId()) != 0; + + if (isMouseOrTouchpad(sources) || isKnownMouse) { const int32_t displayId = getTargetMouseDisplayLocked(info.getAssociatedDisplayId()); mouseDisplaysToKeep.insert(displayId); // For mice, show the cursor immediately when the device is first connected or @@ -439,8 +445,8 @@ PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerCo auto [mousePointerIt, isNewMousePointer] = mMousePointersByDisplay.try_emplace(displayId, getMouseControllerConstructor(displayId)); - auto [_, isNewMouseDevice] = mMouseDevices.emplace(info.getId()); - if ((isNewMouseDevice || isNewMousePointer) && canUnfadeOnDisplay(displayId)) { + mMouseDevices.emplace(info.getId()); + if ((!isKnownMouse || isNewMousePointer) && canUnfadeOnDisplay(displayId)) { mousePointerIt->second->unfade(PointerControllerInterface::Transition::IMMEDIATE); } } diff --git a/services/inputflinger/benchmarks/Android.bp b/services/inputflinger/benchmarks/Android.bp index 2d1257478d..438507229b 100644 --- a/services/inputflinger/benchmarks/Android.bp +++ b/services/inputflinger/benchmarks/Android.bp @@ -11,6 +11,7 @@ package { cc_benchmark { name: "inputflinger_benchmarks", srcs: [ + ":inputdispatcher_common_test_sources", "InputDispatcher_benchmarks.cpp", ], defaults: [ @@ -31,6 +32,8 @@ cc_benchmark { ], static_libs: [ "libattestation", + "libgmock", + "libgtest", "libinputdispatcher", ], } diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp index 5ae3715f2f..5f955908c5 100644 --- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp +++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp @@ -22,7 +22,7 @@ #include "../dispatcher/InputDispatcher.h" #include "../tests/FakeApplicationHandle.h" #include "../tests/FakeInputDispatcherPolicy.h" -#include "../tests/FakeWindowHandle.h" +#include "../tests/FakeWindows.h" using android::base::Result; using android::gui::WindowInfo; @@ -104,16 +104,16 @@ static NotifyMotionArgs generateMotionArgs() { static void benchmarkNotifyMotion(benchmark::State& state) { // Create dispatcher FakeInputDispatcherPolicy fakePolicy; - InputDispatcher dispatcher(fakePolicy); - dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); - dispatcher.start(); + auto dispatcher = std::make_unique<InputDispatcher>(fakePolicy); + dispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); + dispatcher->start(); // Create a window that will receive motion events std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window", DISPLAY_ID); - dispatcher.onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + dispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); NotifyMotionArgs motionArgs = generateMotionArgs(); @@ -122,60 +122,60 @@ static void benchmarkNotifyMotion(benchmark::State& state) { motionArgs.action = AMOTION_EVENT_ACTION_DOWN; motionArgs.downTime = now(); motionArgs.eventTime = motionArgs.downTime; - dispatcher.notifyMotion(motionArgs); + dispatcher->notifyMotion(motionArgs); // Send ACTION_UP motionArgs.action = AMOTION_EVENT_ACTION_UP; motionArgs.eventTime = now(); - dispatcher.notifyMotion(motionArgs); + dispatcher->notifyMotion(motionArgs); - window->consumeMotion(); - window->consumeMotion(); + window->consumeMotionEvent(); + window->consumeMotionEvent(); } - dispatcher.stop(); + dispatcher->stop(); } static void benchmarkInjectMotion(benchmark::State& state) { // Create dispatcher FakeInputDispatcherPolicy fakePolicy; - InputDispatcher dispatcher(fakePolicy); - dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); - dispatcher.start(); + auto dispatcher = std::make_unique<InputDispatcher>(fakePolicy); + dispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); + dispatcher->start(); // Create a window that will receive motion events std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window", DISPLAY_ID); - dispatcher.onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + dispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); for (auto _ : state) { MotionEvent event = generateMotionEvent(); // Send ACTION_DOWN - dispatcher.injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, - INJECT_EVENT_TIMEOUT, - POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER); + dispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, + INJECT_EVENT_TIMEOUT, + POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER); // Send ACTION_UP event.setAction(AMOTION_EVENT_ACTION_UP); - dispatcher.injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, - INJECT_EVENT_TIMEOUT, - POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER); + dispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, + INJECT_EVENT_TIMEOUT, + POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER); - window->consumeMotion(); - window->consumeMotion(); + window->consumeMotionEvent(); + window->consumeMotionEvent(); } - dispatcher.stop(); + dispatcher->stop(); } static void benchmarkOnWindowInfosChanged(benchmark::State& state) { // Create dispatcher FakeInputDispatcherPolicy fakePolicy; - InputDispatcher dispatcher(fakePolicy); - dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); - dispatcher.start(); + auto dispatcher = std::make_unique<InputDispatcher>(fakePolicy); + dispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); + dispatcher->start(); // Create a window std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); @@ -188,12 +188,12 @@ static void benchmarkOnWindowInfosChanged(benchmark::State& state) { std::vector<gui::DisplayInfo> displayInfos{info}; for (auto _ : state) { - dispatcher.onWindowInfosChanged( + dispatcher->onWindowInfosChanged( {windowInfos, displayInfos, /*vsyncId=*/0, /*timestamp=*/0}); - dispatcher.onWindowInfosChanged( + dispatcher->onWindowInfosChanged( {/*windowInfos=*/{}, /*displayInfos=*/{}, /*vsyncId=*/{}, /*timestamp=*/0}); } - dispatcher.stop(); + dispatcher->stop(); } } // namespace diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index e331e8d0a3..76c492eb43 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2532,11 +2532,19 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( if (!isHoverAction) { const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN || maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN; - tempTouchState.addOrUpdateWindow(windowHandle, InputTarget::DispatchMode::AS_IS, - targetFlags, entry.deviceId, {pointer}, - isDownOrPointerDown - ? std::make_optional(entry.eventTime) - : std::nullopt); + Result<void> addResult = + tempTouchState.addOrUpdateWindow(windowHandle, + InputTarget::DispatchMode::AS_IS, + targetFlags, entry.deviceId, {pointer}, + isDownOrPointerDown + ? std::make_optional( + entry.eventTime) + : std::nullopt); + if (!addResult.ok()) { + LOG(ERROR) << "Error while processing " << entry << " for " + << windowHandle->getName(); + logDispatchStateLocked(); + } // If this is the pointer going down and the touched window has a wallpaper // then also add the touched wallpaper windows so they are locked in for the // duration of the touch gesture. We do not collect wallpapers during HOVER_MOVE or @@ -3046,7 +3054,11 @@ void InputDispatcher::addPointerWindowTargetLocked( << ", windowInfo->globalScaleFactor=" << windowInfo->globalScaleFactor; } - it->addPointers(pointerIds, windowInfo->transform); + Result<void> result = it->addPointers(pointerIds, windowInfo->transform); + if (!result.ok()) { + logDispatchStateLocked(); + LOG(FATAL) << result.error().message(); + } } void InputDispatcher::addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets, @@ -4383,7 +4395,7 @@ std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent( // different pointer ids than we expected based on the previous ACTION_DOWN // or ACTION_POINTER_DOWN events that caused us to decide to split the pointers // in this way. - ALOGW("Dropping split motion event because the pointer count is %d but " + ALOGW("Dropping split motion event because the pointer count is %zu but " "we expected there to be %zu pointers. This probably means we received " "a broken sequence of pointer ids from the input device: %s", pointerCoords.size(), pointerIds.count(), @@ -4848,7 +4860,7 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev const bool isPointerEvent = isFromSource(event->getSource(), AINPUT_SOURCE_CLASS_POINTER); // If a pointer event has no displayId specified, inject it to the default display. - const uint32_t displayId = isPointerEvent && (event->getDisplayId() == ADISPLAY_ID_NONE) + const int32_t displayId = isPointerEvent && (event->getDisplayId() == ADISPLAY_ID_NONE) ? ADISPLAY_ID_DEFAULT : event->getDisplayId(); int32_t flags = motionEvent.getFlags(); diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp index 1fec9b7599..02bc3680bf 100644 --- a/services/inputflinger/dispatcher/InputState.cpp +++ b/services/inputflinger/dispatcher/InputState.cpp @@ -95,12 +95,14 @@ bool InputState::trackMotion(const MotionEntry& entry, int32_t flags) { return true; } - if (!mMotionMementos.empty()) { - const MotionMemento& lastMemento = mMotionMementos.back(); - if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) && - !isStylusEvent(entry.source, entry.pointerProperties)) { - // We already have a stylus stream, and the new event is not from stylus. - return false; + if (!input_flags::enable_multi_device_same_window_stream()) { + if (!mMotionMementos.empty()) { + const MotionMemento& lastMemento = mMotionMementos.back(); + if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) && + !isStylusEvent(entry.source, entry.pointerProperties)) { + // We already have a stylus stream, and the new event is not from stylus. + return false; + } } } @@ -336,33 +338,34 @@ bool InputState::shouldCancelPreviousStream(const MotionEntry& motionEntry) cons // it's unlikely that those two streams would be consistent with each other. Therefore, // cancel the previous gesture if the display id changes. if (motionEntry.displayId != lastMemento.displayId) { - LOG(INFO) << "Canceling stream: last displayId was " - << inputEventSourceToString(lastMemento.displayId) << " and new event is " - << motionEntry; + LOG(INFO) << "Canceling stream: last displayId was " << lastMemento.displayId + << " and new event is " << motionEntry; return true; } return false; } - if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties)) { - // A stylus is already active. - if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) && - actionMasked == AMOTION_EVENT_ACTION_DOWN) { - // If this new event is from a different device, then cancel the old - // stylus and allow the new stylus to take over, but only if it's going down. - // Otherwise, they will start to race each other. - return true; - } + if (!input_flags::enable_multi_device_same_window_stream()) { + if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties)) { + // A stylus is already active. + if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) && + actionMasked == AMOTION_EVENT_ACTION_DOWN) { + // If this new event is from a different device, then cancel the old + // stylus and allow the new stylus to take over, but only if it's going down. + // Otherwise, they will start to race each other. + return true; + } - // Keep the current stylus gesture. - return false; - } + // Keep the current stylus gesture. + return false; + } - // Cancel the current gesture if this is a start of a new gesture from a new device. - if (actionMasked == AMOTION_EVENT_ACTION_DOWN || - actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) { - return true; + // Cancel the current gesture if this is a start of a new gesture from a new device. + if (actionMasked == AMOTION_EVENT_ACTION_DOWN || + actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) { + return true; + } } // By default, don't cancel any events. return false; diff --git a/services/inputflinger/dispatcher/InputTarget.cpp b/services/inputflinger/dispatcher/InputTarget.cpp index 35ad858736..f9a2855974 100644 --- a/services/inputflinger/dispatcher/InputTarget.cpp +++ b/services/inputflinger/dispatcher/InputTarget.cpp @@ -22,6 +22,8 @@ #include <inttypes.h> #include <string> +using android::base::Error; +using android::base::Result; using android::base::StringPrintf; namespace android::inputdispatcher { @@ -35,28 +37,29 @@ const static ui::Transform kIdentityTransform{}; InputTarget::InputTarget(const std::shared_ptr<Connection>& connection, ftl::Flags<Flags> flags) : connection(connection), flags(flags) {} -void InputTarget::addPointers(std::bitset<MAX_POINTER_ID + 1> newPointerIds, - const ui::Transform& transform) { +Result<void> InputTarget::addPointers(std::bitset<MAX_POINTER_ID + 1> newPointerIds, + const ui::Transform& transform) { // The pointerIds can be empty, but still a valid InputTarget. This can happen when there is no // valid pointer property from the input event. if (newPointerIds.none()) { setDefaultPointerTransform(transform); - return; + return {}; } // Ensure that the new set of pointers doesn't overlap with the current set of pointers. if ((getPointerIds() & newPointerIds).any()) { - LOG(FATAL) << __func__ << " - overlap with incoming pointers " - << bitsetToString(newPointerIds) << " in " << *this; + return Error() << __func__ << " - overlap with incoming pointers " + << bitsetToString(newPointerIds) << " in " << *this; } for (auto& [existingTransform, existingPointers] : mPointerTransforms) { if (transform == existingTransform) { existingPointers |= newPointerIds; - return; + return {}; } } mPointerTransforms.emplace_back(transform, newPointerIds); + return {}; } void InputTarget::setDefaultPointerTransform(const ui::Transform& transform) { diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h index 058639a742..60a75ee107 100644 --- a/services/inputflinger/dispatcher/InputTarget.h +++ b/services/inputflinger/dispatcher/InputTarget.h @@ -92,7 +92,8 @@ public: InputTarget() = default; InputTarget(const std::shared_ptr<Connection>&, ftl::Flags<Flags> = {}); - void addPointers(std::bitset<MAX_POINTER_ID + 1> pointerIds, const ui::Transform& transform); + android::base::Result<void> addPointers(std::bitset<MAX_POINTER_ID + 1> pointerIds, + const ui::Transform& transform); void setDefaultPointerTransform(const ui::Transform& transform); /** diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index f8aa62500e..0caa5e1402 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -70,14 +70,14 @@ void TouchState::clearWindowsWithoutPointers() { }); } -void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle, - InputTarget::DispatchMode dispatchMode, - ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId, - const std::vector<PointerProperties>& touchingPointers, - std::optional<nsecs_t> firstDownTimeInTarget) { +android::base::Result<void> TouchState::addOrUpdateWindow( + const sp<WindowInfoHandle>& windowHandle, InputTarget::DispatchMode dispatchMode, + ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId, + const std::vector<PointerProperties>& touchingPointers, + std::optional<nsecs_t> firstDownTimeInTarget) { if (touchingPointers.empty()) { LOG(FATAL) << __func__ << "No pointers specified for " << windowHandle->getName(); - return; + return android::base::Error(); } for (TouchedWindow& touchedWindow : windows) { // We do not compare windows by token here because two windows that share the same token @@ -91,11 +91,12 @@ 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, touchingPointers); + android::base::Result<void> addResult = + touchedWindow.addTouchingPointers(deviceId, touchingPointers); if (firstDownTimeInTarget) { touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget); } - return; + return addResult; } } TouchedWindow touchedWindow; @@ -107,6 +108,7 @@ void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle, touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget); } windows.push_back(touchedWindow); + return {}; } void TouchState::addHoveringPointerToWindow(const sp<WindowInfoHandle>& windowHandle, diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index 3d534bc71d..559a3fd041 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -43,11 +43,11 @@ struct TouchState { void removeTouchingPointer(DeviceId deviceId, int32_t pointerId); void removeTouchingPointerFromWindow(DeviceId deviceId, int32_t pointerId, const sp<android::gui::WindowInfoHandle>& windowHandle); - void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle, - InputTarget::DispatchMode dispatchMode, - ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId, - const std::vector<PointerProperties>& touchingPointers, - std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt); + android::base::Result<void> addOrUpdateWindow( + const sp<android::gui::WindowInfoHandle>& windowHandle, + InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags, + DeviceId deviceId, const std::vector<PointerProperties>& touchingPointers, + std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt); void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle, DeviceId deviceId, const PointerProperties& pointer); void removeHoveringPointer(DeviceId deviceId, int32_t pointerId); diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp index 037d7c8e99..1f86f6635a 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.cpp +++ b/services/inputflinger/dispatcher/TouchedWindow.cpp @@ -20,6 +20,7 @@ #include <android-base/stringprintf.h> #include <input/PrintTools.h> +using android::base::Result; using android::base::StringPrintf; namespace android { @@ -89,8 +90,8 @@ void TouchedWindow::addHoveringPointer(DeviceId deviceId, const PointerPropertie hoveringPointers.push_back(pointer); } -void TouchedWindow::addTouchingPointers(DeviceId deviceId, - const std::vector<PointerProperties>& pointers) { +Result<void> TouchedWindow::addTouchingPointers(DeviceId deviceId, + const std::vector<PointerProperties>& pointers) { std::vector<PointerProperties>& touchingPointers = mDeviceStates[deviceId].touchingPointers; const size_t initialSize = touchingPointers.size(); for (const PointerProperties& pointer : pointers) { @@ -98,11 +99,14 @@ void TouchedWindow::addTouchingPointers(DeviceId deviceId, return properties.id == pointer.id; }); } - if (touchingPointers.size() != initialSize) { + const bool foundInconsistentState = touchingPointers.size() != initialSize; + touchingPointers.insert(touchingPointers.end(), pointers.begin(), pointers.end()); + if (foundInconsistentState) { LOG(ERROR) << __func__ << ": " << dumpVector(pointers, streamableToString) << ", device " << deviceId << " already in " << *this; + return android::base::Error(); } - touchingPointers.insert(touchingPointers.end(), pointers.begin(), pointers.end()); + return {}; } bool TouchedWindow::hasTouchingPointers() const { diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h index 0d1531f8ff..4f0ad1628a 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -46,7 +46,8 @@ struct TouchedWindow { bool hasTouchingPointers() const; bool hasTouchingPointers(DeviceId deviceId) const; std::vector<PointerProperties> getTouchingPointers(DeviceId deviceId) const; - void addTouchingPointers(DeviceId deviceId, const std::vector<PointerProperties>& pointers); + android::base::Result<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; diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp index 1d4d11c13a..4931a5f5dd 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp @@ -86,8 +86,10 @@ InputTargetInfo getTargetInfo(const InputTarget& target) { // This is a global monitor, assume its target is the system. return {.uid = gui::Uid{AID_SYSTEM}, .isSecureWindow = false}; } - return {target.windowHandle->getInfo()->ownerUid, - target.windowHandle->getInfo()->layoutParamsFlags.test(gui::WindowInfo::Flag::SECURE)}; + const auto& info = *target.windowHandle->getInfo(); + const bool isSensitiveTarget = + info.inputConfig.test(gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING); + return {target.windowHandle->getInfo()->ownerUid, isSensitiveTarget}; } } // namespace diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 7d27d4a9ce..8d915996f7 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -20,8 +20,24 @@ #include "TouchInputMapper.h" +#include <algorithm> +#include <cinttypes> +#include <cmath> +#include <cstddef> +#include <tuple> + +#include <math.h> + +#include <android-base/stringprintf.h> +#include <android/input.h> #include <ftl/enum.h> #include <input/PrintTools.h> +#include <input/PropertyMap.h> +#include <input/VirtualKeyMap.h> +#include <linux/input-event-codes.h> +#include <log/log_main.h> +#include <math/vec2.h> +#include <ui/FloatRect.h> #include "CursorButtonAccumulator.h" #include "CursorScrollAccumulator.h" @@ -147,20 +163,6 @@ void TouchInputMapper::populateDeviceInfo(InputDeviceInfo& info) { info.addMotionRange(mOrientedRanges.y); info.addMotionRange(mOrientedRanges.pressure); - if (mDeviceMode == DeviceMode::UNSCALED && mSource == AINPUT_SOURCE_TOUCHPAD) { - // Populate RELATIVE_X and RELATIVE_Y motion ranges for touchpad capture mode. - // - // RELATIVE_X and RELATIVE_Y motion ranges should be the largest possible relative - // motion, i.e. the hardware dimensions, as the finger could move completely across the - // touchpad in one sample cycle. - const InputDeviceInfo::MotionRange& x = mOrientedRanges.x; - const InputDeviceInfo::MotionRange& y = mOrientedRanges.y; - info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -x.max, x.max, x.flat, x.fuzz, - x.resolution); - info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -y.max, y.max, y.flat, y.fuzz, - y.resolution); - } - if (mOrientedRanges.size) { info.addMotionRange(*mOrientedRanges.size); } @@ -531,7 +533,7 @@ bool TouchInputMapper::hasExternalStylus() const { * 4. Otherwise, use a non-display viewport. */ std::optional<DisplayViewport> TouchInputMapper::findViewport() { - if (mParameters.hasAssociatedDisplay && mDeviceMode != DeviceMode::UNSCALED) { + if (mParameters.hasAssociatedDisplay) { if (getDeviceContext().getAssociatedViewport()) { return getDeviceContext().getAssociatedViewport(); } @@ -939,8 +941,10 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) mSource = AINPUT_SOURCE_TOUCH_NAVIGATION; mDeviceMode = DeviceMode::NAVIGATION; } else { - mSource = AINPUT_SOURCE_TOUCHPAD; - mDeviceMode = DeviceMode::UNSCALED; + ALOGW("Touch device '%s' has invalid parameters or configuration. The device will be " + "inoperable.", + getDeviceName().c_str()); + mDeviceMode = DeviceMode::DISABLED; } const std::optional<DisplayViewport> newViewportOpt = findViewport(); @@ -1884,8 +1888,7 @@ std::list<NotifyArgs> TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t } if (!mCurrentRawState.rawPointerData.hoveringIdBits.isEmpty() && - mCurrentRawState.rawPointerData.touchingIdBits.isEmpty() && - mDeviceMode != DeviceMode::UNSCALED) { + mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) { // We have hovering pointers, and there are no touching pointers. bool hoveringPointersInFrame = false; auto hoveringIds = mCurrentRawState.rawPointerData.hoveringIdBits; @@ -1912,7 +1915,7 @@ std::list<NotifyArgs> TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t // Skip checking whether the pointer is inside the physical frame if the device is in // unscaled or pointer mode. if (!isPointInsidePhysicalFrame(pointer.x, pointer.y) && - mDeviceMode != DeviceMode::UNSCALED && mDeviceMode != DeviceMode::POINTER) { + mDeviceMode != DeviceMode::POINTER) { // If exactly one pointer went down, check for virtual key hit. // Otherwise, we will drop the entire stroke. if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) { diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 4b39e4099c..8451675944 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -16,17 +16,38 @@ #pragma once +#include <array> +#include <climits> +#include <limits> +#include <list> +#include <memory> #include <optional> #include <string> +#include <utility> +#include <vector> #include <stdint.h> +#include <gui/constants.h> +#include <input/DisplayViewport.h> +#include <input/Input.h> +#include <input/InputDevice.h> +#include <input/VelocityControl.h> +#include <input/VelocityTracker.h> +#include <ui/Rect.h> #include <ui/Rotation.h> +#include <ui/Size.h> +#include <ui/Transform.h> +#include <utils/BitSet.h> +#include <utils/Timers.h> #include "CursorButtonAccumulator.h" #include "CursorScrollAccumulator.h" #include "EventHub.h" #include "InputMapper.h" #include "InputReaderBase.h" +#include "NotifyArgs.h" +#include "PointerControllerInterface.h" +#include "StylusState.h" #include "TouchButtonAccumulator.h" namespace android { @@ -195,7 +216,6 @@ protected: enum class DeviceMode { DISABLED, // input is disabled DIRECT, // direct mapping (touchscreen) - UNSCALED, // unscaled mapping (e.g. captured touchpad) NAVIGATION, // unscaled mapping with assist gesture (touch navigation) POINTER, // pointer mapping (e.g. uncaptured touchpad, drawing tablet) diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 721cdfdfb0..f558ba1196 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -185,6 +185,7 @@ private: static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atomTag, AStatsEventList* outEventList, void* cookie) { + ALOGI("Received pull request for touchpad usage atom"); LOG_ALWAYS_FATAL_IF(atomTag != android::util::TOUCHPAD_USAGE); MetricsAccumulator& accumulator = MetricsAccumulator::getInstance(); accumulator.produceAtomsAndReset(*outEventList); @@ -192,6 +193,7 @@ private: } void produceAtomsAndReset(AStatsEventList& outEventList) { + ALOGI("Acquiring lock for touchpad usage metrics..."); std::scoped_lock lock(mLock); produceAtomsLocked(outEventList); resetCountersLocked(); diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 09ae6dd28c..6ae9790aac 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -22,6 +22,15 @@ package { default_applicable_licenses: ["frameworks_native_license"], } +// Source files shared with InputDispatcher's benchmarks and fuzzers +filegroup { + name: "inputdispatcher_common_test_sources", + srcs: [ + "FakeInputDispatcherPolicy.cpp", + "FakeWindows.cpp", + ], +} + cc_test { name: "inputflinger_tests", host_supported: true, @@ -38,6 +47,7 @@ cc_test { "libinputflinger_defaults", ], srcs: [ + ":inputdispatcher_common_test_sources", "AnrTracker_test.cpp", "CapturedTouchpadEventConverter_test.cpp", "CursorInputMapper_test.cpp", diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp new file mode 100644 index 0000000000..e231bccb79 --- /dev/null +++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp @@ -0,0 +1,473 @@ +/* + * 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 "FakeInputDispatcherPolicy.h" + +#include <gtest/gtest.h> + +namespace android { + +// --- FakeInputDispatcherPolicy --- + +void FakeInputDispatcherPolicy::assertFilterInputEventWasCalled(const NotifyKeyArgs& args) { + assertFilterInputEventWasCalledInternal([&args](const InputEvent& event) { + ASSERT_EQ(event.getType(), InputEventType::KEY); + EXPECT_EQ(event.getDisplayId(), args.displayId); + + const auto& keyEvent = static_cast<const KeyEvent&>(event); + EXPECT_EQ(keyEvent.getEventTime(), args.eventTime); + EXPECT_EQ(keyEvent.getAction(), args.action); + }); +} + +void FakeInputDispatcherPolicy::assertFilterInputEventWasCalled(const NotifyMotionArgs& args, + vec2 point) { + assertFilterInputEventWasCalledInternal([&](const InputEvent& event) { + ASSERT_EQ(event.getType(), InputEventType::MOTION); + EXPECT_EQ(event.getDisplayId(), args.displayId); + + const auto& motionEvent = static_cast<const MotionEvent&>(event); + EXPECT_EQ(motionEvent.getEventTime(), args.eventTime); + EXPECT_EQ(motionEvent.getAction(), args.action); + EXPECT_NEAR(motionEvent.getX(0), point.x, MotionEvent::ROUNDING_PRECISION); + EXPECT_NEAR(motionEvent.getY(0), point.y, MotionEvent::ROUNDING_PRECISION); + EXPECT_NEAR(motionEvent.getRawX(0), point.x, MotionEvent::ROUNDING_PRECISION); + EXPECT_NEAR(motionEvent.getRawY(0), point.y, MotionEvent::ROUNDING_PRECISION); + }); +} + +void FakeInputDispatcherPolicy::assertFilterInputEventWasNotCalled() { + std::scoped_lock lock(mLock); + ASSERT_EQ(nullptr, mFilteredEvent); +} + +void FakeInputDispatcherPolicy::assertNotifyConfigurationChangedWasCalled(nsecs_t when) { + std::scoped_lock lock(mLock); + ASSERT_TRUE(mConfigurationChangedTime) << "Timed out waiting for configuration changed call"; + ASSERT_EQ(*mConfigurationChangedTime, when); + mConfigurationChangedTime = std::nullopt; +} + +void FakeInputDispatcherPolicy::assertNotifySwitchWasCalled(const NotifySwitchArgs& args) { + std::scoped_lock lock(mLock); + ASSERT_TRUE(mLastNotifySwitch); + // We do not check id because it is not exposed to the policy + EXPECT_EQ(args.eventTime, mLastNotifySwitch->eventTime); + EXPECT_EQ(args.policyFlags, mLastNotifySwitch->policyFlags); + EXPECT_EQ(args.switchValues, mLastNotifySwitch->switchValues); + EXPECT_EQ(args.switchMask, mLastNotifySwitch->switchMask); + mLastNotifySwitch = std::nullopt; +} + +void FakeInputDispatcherPolicy::assertOnPointerDownEquals(const sp<IBinder>& touchedToken) { + std::scoped_lock lock(mLock); + ASSERT_EQ(touchedToken, mOnPointerDownToken); + mOnPointerDownToken.clear(); +} + +void FakeInputDispatcherPolicy::assertOnPointerDownWasNotCalled() { + std::scoped_lock lock(mLock); + ASSERT_TRUE(mOnPointerDownToken == nullptr) + << "Expected onPointerDownOutsideFocus to not have been called"; +} + +void FakeInputDispatcherPolicy::assertNotifyNoFocusedWindowAnrWasCalled( + std::chrono::nanoseconds timeout, + const std::shared_ptr<InputApplicationHandle>& expectedApplication) { + std::unique_lock lock(mLock); + android::base::ScopedLockAssertion assumeLocked(mLock); + std::shared_ptr<InputApplicationHandle> application; + ASSERT_NO_FATAL_FAILURE( + application = getAnrTokenLockedInterruptible(timeout, mAnrApplications, lock)); + ASSERT_EQ(expectedApplication, application); +} + +void FakeInputDispatcherPolicy::assertNotifyWindowUnresponsiveWasCalled( + std::chrono::nanoseconds timeout, const sp<gui::WindowInfoHandle>& window) { + LOG_ALWAYS_FATAL_IF(window == nullptr, "window should not be null"); + assertNotifyWindowUnresponsiveWasCalled(timeout, window->getToken(), + window->getInfo()->ownerPid); +} + +void FakeInputDispatcherPolicy::assertNotifyWindowUnresponsiveWasCalled( + std::chrono::nanoseconds timeout, const sp<IBinder>& expectedToken, + std::optional<gui::Pid> expectedPid) { + std::unique_lock lock(mLock); + android::base::ScopedLockAssertion assumeLocked(mLock); + AnrResult result; + ASSERT_NO_FATAL_FAILURE(result = getAnrTokenLockedInterruptible(timeout, mAnrWindows, lock)); + ASSERT_EQ(expectedToken, result.token); + ASSERT_EQ(expectedPid, result.pid); +} + +sp<IBinder> FakeInputDispatcherPolicy::getUnresponsiveWindowToken( + std::chrono::nanoseconds timeout) { + std::unique_lock lock(mLock); + android::base::ScopedLockAssertion assumeLocked(mLock); + AnrResult result = getAnrTokenLockedInterruptible(timeout, mAnrWindows, lock); + const auto& [token, _] = result; + return token; +} + +void FakeInputDispatcherPolicy::assertNotifyWindowResponsiveWasCalled( + const sp<IBinder>& expectedToken, std::optional<gui::Pid> expectedPid) { + std::unique_lock lock(mLock); + android::base::ScopedLockAssertion assumeLocked(mLock); + AnrResult result; + ASSERT_NO_FATAL_FAILURE(result = getAnrTokenLockedInterruptible(0s, mResponsiveWindows, lock)); + ASSERT_EQ(expectedToken, result.token); + ASSERT_EQ(expectedPid, result.pid); +} + +sp<IBinder> FakeInputDispatcherPolicy::getResponsiveWindowToken() { + std::unique_lock lock(mLock); + android::base::ScopedLockAssertion assumeLocked(mLock); + AnrResult result = getAnrTokenLockedInterruptible(0s, mResponsiveWindows, lock); + const auto& [token, _] = result; + return token; +} + +void FakeInputDispatcherPolicy::assertNotifyAnrWasNotCalled() { + std::scoped_lock lock(mLock); + ASSERT_TRUE(mAnrApplications.empty()); + ASSERT_TRUE(mAnrWindows.empty()); + ASSERT_TRUE(mResponsiveWindows.empty()) + << "ANR was not called, but please also consume the 'connection is responsive' " + "signal"; +} + +PointerCaptureRequest FakeInputDispatcherPolicy::assertSetPointerCaptureCalled( + const sp<gui::WindowInfoHandle>& window, bool enabled) { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + + if (!mPointerCaptureChangedCondition + .wait_for(lock, 100ms, [this, enabled, window]() REQUIRES(mLock) { + if (enabled) { + return mPointerCaptureRequest->isEnable() && + mPointerCaptureRequest->window == window->getToken(); + } else { + return !mPointerCaptureRequest->isEnable(); + } + })) { + ADD_FAILURE() << "Timed out waiting for setPointerCapture(" << window->getName() << ", " + << enabled << ") to be called."; + return {}; + } + auto request = *mPointerCaptureRequest; + mPointerCaptureRequest.reset(); + return request; +} + +void FakeInputDispatcherPolicy::assertSetPointerCaptureNotCalled() { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + + if (mPointerCaptureChangedCondition.wait_for(lock, 100ms) != std::cv_status::timeout) { + FAIL() << "Expected setPointerCapture(request) to not be called, but was called. " + "enabled = " + << std::to_string(mPointerCaptureRequest->isEnable()); + } + mPointerCaptureRequest.reset(); +} + +void FakeInputDispatcherPolicy::assertDropTargetEquals(const InputDispatcherInterface& dispatcher, + const sp<IBinder>& targetToken) { + dispatcher.waitForIdle(); + std::scoped_lock lock(mLock); + ASSERT_TRUE(mNotifyDropWindowWasCalled); + ASSERT_EQ(targetToken, mDropTargetWindowToken); + mNotifyDropWindowWasCalled = false; +} + +void FakeInputDispatcherPolicy::assertNotifyInputChannelBrokenWasCalled(const sp<IBinder>& token) { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + std::optional<sp<IBinder>> receivedToken = + getItemFromStorageLockedInterruptible(100ms, mBrokenInputChannels, lock, + mNotifyInputChannelBroken); + ASSERT_TRUE(receivedToken.has_value()) << "Did not receive the broken channel token"; + ASSERT_EQ(token, *receivedToken); +} + +void FakeInputDispatcherPolicy::setInterceptKeyTimeout(std::chrono::milliseconds timeout) { + mInterceptKeyTimeout = timeout; +} + +std::chrono::nanoseconds FakeInputDispatcherPolicy::getKeyWaitingForEventsTimeout() { + return 500ms; +} + +void FakeInputDispatcherPolicy::setStaleEventTimeout(std::chrono::nanoseconds timeout) { + mStaleEventTimeout = timeout; +} + +void FakeInputDispatcherPolicy::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 FakeInputDispatcherPolicy::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 FakeInputDispatcherPolicy::assertNotifyDeviceInteractionWasCalled(int32_t deviceId, + std::set<gui::Uid> uids) { + ASSERT_EQ(std::make_pair(deviceId, uids), mNotifiedInteractions.popWithTimeout(100ms)); +} + +void FakeInputDispatcherPolicy::assertNotifyDeviceInteractionWasNotCalled() { + ASSERT_FALSE(mNotifiedInteractions.popWithTimeout(10ms)); +} + +void FakeInputDispatcherPolicy::setUnhandledKeyHandler( + std::function<std::optional<KeyEvent>(const KeyEvent&)> handler) { + std::scoped_lock lock(mLock); + mUnhandledKeyHandler = handler; +} + +void FakeInputDispatcherPolicy::assertUnhandledKeyReported(int32_t keycode) { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + std::optional<int32_t> unhandledKeycode = + getItemFromStorageLockedInterruptible(100ms, mReportedUnhandledKeycodes, lock, + mNotifyUnhandledKey); + ASSERT_TRUE(unhandledKeycode) << "Expected unhandled key to be reported"; + ASSERT_EQ(unhandledKeycode, keycode); +} + +void FakeInputDispatcherPolicy::assertUnhandledKeyNotReported() { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + std::optional<int32_t> unhandledKeycode = + getItemFromStorageLockedInterruptible(10ms, mReportedUnhandledKeycodes, lock, + mNotifyUnhandledKey); + ASSERT_FALSE(unhandledKeycode) << "Expected unhandled key NOT to be reported"; +} + +template <class T> +T FakeInputDispatcherPolicy::getAnrTokenLockedInterruptible(std::chrono::nanoseconds timeout, + std::queue<T>& storage, + std::unique_lock<std::mutex>& lock) + REQUIRES(mLock) { + // If there is an ANR, Dispatcher won't be idle because there are still events + // in the waitQueue that we need to check on. So we can't wait for dispatcher to be idle + // before checking if ANR was called. + // Since dispatcher is not guaranteed to call notifyNoFocusedWindowAnr right away, we need + // to provide it some time to act. 100ms seems reasonable. + std::chrono::duration timeToWait = timeout + 100ms; // provide some slack + const std::chrono::time_point start = std::chrono::steady_clock::now(); + std::optional<T> token = + getItemFromStorageLockedInterruptible(timeToWait, storage, lock, mNotifyAnr); + if (!token.has_value()) { + ADD_FAILURE() << "Did not receive the ANR callback"; + return {}; + } + + const std::chrono::duration waited = std::chrono::steady_clock::now() - start; + // Ensure that the ANR didn't get raised too early. We can't be too strict here because + // the dispatcher started counting before this function was called + if (std::chrono::abs(timeout - waited) > 100ms) { + ADD_FAILURE() << "ANR was raised too early or too late. Expected " + << std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count() + << "ms, but waited " + << std::chrono::duration_cast<std::chrono::milliseconds>(waited).count() + << "ms instead"; + } + return *token; +} + +template <class T> +std::optional<T> FakeInputDispatcherPolicy::getItemFromStorageLockedInterruptible( + std::chrono::nanoseconds timeout, std::queue<T>& storage, + std::unique_lock<std::mutex>& lock, std::condition_variable& condition) REQUIRES(mLock) { + condition.wait_for(lock, timeout, [&storage]() REQUIRES(mLock) { return !storage.empty(); }); + if (storage.empty()) { + return std::nullopt; + } + T item = storage.front(); + storage.pop(); + return std::make_optional(item); +} + +void FakeInputDispatcherPolicy::notifyConfigurationChanged(nsecs_t when) { + std::scoped_lock lock(mLock); + mConfigurationChangedTime = when; +} + +void FakeInputDispatcherPolicy::notifyWindowUnresponsive(const sp<IBinder>& connectionToken, + std::optional<gui::Pid> pid, + const std::string&) { + std::scoped_lock lock(mLock); + mAnrWindows.push({connectionToken, pid}); + mNotifyAnr.notify_all(); +} + +void FakeInputDispatcherPolicy::notifyWindowResponsive(const sp<IBinder>& connectionToken, + std::optional<gui::Pid> pid) { + std::scoped_lock lock(mLock); + mResponsiveWindows.push({connectionToken, pid}); + mNotifyAnr.notify_all(); +} + +void FakeInputDispatcherPolicy::notifyNoFocusedWindowAnr( + const std::shared_ptr<InputApplicationHandle>& applicationHandle) { + std::scoped_lock lock(mLock); + mAnrApplications.push(applicationHandle); + mNotifyAnr.notify_all(); +} + +void FakeInputDispatcherPolicy::notifyInputChannelBroken(const sp<IBinder>& connectionToken) { + std::scoped_lock lock(mLock); + mBrokenInputChannels.push(connectionToken); + mNotifyInputChannelBroken.notify_all(); +} + +void FakeInputDispatcherPolicy::notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) {} + +void FakeInputDispatcherPolicy::notifySensorEvent(int32_t deviceId, + InputDeviceSensorType sensorType, + InputDeviceSensorAccuracy accuracy, + nsecs_t timestamp, + const std::vector<float>& values) {} + +void FakeInputDispatcherPolicy::notifySensorAccuracy(int deviceId, InputDeviceSensorType sensorType, + InputDeviceSensorAccuracy accuracy) {} + +void FakeInputDispatcherPolicy::notifyVibratorState(int32_t deviceId, bool isOn) {} + +bool FakeInputDispatcherPolicy::filterInputEvent(const InputEvent& inputEvent, + uint32_t policyFlags) { + std::scoped_lock lock(mLock); + switch (inputEvent.getType()) { + case InputEventType::KEY: { + const KeyEvent& keyEvent = static_cast<const KeyEvent&>(inputEvent); + mFilteredEvent = std::make_unique<KeyEvent>(keyEvent); + break; + } + + case InputEventType::MOTION: { + const MotionEvent& motionEvent = static_cast<const MotionEvent&>(inputEvent); + mFilteredEvent = std::make_unique<MotionEvent>(motionEvent); + break; + } + default: { + ADD_FAILURE() << "Should only filter keys or motions"; + break; + } + } + return true; +} + +void FakeInputDispatcherPolicy::interceptKeyBeforeQueueing(const KeyEvent& inputEvent, uint32_t&) { + if (inputEvent.getAction() == AKEY_EVENT_ACTION_UP) { + // Clear intercept state when we handled the event. + mInterceptKeyTimeout = 0ms; + } +} + +void FakeInputDispatcherPolicy::interceptMotionBeforeQueueing(int32_t, uint32_t, int32_t, nsecs_t, + uint32_t&) {} + +nsecs_t FakeInputDispatcherPolicy::interceptKeyBeforeDispatching(const sp<IBinder>&, + const KeyEvent&, uint32_t) { + nsecs_t delay = std::chrono::nanoseconds(mInterceptKeyTimeout).count(); + // Clear intercept state so we could dispatch the event in next wake. + mInterceptKeyTimeout = 0ms; + return delay; +} + +std::optional<KeyEvent> FakeInputDispatcherPolicy::dispatchUnhandledKey(const sp<IBinder>&, + const KeyEvent& event, + uint32_t) { + std::scoped_lock lock(mLock); + mReportedUnhandledKeycodes.emplace(event.getKeyCode()); + mNotifyUnhandledKey.notify_all(); + return mUnhandledKeyHandler != nullptr ? mUnhandledKeyHandler(event) : std::nullopt; +} + +void FakeInputDispatcherPolicy::notifySwitch(nsecs_t when, uint32_t switchValues, + uint32_t switchMask, uint32_t policyFlags) { + std::scoped_lock lock(mLock); + // We simply reconstruct NotifySwitchArgs in policy because InputDispatcher is + // essentially a passthrough for notifySwitch. + mLastNotifySwitch = + NotifySwitchArgs(InputEvent::nextId(), when, policyFlags, switchValues, switchMask); +} + +void FakeInputDispatcherPolicy::pokeUserActivity(nsecs_t eventTime, int32_t eventType, + int32_t displayId) { + std::scoped_lock lock(mLock); + mNotifyUserActivity.notify_all(); + mUserActivityPokeEvents.push({eventTime, eventType, displayId}); +} + +bool FakeInputDispatcherPolicy::isStaleEvent(nsecs_t currentTime, nsecs_t eventTime) { + return std::chrono::nanoseconds(currentTime - eventTime) >= mStaleEventTimeout; +} + +void FakeInputDispatcherPolicy::onPointerDownOutsideFocus(const sp<IBinder>& newToken) { + std::scoped_lock lock(mLock); + mOnPointerDownToken = newToken; +} + +void FakeInputDispatcherPolicy::setPointerCapture(const PointerCaptureRequest& request) { + std::scoped_lock lock(mLock); + mPointerCaptureRequest = {request}; + mPointerCaptureChangedCondition.notify_all(); +} + +void FakeInputDispatcherPolicy::notifyDropWindow(const sp<IBinder>& token, float x, float y) { + std::scoped_lock lock(mLock); + mNotifyDropWindowWasCalled = true; + mDropTargetWindowToken = token; +} + +void FakeInputDispatcherPolicy::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp, + const std::set<gui::Uid>& uids) { + ASSERT_TRUE(mNotifiedInteractions.emplace(deviceId, uids)); +} + +void FakeInputDispatcherPolicy::assertFilterInputEventWasCalledInternal( + const std::function<void(const InputEvent&)>& verify) { + std::scoped_lock lock(mLock); + ASSERT_NE(nullptr, mFilteredEvent) << "Expected filterInputEvent() to have been called."; + verify(*mFilteredEvent); + mFilteredEvent = nullptr; +} + +gui::Uid FakeInputDispatcherPolicy::getPackageUid(std::string) { + return gui::Uid::INVALID; +} + +} // namespace android diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h index e0a73247eb..d83924f202 100644 --- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h +++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h @@ -16,78 +16,190 @@ #pragma once -#include <android-base/logging.h> #include "InputDispatcherPolicyInterface.h" -namespace android { +#include "InputDispatcherInterface.h" +#include "NotifyArgs.h" + +#include <condition_variable> +#include <functional> +#include <memory> +#include <mutex> +#include <optional> +#include <queue> +#include <string> +#include <vector> + +#include <android-base/logging.h> +#include <android-base/thread_annotations.h> +#include <binder/IBinder.h> +#include <gui/PidUid.h> +#include <gui/WindowInfo.h> +#include <input/BlockingQueue.h> +#include <input/Input.h> -// --- FakeInputDispatcherPolicy --- +namespace android { class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface { public: FakeInputDispatcherPolicy() = default; virtual ~FakeInputDispatcherPolicy() = default; -private: - void notifyConfigurationChanged(nsecs_t) override {} - - void notifyNoFocusedWindowAnr( - const std::shared_ptr<InputApplicationHandle>& applicationHandle) override { - LOG(ERROR) << "There is no focused window for " << applicationHandle->getName(); - } + struct AnrResult { + sp<IBinder> token{}; + std::optional<gui::Pid> pid{}; + }; + + struct UserActivityPokeEvent { + nsecs_t eventTime; + int32_t eventType; + int32_t displayId; + + bool operator==(const UserActivityPokeEvent& rhs) const = default; + inline friend std::ostream& operator<<(std::ostream& os, const UserActivityPokeEvent& ev) { + os << "UserActivityPokeEvent[time=" << ev.eventTime << ", eventType=" << ev.eventType + << ", displayId=" << ev.displayId << "]"; + return os; + } + }; + + void assertFilterInputEventWasCalled(const NotifyKeyArgs& args); + void assertFilterInputEventWasCalled(const NotifyMotionArgs& args, vec2 point); + void assertFilterInputEventWasNotCalled(); + void assertNotifyConfigurationChangedWasCalled(nsecs_t when); + void assertNotifySwitchWasCalled(const NotifySwitchArgs& args); + void assertOnPointerDownEquals(const sp<IBinder>& touchedToken); + void assertOnPointerDownWasNotCalled(); + /** + * This function must be called soon after the expected ANR timer starts, + * because we are also checking how much time has passed. + */ + void assertNotifyNoFocusedWindowAnrWasCalled( + std::chrono::nanoseconds timeout, + const std::shared_ptr<InputApplicationHandle>& expectedApplication); + void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout, + const sp<gui::WindowInfoHandle>& window); + void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout, + const sp<IBinder>& expectedToken, + std::optional<gui::Pid> expectedPid); + /** Wrap call with ASSERT_NO_FATAL_FAILURE() to ensure the return value is valid. */ + sp<IBinder> getUnresponsiveWindowToken(std::chrono::nanoseconds timeout); + void assertNotifyWindowResponsiveWasCalled(const sp<IBinder>& expectedToken, + std::optional<gui::Pid> expectedPid); + /** Wrap call with ASSERT_NO_FATAL_FAILURE() to ensure the return value is valid. */ + sp<IBinder> getResponsiveWindowToken(); + void assertNotifyAnrWasNotCalled(); + PointerCaptureRequest assertSetPointerCaptureCalled(const sp<gui::WindowInfoHandle>& window, + bool enabled); + void assertSetPointerCaptureNotCalled(); + void assertDropTargetEquals(const InputDispatcherInterface& dispatcher, + const sp<IBinder>& targetToken); + void assertNotifyInputChannelBrokenWasCalled(const sp<IBinder>& token); + /** + * Set policy timeout. A value of zero means next key will not be intercepted. + */ + void setInterceptKeyTimeout(std::chrono::milliseconds timeout); + std::chrono::nanoseconds getKeyWaitingForEventsTimeout() override; + void setStaleEventTimeout(std::chrono::nanoseconds timeout); + void assertUserActivityNotPoked(); + /** + * 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 = {}); + void assertNotifyDeviceInteractionWasCalled(int32_t deviceId, std::set<gui::Uid> uids); + void assertNotifyDeviceInteractionWasNotCalled(); + void setUnhandledKeyHandler(std::function<std::optional<KeyEvent>(const KeyEvent&)> handler); + void assertUnhandledKeyReported(int32_t keycode); + void assertUnhandledKeyNotReported(); +private: + std::mutex mLock; + std::unique_ptr<InputEvent> mFilteredEvent GUARDED_BY(mLock); + std::optional<nsecs_t> mConfigurationChangedTime GUARDED_BY(mLock); + sp<IBinder> mOnPointerDownToken GUARDED_BY(mLock); + std::optional<NotifySwitchArgs> mLastNotifySwitch GUARDED_BY(mLock); + + std::condition_variable mPointerCaptureChangedCondition; + + std::optional<PointerCaptureRequest> mPointerCaptureRequest GUARDED_BY(mLock); + // ANR handling + std::queue<std::shared_ptr<InputApplicationHandle>> mAnrApplications GUARDED_BY(mLock); + std::queue<AnrResult> mAnrWindows GUARDED_BY(mLock); + std::queue<AnrResult> mResponsiveWindows GUARDED_BY(mLock); + std::condition_variable mNotifyAnr; + std::queue<sp<IBinder>> mBrokenInputChannels GUARDED_BY(mLock); + std::condition_variable mNotifyInputChannelBroken; + + sp<IBinder> mDropTargetWindowToken GUARDED_BY(mLock); + bool mNotifyDropWindowWasCalled GUARDED_BY(mLock) = false; + + std::condition_variable mNotifyUserActivity; + std::queue<UserActivityPokeEvent> mUserActivityPokeEvents; + + std::chrono::milliseconds mInterceptKeyTimeout = 0ms; + + std::chrono::nanoseconds mStaleEventTimeout = 1000ms; + + BlockingQueue<std::pair<int32_t /*deviceId*/, std::set<gui::Uid>>> mNotifiedInteractions; + + std::condition_variable mNotifyUnhandledKey; + std::queue<int32_t> mReportedUnhandledKeycodes GUARDED_BY(mLock); + std::function<std::optional<KeyEvent>(const KeyEvent&)> mUnhandledKeyHandler GUARDED_BY(mLock); + + /** + * All three ANR-related callbacks behave the same way, so we use this generic function to wait + * for a specific container to become non-empty. When the container is non-empty, return the + * first entry from the container and erase it. + */ + template <class T> + T getAnrTokenLockedInterruptible(std::chrono::nanoseconds timeout, std::queue<T>& storage, + std::unique_lock<std::mutex>& lock) REQUIRES(mLock); + + template <class T> + std::optional<T> getItemFromStorageLockedInterruptible(std::chrono::nanoseconds timeout, + std::queue<T>& storage, + std::unique_lock<std::mutex>& lock, + std::condition_variable& condition) + REQUIRES(mLock); + + void notifyConfigurationChanged(nsecs_t when) override; void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<gui::Pid> pid, - const std::string& reason) override { - LOG(ERROR) << "Window is not responding: " << reason; - } - + const std::string&) override; void notifyWindowResponsive(const sp<IBinder>& connectionToken, - std::optional<gui::Pid> pid) override {} - - void notifyInputChannelBroken(const sp<IBinder>&) override {} - - void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override {} - + std::optional<gui::Pid> pid) override; + void notifyNoFocusedWindowAnr( + const std::shared_ptr<InputApplicationHandle>& applicationHandle) override; + void notifyInputChannelBroken(const sp<IBinder>& connectionToken) override; + void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override; void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType, InputDeviceSensorAccuracy accuracy, nsecs_t timestamp, - const std::vector<float>& values) override {} - - void notifySensorAccuracy(int32_t deviceId, InputDeviceSensorType sensorType, - InputDeviceSensorAccuracy accuracy) override {} - - void notifyVibratorState(int32_t deviceId, bool isOn) override {} - - bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override { - return true; // dispatch event normally - } - - void interceptKeyBeforeQueueing(const KeyEvent&, 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; - } - - std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent&, - uint32_t) override { - return {}; - } - - void notifySwitch(nsecs_t, uint32_t, uint32_t, uint32_t) override {} - - void pokeUserActivity(nsecs_t, int32_t, int32_t) override {} - - void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {} - - void setPointerCapture(const PointerCaptureRequest&) override {} - - void notifyDropWindow(const sp<IBinder>&, float x, float y) override {} - - void notifyDeviceInteraction(DeviceId deviceId, nsecs_t timestamp, - const std::set<gui::Uid>& uids) override {} - - gui::Uid getPackageUid(std::string) override { return gui::Uid::INVALID; } + const std::vector<float>& values) override; + void notifySensorAccuracy(int deviceId, InputDeviceSensorType sensorType, + InputDeviceSensorAccuracy accuracy) override; + void notifyVibratorState(int32_t deviceId, bool isOn) override; + bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override; + void interceptKeyBeforeQueueing(const KeyEvent& inputEvent, 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; + std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent& event, + uint32_t) override; + void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask, + uint32_t policyFlags) override; + void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override; + bool isStaleEvent(nsecs_t currentTime, nsecs_t eventTime) override; + void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override; + void setPointerCapture(const PointerCaptureRequest& request) override; + void notifyDropWindow(const sp<IBinder>& token, float x, float y) override; + void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp, + const std::set<gui::Uid>& uids) override; + gui::Uid getPackageUid(std::string) override; + + void assertFilterInputEventWasCalledInternal( + const std::function<void(const InputEvent&)>& verify); }; } // namespace android diff --git a/services/inputflinger/tests/FakeWindowHandle.h b/services/inputflinger/tests/FakeWindowHandle.h deleted file mode 100644 index 8ce61e7800..0000000000 --- a/services/inputflinger/tests/FakeWindowHandle.h +++ /dev/null @@ -1,275 +0,0 @@ -/* - * 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 <android-base/logging.h> -#include <input/InputConsumer.h> -#include "../dispatcher/InputDispatcher.h" - -using android::base::Result; -using android::gui::Pid; -using android::gui::TouchOcclusionMode; -using android::gui::Uid; -using android::gui::WindowInfo; -using android::gui::WindowInfoHandle; - -namespace android { -namespace inputdispatcher { - -namespace { - -// The default pid and uid for windows created by the test. -constexpr gui::Pid WINDOW_PID{999}; -constexpr gui::Uid WINDOW_UID{1001}; - -static constexpr std::chrono::nanoseconds DISPATCHING_TIMEOUT = 100ms; - -} // namespace - -class FakeInputReceiver { -public: - std::unique_ptr<InputEvent> consumeEvent(std::chrono::milliseconds timeout) { - uint32_t consumeSeq = 0; - std::unique_ptr<InputEvent> event; - - std::chrono::time_point start = std::chrono::steady_clock::now(); - status_t result = WOULD_BLOCK; - while (result == WOULD_BLOCK) { - InputEvent* rawEventPtr = nullptr; - result = mConsumer.consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, - &rawEventPtr); - event = std::unique_ptr<InputEvent>(rawEventPtr); - std::chrono::duration elapsed = std::chrono::steady_clock::now() - start; - if (elapsed > timeout) { - if (timeout != 0ms) { - LOG(ERROR) << "Waited too long for consumer to produce an event, giving up"; - } - break; - } - } - // Events produced by this factory are owned pointers. - if (result != OK) { - if (timeout == 0ms) { - // This is likely expected. No need to log. - } else { - LOG(ERROR) << "Received result = " << result << " from consume"; - } - return nullptr; - } - result = mConsumer.sendFinishedSignal(consumeSeq, true); - if (result != OK) { - LOG(ERROR) << "Received result = " << result << " from sendFinishedSignal"; - } - return event; - } - - explicit FakeInputReceiver(std::unique_ptr<InputChannel> channel, const std::string name) - : mConsumer(std::move(channel)) {} - - virtual ~FakeInputReceiver() {} - -private: - std::unique_ptr<InputChannel> mClientChannel; - InputConsumer mConsumer; - DynamicInputEventFactory mEventFactory; -}; - -class FakeWindowHandle : public WindowInfoHandle { -public: - static const int32_t WIDTH = 600; - static const int32_t HEIGHT = 800; - - FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle, - InputDispatcher& dispatcher, const std::string name, int32_t displayId) - : mName(name) { - Result<std::unique_ptr<InputChannel>> channel = dispatcher.createInputChannel(name); - mInfo.token = (*channel)->getConnectionToken(); - mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name); - - inputApplicationHandle->updateInfo(); - mInfo.applicationInfo = *inputApplicationHandle->getInfo(); - - mInfo.id = sId++; - mInfo.name = name; - mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT; - mInfo.alpha = 1.0; - mInfo.frame.left = 0; - mInfo.frame.top = 0; - mInfo.frame.right = WIDTH; - mInfo.frame.bottom = HEIGHT; - mInfo.transform.set(0, 0); - mInfo.globalScaleFactor = 1.0; - mInfo.touchableRegion.clear(); - mInfo.addTouchableRegion(Rect(0, 0, WIDTH, HEIGHT)); - mInfo.ownerPid = WINDOW_PID; - mInfo.ownerUid = WINDOW_UID; - mInfo.displayId = displayId; - mInfo.inputConfig = WindowInfo::InputConfig::DEFAULT; - } - - sp<FakeWindowHandle> clone(int32_t displayId) { - sp<FakeWindowHandle> handle = sp<FakeWindowHandle>::make(mInfo.name + "(Mirror)"); - handle->mInfo = mInfo; - handle->mInfo.displayId = displayId; - handle->mInfo.id = sId++; - handle->mInputReceiver = mInputReceiver; - return handle; - } - - void setTouchable(bool touchable) { - mInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, !touchable); - } - - void setFocusable(bool focusable) { - mInfo.setInputConfig(WindowInfo::InputConfig::NOT_FOCUSABLE, !focusable); - } - - void setVisible(bool visible) { - mInfo.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !visible); - } - - void setDispatchingTimeout(std::chrono::nanoseconds timeout) { - mInfo.dispatchingTimeout = timeout; - } - - void setPaused(bool paused) { - mInfo.setInputConfig(WindowInfo::InputConfig::PAUSE_DISPATCHING, paused); - } - - void setPreventSplitting(bool preventSplitting) { - mInfo.setInputConfig(WindowInfo::InputConfig::PREVENT_SPLITTING, preventSplitting); - } - - void setSlippery(bool slippery) { - mInfo.setInputConfig(WindowInfo::InputConfig::SLIPPERY, slippery); - } - - void setWatchOutsideTouch(bool watchOutside) { - mInfo.setInputConfig(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH, watchOutside); - } - - void setSpy(bool spy) { mInfo.setInputConfig(WindowInfo::InputConfig::SPY, spy); } - - void setInterceptsStylus(bool interceptsStylus) { - mInfo.setInputConfig(WindowInfo::InputConfig::INTERCEPTS_STYLUS, interceptsStylus); - } - - void setDropInput(bool dropInput) { - mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT, dropInput); - } - - void setDropInputIfObscured(bool dropInputIfObscured) { - mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED, dropInputIfObscured); - } - - void setNoInputChannel(bool noInputChannel) { - mInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, noInputChannel); - } - - void setDisableUserActivity(bool disableUserActivity) { - mInfo.setInputConfig(WindowInfo::InputConfig::DISABLE_USER_ACTIVITY, disableUserActivity); - } - - void setAlpha(float alpha) { mInfo.alpha = alpha; } - - void setTouchOcclusionMode(TouchOcclusionMode mode) { mInfo.touchOcclusionMode = mode; } - - void setApplicationToken(sp<IBinder> token) { mInfo.applicationInfo.token = token; } - - void setFrame(const Rect& frame, const ui::Transform& displayTransform = ui::Transform()) { - mInfo.frame.left = frame.left; - mInfo.frame.top = frame.top; - mInfo.frame.right = frame.right; - mInfo.frame.bottom = frame.bottom; - mInfo.touchableRegion.clear(); - mInfo.addTouchableRegion(frame); - - const Rect logicalDisplayFrame = displayTransform.transform(frame); - ui::Transform translate; - translate.set(-logicalDisplayFrame.left, -logicalDisplayFrame.top); - mInfo.transform = translate * displayTransform; - } - - void setTouchableRegion(const Region& region) { mInfo.touchableRegion = region; } - - void setIsWallpaper(bool isWallpaper) { - mInfo.setInputConfig(WindowInfo::InputConfig::IS_WALLPAPER, isWallpaper); - } - - void setDupTouchToWallpaper(bool hasWallpaper) { - mInfo.setInputConfig(WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER, hasWallpaper); - } - - void setTrustedOverlay(bool trustedOverlay) { - mInfo.setInputConfig(WindowInfo::InputConfig::TRUSTED_OVERLAY, trustedOverlay); - } - - void setWindowTransform(float dsdx, float dtdx, float dtdy, float dsdy) { - mInfo.transform.set(dsdx, dtdx, dtdy, dsdy); - } - - void setWindowScale(float xScale, float yScale) { setWindowTransform(xScale, 0, 0, yScale); } - - void setWindowOffset(float offsetX, float offsetY) { mInfo.transform.set(offsetX, offsetY); } - - std::unique_ptr<InputEvent> consume(std::chrono::milliseconds timeout) { - if (mInputReceiver == nullptr) { - return nullptr; - } - return mInputReceiver->consumeEvent(timeout); - } - - void consumeMotion() { - std::unique_ptr<InputEvent> event = consume(100ms); - - if (event == nullptr) { - LOG(FATAL) << mName << ": expected a MotionEvent, but didn't get one."; - return; - } - - if (event->getType() != InputEventType::MOTION) { - LOG(FATAL) << mName << " expected a MotionEvent, got " << *event; - return; - } - } - - sp<IBinder> getToken() { return mInfo.token; } - - const std::string& getName() { return mName; } - - void setOwnerInfo(Pid ownerPid, Uid ownerUid) { - mInfo.ownerPid = ownerPid; - mInfo.ownerUid = ownerUid; - } - - Pid getPid() const { return mInfo.ownerPid; } - - void destroyReceiver() { mInputReceiver = nullptr; } - -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>; -}; - -std::atomic<int32_t> FakeWindowHandle::sId{1}; - -} // namespace inputdispatcher - -} // namespace android diff --git a/services/inputflinger/tests/FakeWindows.cpp b/services/inputflinger/tests/FakeWindows.cpp new file mode 100644 index 0000000000..a6955eca94 --- /dev/null +++ b/services/inputflinger/tests/FakeWindows.cpp @@ -0,0 +1,359 @@ +/* + * 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 "FakeWindows.h" + +#include <gtest/gtest.h> + +namespace android { + +// --- FakeInputReceiver --- + +FakeInputReceiver::FakeInputReceiver(std::unique_ptr<InputChannel> clientChannel, + const std::string name) + : mConsumer(std::move(clientChannel)), mName(name) {} + +std::unique_ptr<InputEvent> FakeInputReceiver::consume(std::chrono::milliseconds timeout, + bool handled) { + auto [consumeSeq, event] = receiveEvent(timeout); + if (!consumeSeq) { + return nullptr; + } + finishEvent(*consumeSeq, handled); + return std::move(event); +} + +std::pair<std::optional<uint32_t>, std::unique_ptr<InputEvent>> FakeInputReceiver::receiveEvent( + std::chrono::milliseconds timeout) { + uint32_t consumeSeq; + 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, + &rawEventPtr); + event = std::unique_ptr<InputEvent>(rawEventPtr); + std::chrono::duration elapsed = std::chrono::steady_clock::now() - start; + if (elapsed > timeout) { + break; + } + } + + if (status == WOULD_BLOCK) { + // Just means there's no event available. + return std::make_pair(std::nullopt, nullptr); + } + + if (status != OK) { + ADD_FAILURE() << mName.c_str() << ": consumer consume should return OK."; + return std::make_pair(std::nullopt, nullptr); + } + if (event == nullptr) { + ADD_FAILURE() << "Consumed correctly, but received NULL event from consumer"; + } + return std::make_pair(consumeSeq, std::move(event)); +} + +void FakeInputReceiver::finishEvent(uint32_t consumeSeq, bool handled) { + const status_t status = mConsumer.sendFinishedSignal(consumeSeq, handled); + ASSERT_EQ(OK, status) << mName.c_str() << ": consumer sendFinishedSignal should return OK."; +} + +void FakeInputReceiver::sendTimeline(int32_t inputEventId, + std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) { + const status_t status = mConsumer.sendTimeline(inputEventId, timeline); + ASSERT_EQ(OK, status); +} + +void FakeInputReceiver::consumeEvent(InputEventType expectedEventType, int32_t expectedAction, + std::optional<int32_t> expectedDisplayId, + std::optional<int32_t> expectedFlags) { + 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(expectedEventType, event->getType()) + << mName.c_str() << " expected " << ftl::enum_string(expectedEventType) + << " event, got " << *event; + + if (expectedDisplayId.has_value()) { + EXPECT_EQ(expectedDisplayId, event->getDisplayId()); + } + + switch (expectedEventType) { + case InputEventType::KEY: { + const KeyEvent& keyEvent = static_cast<const KeyEvent&>(*event); + ASSERT_THAT(keyEvent, WithKeyAction(expectedAction)); + if (expectedFlags.has_value()) { + EXPECT_EQ(expectedFlags.value(), keyEvent.getFlags()); + } + break; + } + case InputEventType::MOTION: { + const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event); + ASSERT_THAT(motionEvent, WithMotionAction(expectedAction)); + if (expectedFlags.has_value()) { + EXPECT_EQ(expectedFlags.value(), motionEvent.getFlags()); + } + break; + } + case InputEventType::FOCUS: { + FAIL() << "Use 'consumeFocusEvent' for FOCUS events"; + } + case InputEventType::CAPTURE: { + FAIL() << "Use 'consumeCaptureEvent' for CAPTURE events"; + } + case InputEventType::TOUCH_MODE: { + FAIL() << "Use 'consumeTouchModeEvent' for TOUCH_MODE events"; + } + case InputEventType::DRAG: { + FAIL() << "Use 'consumeDragEvent' for DRAG events"; + } + } +} + +std::unique_ptr<MotionEvent> FakeInputReceiver::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."; + return nullptr; + } + + if (event->getType() != InputEventType::MOTION) { + ADD_FAILURE() << mName << " expected a MotionEvent, got " << *event; + return nullptr; + } + return std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event.release())); +} + +void FakeInputReceiver::consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) { + std::unique_ptr<MotionEvent> motionEvent = consumeMotion(); + ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event, but expected " << matcher; + ASSERT_THAT(*motionEvent, matcher); +} + +void FakeInputReceiver::consumeFocusEvent(bool hasFocus, bool inTouchMode) { + 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()) << "Instead of FocusEvent, got " << *event; + + 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()); +} + +void FakeInputReceiver::consumeCaptureEvent(bool hasCapture) { + 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()) + << "Instead of CaptureEvent, got " << *event; + + ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) + << mName.c_str() << ": event displayId should always be NONE."; + + const auto& captureEvent = static_cast<const CaptureEvent&>(*event); + EXPECT_EQ(hasCapture, captureEvent.getPointerCaptureEnabled()); +} + +void FakeInputReceiver::consumeDragEvent(bool isExiting, float x, float y) { + 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; + + EXPECT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) + << mName.c_str() << ": event displayId should always be NONE."; + + const auto& dragEvent = static_cast<const DragEvent&>(*event); + EXPECT_EQ(isExiting, dragEvent.isExiting()); + EXPECT_EQ(x, dragEvent.getX()); + EXPECT_EQ(y, dragEvent.getY()); +} + +void FakeInputReceiver::consumeTouchModeEvent(bool inTouchMode) { + 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()) + << "Instead of TouchModeEvent, got " << *event; + + ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) + << mName.c_str() << ": event displayId should always be NONE."; + const auto& touchModeEvent = static_cast<const TouchModeEvent&>(*event); + EXPECT_EQ(inTouchMode, touchModeEvent.isInTouchMode()); +} + +void FakeInputReceiver::assertNoEvents(std::chrono::milliseconds timeout) { + std::unique_ptr<InputEvent> event = consume(timeout); + if (event == nullptr) { + return; + } + if (event->getType() == InputEventType::KEY) { + KeyEvent& keyEvent = static_cast<KeyEvent&>(*event); + ADD_FAILURE() << "Received key event " << keyEvent; + } else if (event->getType() == InputEventType::MOTION) { + MotionEvent& motionEvent = static_cast<MotionEvent&>(*event); + ADD_FAILURE() << "Received motion event " << motionEvent; + } else if (event->getType() == InputEventType::FOCUS) { + FocusEvent& focusEvent = static_cast<FocusEvent&>(*event); + ADD_FAILURE() << "Received focus event, hasFocus = " + << (focusEvent.getHasFocus() ? "true" : "false"); + } else if (event->getType() == InputEventType::CAPTURE) { + const auto& captureEvent = static_cast<CaptureEvent&>(*event); + ADD_FAILURE() << "Received capture event, pointerCaptureEnabled = " + << (captureEvent.getPointerCaptureEnabled() ? "true" : "false"); + } else if (event->getType() == InputEventType::TOUCH_MODE) { + const auto& touchModeEvent = static_cast<TouchModeEvent&>(*event); + ADD_FAILURE() << "Received touch mode event, inTouchMode = " + << (touchModeEvent.isInTouchMode() ? "true" : "false"); + } + FAIL() << mName.c_str() + << ": should not have received any events, so consume() should return NULL"; +} + +sp<IBinder> FakeInputReceiver::getToken() { + return mConsumer.getChannel()->getConnectionToken(); +} + +int FakeInputReceiver::getChannelFd() { + return mConsumer.getChannel()->getFd(); +} + +// --- FakeWindowHandle --- + +std::function<void(const std::unique_ptr<InputEvent>&, const gui::WindowInfo&)> + FakeWindowHandle::sOnEventReceivedCallback{}; + +std::atomic<int32_t> FakeWindowHandle::sId{1}; + +FakeWindowHandle::FakeWindowHandle( + const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle, + const std::unique_ptr<inputdispatcher::InputDispatcher>& dispatcher, const std::string name, + int32_t displayId, bool createInputChannel) + : mName(name) { + sp<IBinder> token; + if (createInputChannel) { + base::Result<std::unique_ptr<InputChannel>> channel = dispatcher->createInputChannel(name); + token = (*channel)->getConnectionToken(); + mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name); + } + + inputApplicationHandle->updateInfo(); + mInfo.applicationInfo = *inputApplicationHandle->getInfo(); + + mInfo.token = token; + mInfo.id = sId++; + mInfo.name = name; + mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT; + mInfo.alpha = 1.0; + mInfo.frame = Rect(0, 0, WIDTH, HEIGHT); + mInfo.transform.set(0, 0); + mInfo.globalScaleFactor = 1.0; + mInfo.touchableRegion.clear(); + mInfo.addTouchableRegion(Rect(0, 0, WIDTH, HEIGHT)); + mInfo.ownerPid = WINDOW_PID; + mInfo.ownerUid = WINDOW_UID; + mInfo.displayId = displayId; + mInfo.inputConfig = InputConfig::DEFAULT; +} + +sp<FakeWindowHandle> FakeWindowHandle::clone(int32_t displayId) { + sp<FakeWindowHandle> handle = sp<FakeWindowHandle>::make(mInfo.name + "(Mirror)"); + handle->mInfo = mInfo; + handle->mInfo.displayId = displayId; + handle->mInfo.id = sId++; + handle->mInputReceiver = mInputReceiver; + return handle; +} + +std::unique_ptr<KeyEvent> FakeWindowHandle::consumeKey(bool handled) { + std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED, handled); + if (event == nullptr) { + ADD_FAILURE() << "No event"; + return nullptr; + } + if (event->getType() != InputEventType::KEY) { + ADD_FAILURE() << "Instead of key event, got " << event; + return nullptr; + } + return std::unique_ptr<KeyEvent>(static_cast<KeyEvent*>(event.release())); +} + +std::unique_ptr<MotionEvent> FakeWindowHandle::consumeMotionEvent( + const ::testing::Matcher<MotionEvent>& matcher) { + std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED); + if (event == nullptr) { + std::ostringstream matcherDescription; + matcher.DescribeTo(&matcherDescription); + ADD_FAILURE() << "No event (expected " << matcherDescription.str() << ") on " << mName; + return nullptr; + } + if (event->getType() != InputEventType::MOTION) { + ADD_FAILURE() << "Instead of motion event, got " << *event << " on " << mName; + return nullptr; + } + std::unique_ptr<MotionEvent> motionEvent = + std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event.release())); + if (motionEvent == nullptr) { + return nullptr; + } + EXPECT_THAT(*motionEvent, matcher) << " on " << mName; + return motionEvent; +} + +void FakeWindowHandle::assertNoEvents(std::optional<std::chrono::milliseconds> timeout) { + if (mInputReceiver == nullptr && mInfo.inputConfig.test(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(timeout.value_or(CONSUME_TIMEOUT_NO_EVENT_EXPECTED)); +} + +std::unique_ptr<InputEvent> FakeWindowHandle::consume(std::chrono::milliseconds timeout, + bool handled) { + 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"; + } + + if (sOnEventReceivedCallback != nullptr) { + sOnEventReceivedCallback(event, mInfo); + } + return event; +} + +std::pair<std::optional<uint32_t /*seq*/>, std::unique_ptr<InputEvent>> +FakeWindowHandle::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; + + if (sOnEventReceivedCallback != nullptr) { + sOnEventReceivedCallback(event, mInfo); + } + return out; +} + +} // namespace android diff --git a/services/inputflinger/tests/FakeWindows.h b/services/inputflinger/tests/FakeWindows.h new file mode 100644 index 0000000000..c0c8975e76 --- /dev/null +++ b/services/inputflinger/tests/FakeWindows.h @@ -0,0 +1,387 @@ +/* + * 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/InputDispatcher.h" +#include "TestEventMatchers.h" + +#include <android-base/logging.h> +#include <gtest/gtest.h> +#include <input/Input.h> +#include <input/InputConsumer.h> + +namespace android { + +/** + * If we expect to receive the event, the timeout can be made very long. When the test are running + * correctly, we will actually never wait until the end of the timeout because the wait will end + * when the event comes in. Still, this value shouldn't be infinite. During development, a local + * change may cause the test to fail. This timeout should be short enough to not annoy so that the + * developer can see the failure quickly (on human scale). + */ +static constexpr std::chrono::duration CONSUME_TIMEOUT_EVENT_EXPECTED = 1000ms; + +/** + * When no event is expected, we can have a very short timeout. A large value here would slow down + * the tests. In the unlikely event of system being too slow, the event may still be present but the + * timeout would complete before it is consumed. This would result in test flakiness. If this + * occurs, the flakiness rate would be high. Since the flakes are treated with high priority, this + * would get noticed and addressed quickly. + */ +static constexpr std::chrono::duration CONSUME_TIMEOUT_NO_EVENT_EXPECTED = 10ms; + +/** + * The default pid and uid for windows created on the primary display by the test. + */ +static constexpr gui::Pid WINDOW_PID{999}; +static constexpr gui::Uid WINDOW_UID{1001}; + +/** + * Default input dispatching timeout if there is no focused application or paused window + * from which to determine an appropriate dispatching timeout. + */ +static const std::chrono::duration DISPATCHING_TIMEOUT = std::chrono::milliseconds( + android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS * + android::base::HwTimeoutMultiplier()); + +// --- FakeInputReceiver --- + +class FakeInputReceiver { +public: + explicit FakeInputReceiver(std::unique_ptr<InputChannel> clientChannel, const std::string name); + + std::unique_ptr<InputEvent> consume(std::chrono::milliseconds timeout, bool handled = false); + /** + * Receive an event without acknowledging it. + * Return the sequence number that could later be used to send finished signal. + */ + std::pair<std::optional<uint32_t>, std::unique_ptr<InputEvent>> receiveEvent( + std::chrono::milliseconds timeout); + /** + * To be used together with "receiveEvent" to complete the consumption of an event. + */ + void finishEvent(uint32_t consumeSeq, bool handled = true); + + void sendTimeline(int32_t inputEventId, std::array<nsecs_t, GraphicsTimeline::SIZE> timeline); + + void consumeEvent(android::InputEventType expectedEventType, int32_t expectedAction, + std::optional<int32_t> expectedDisplayId, + std::optional<int32_t> expectedFlags); + + std::unique_ptr<MotionEvent> consumeMotion(); + void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher); + + void consumeFocusEvent(bool hasFocus, bool inTouchMode); + void consumeCaptureEvent(bool hasCapture); + void consumeDragEvent(bool isExiting, float x, float y); + void consumeTouchModeEvent(bool inTouchMode); + + void assertNoEvents(std::chrono::milliseconds timeout); + + sp<IBinder> getToken(); + int getChannelFd(); + +private: + InputConsumer mConsumer; + DynamicInputEventFactory mEventFactory; + std::string mName; +}; + +// --- FakeWindowHandle --- + +class FakeWindowHandle : public gui::WindowInfoHandle { +public: + static const int32_t WIDTH = 600; + static const int32_t HEIGHT = 800; + using InputConfig = gui::WindowInfo::InputConfig; + + // This is a callback that is fired when an event is received by the window. + // It is static to avoid having to pass it individually into all of the FakeWindowHandles + // created by tests. + // TODO(b/210460522): Update the tests to use a factory pattern so that we can avoid + // the need to make this static. + static std::function<void(const std::unique_ptr<InputEvent>&, const gui::WindowInfo&)> + sOnEventReceivedCallback; + + FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle, + const std::unique_ptr<inputdispatcher::InputDispatcher>& dispatcher, + const std::string name, int32_t displayId, bool createInputChannel = true); + + sp<FakeWindowHandle> clone(int32_t displayId); + + inline void setTouchable(bool touchable) { + mInfo.setInputConfig(InputConfig::NOT_TOUCHABLE, !touchable); + } + + inline void setFocusable(bool focusable) { + mInfo.setInputConfig(InputConfig::NOT_FOCUSABLE, !focusable); + } + + inline void setVisible(bool visible) { + mInfo.setInputConfig(InputConfig::NOT_VISIBLE, !visible); + } + + inline void setDispatchingTimeout(std::chrono::nanoseconds timeout) { + mInfo.dispatchingTimeout = timeout; + } + + inline void setPaused(bool paused) { + mInfo.setInputConfig(InputConfig::PAUSE_DISPATCHING, paused); + } + + inline void setPreventSplitting(bool preventSplitting) { + mInfo.setInputConfig(InputConfig::PREVENT_SPLITTING, preventSplitting); + } + + inline void setSlippery(bool slippery) { + mInfo.setInputConfig(InputConfig::SLIPPERY, slippery); + } + + inline void setWatchOutsideTouch(bool watchOutside) { + mInfo.setInputConfig(InputConfig::WATCH_OUTSIDE_TOUCH, watchOutside); + } + + inline void setSpy(bool spy) { mInfo.setInputConfig(InputConfig::SPY, spy); } + + inline void setInterceptsStylus(bool interceptsStylus) { + mInfo.setInputConfig(InputConfig::INTERCEPTS_STYLUS, interceptsStylus); + } + + inline void setDropInput(bool dropInput) { + mInfo.setInputConfig(InputConfig::DROP_INPUT, dropInput); + } + + inline void setDropInputIfObscured(bool dropInputIfObscured) { + mInfo.setInputConfig(InputConfig::DROP_INPUT_IF_OBSCURED, dropInputIfObscured); + } + + inline void setNoInputChannel(bool noInputChannel) { + mInfo.setInputConfig(InputConfig::NO_INPUT_CHANNEL, noInputChannel); + } + + inline void setDisableUserActivity(bool disableUserActivity) { + mInfo.setInputConfig(InputConfig::DISABLE_USER_ACTIVITY, disableUserActivity); + } + + inline void setGlobalStylusBlocksTouch(bool shouldGlobalStylusBlockTouch) { + mInfo.setInputConfig(InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH, shouldGlobalStylusBlockTouch); + } + + inline void setAlpha(float alpha) { mInfo.alpha = alpha; } + + inline void setTouchOcclusionMode(gui::TouchOcclusionMode mode) { + mInfo.touchOcclusionMode = mode; + } + + inline void setApplicationToken(sp<IBinder> token) { mInfo.applicationInfo.token = token; } + + inline void setFrame(const Rect& frame, + const ui::Transform& displayTransform = ui::Transform()) { + mInfo.frame = frame; + mInfo.touchableRegion.clear(); + mInfo.addTouchableRegion(frame); + + const Rect logicalDisplayFrame = displayTransform.transform(frame); + ui::Transform translate; + translate.set(-logicalDisplayFrame.left, -logicalDisplayFrame.top); + mInfo.transform = translate * displayTransform; + } + + inline void setTouchableRegion(const Region& region) { mInfo.touchableRegion = region; } + + inline void setIsWallpaper(bool isWallpaper) { + mInfo.setInputConfig(InputConfig::IS_WALLPAPER, isWallpaper); + } + + inline void setDupTouchToWallpaper(bool hasWallpaper) { + mInfo.setInputConfig(InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER, hasWallpaper); + } + + inline void setTrustedOverlay(bool trustedOverlay) { + mInfo.setInputConfig(InputConfig::TRUSTED_OVERLAY, trustedOverlay); + } + + inline void setWindowTransform(float dsdx, float dtdx, float dtdy, float dsdy) { + mInfo.transform.set(dsdx, dtdx, dtdy, dsdy); + } + + inline void setWindowScale(float xScale, float yScale) { + setWindowTransform(xScale, 0, 0, yScale); + } + + inline void setWindowOffset(float offsetX, float offsetY) { + mInfo.transform.set(offsetX, offsetY); + } + + std::unique_ptr<KeyEvent> consumeKey(bool handled = true); + + inline void consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) { + std::unique_ptr<KeyEvent> keyEvent = consumeKey(); + ASSERT_NE(nullptr, keyEvent); + ASSERT_THAT(*keyEvent, matcher); + } + + inline void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) { + consumeKeyEvent(testing::AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), + WithDisplayId(expectedDisplayId), WithFlags(expectedFlags))); + } + + inline void consumeKeyUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) { + consumeKeyEvent(testing::AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), + WithDisplayId(expectedDisplayId), WithFlags(expectedFlags))); + } + + inline void consumeMotionCancel(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, + int32_t expectedFlags = 0) { + consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL), + WithDisplayId(expectedDisplayId), + WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED))); + } + + inline void consumeMotionMove(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, + int32_t expectedFlags = 0) { + consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithDisplayId(expectedDisplayId), + WithFlags(expectedFlags))); + } + + inline void consumeMotionDown(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, + int32_t expectedFlags = 0) { + consumeAnyMotionDown(expectedDisplayId, expectedFlags); + } + + inline void consumeAnyMotionDown(std::optional<int32_t> expectedDisplayId = std::nullopt, + std::optional<int32_t> expectedFlags = std::nullopt) { + consumeMotionEvent( + testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + testing::Conditional(expectedDisplayId.has_value(), + WithDisplayId(*expectedDisplayId), testing::_), + testing::Conditional(expectedFlags.has_value(), + WithFlags(*expectedFlags), testing::_))); + } + + inline void consumeMotionPointerDown(int32_t pointerIdx, + int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, + int32_t expectedFlags = 0) { + const int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN | + (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + consumeMotionEvent(testing::AllOf(WithMotionAction(action), + WithDisplayId(expectedDisplayId), + WithFlags(expectedFlags))); + } + + inline void consumeMotionPointerUp(int32_t pointerIdx, + int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, + int32_t expectedFlags = 0) { + const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP | + (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + consumeMotionEvent(testing::AllOf(WithMotionAction(action), + WithDisplayId(expectedDisplayId), + WithFlags(expectedFlags))); + } + + inline void consumeMotionUp(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, + int32_t expectedFlags = 0) { + consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithDisplayId(expectedDisplayId), + WithFlags(expectedFlags))); + } + + inline void consumeMotionOutside(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, + int32_t expectedFlags = 0) { + consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_OUTSIDE), + WithDisplayId(expectedDisplayId), + WithFlags(expectedFlags))); + } + + inline void consumeMotionOutsideWithZeroedCoords() { + consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_OUTSIDE), + WithRawCoords(0, 0))); + } + + inline void consumeFocusEvent(bool hasFocus, bool inTouchMode = true) { + ASSERT_NE(mInputReceiver, nullptr) + << "Cannot consume events from a window with no receiver"; + mInputReceiver->consumeFocusEvent(hasFocus, inTouchMode); + } + + inline void consumeCaptureEvent(bool hasCapture) { + ASSERT_NE(mInputReceiver, nullptr) + << "Cannot consume events from a window with no receiver"; + mInputReceiver->consumeCaptureEvent(hasCapture); + } + + std::unique_ptr<MotionEvent> consumeMotionEvent( + const ::testing::Matcher<MotionEvent>& matcher = testing::_); + + inline void consumeDragEvent(bool isExiting, float x, float y) { + mInputReceiver->consumeDragEvent(isExiting, x, y); + } + + inline void consumeTouchModeEvent(bool inTouchMode) { + ASSERT_NE(mInputReceiver, nullptr) + << "Cannot consume events from a window with no receiver"; + mInputReceiver->consumeTouchModeEvent(inTouchMode); + } + + inline std::pair<std::optional<uint32_t>, std::unique_ptr<InputEvent>> receiveEvent() { + return receive(); + } + + inline void finishEvent(uint32_t sequenceNum) { + ASSERT_NE(mInputReceiver, nullptr) << "Invalid receive event on window with no receiver"; + mInputReceiver->finishEvent(sequenceNum); + } + + inline void sendTimeline(int32_t inputEventId, + std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) { + ASSERT_NE(mInputReceiver, nullptr) << "Invalid receive event on window with no receiver"; + mInputReceiver->sendTimeline(inputEventId, timeline); + } + + void assertNoEvents(std::optional<std::chrono::milliseconds> timeout = {}); + + inline sp<IBinder> getToken() { return mInfo.token; } + + inline const std::string& getName() { return mName; } + + inline void setOwnerInfo(gui::Pid ownerPid, gui::Uid ownerUid) { + mInfo.ownerPid = ownerPid; + mInfo.ownerUid = ownerUid; + } + + inline gui::Pid getPid() const { return mInfo.ownerPid; } + + inline void destroyReceiver() { mInputReceiver = nullptr; } + + inline 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); + +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(); +}; + +} // namespace android diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 9e3a4f10ef..62a92352e5 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -16,7 +16,9 @@ #include "../dispatcher/InputDispatcher.h" #include "FakeApplicationHandle.h" +#include "FakeInputDispatcherPolicy.h" #include "FakeInputTracingBackend.h" +#include "FakeWindows.h" #include "TestEventMatchers.h" #include <NotifyArgsBuilders.h> @@ -57,6 +59,8 @@ namespace android::inputdispatcher { using namespace ftl::flag_operators; using testing::AllOf; using testing::Not; +using testing::Pointee; +using testing::UnorderedElementsAre; namespace { @@ -106,10 +110,6 @@ static constexpr int32_t POINTER_1_UP = static constexpr int32_t POINTER_2_UP = AMOTION_EVENT_ACTION_POINTER_UP | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); -// The default pid and uid for windows created on the primary display by the test. -static constexpr gui::Pid WINDOW_PID{999}; -static constexpr gui::Uid WINDOW_UID{1001}; - // The default pid and uid for the windows created on the secondary display by the test. static constexpr gui::Pid SECONDARY_WINDOW_PID{1010}; static constexpr gui::Uid SECONDARY_WINDOW_UID{1012}; @@ -117,23 +117,6 @@ static constexpr gui::Uid SECONDARY_WINDOW_UID{1012}; // An arbitrary pid of the gesture monitor window static constexpr gui::Pid MONITOR_PID{2001}; -/** - * If we expect to receive the event, the timeout can be made very long. When the test are running - * correctly, we will actually never wait until the end of the timeout because the wait will end - * when the event comes in. Still, this value shouldn't be infinite. During development, a local - * change may cause the test to fail. This timeout should be short enough to not annoy so that the - * developer can see the failure quickly (on human scale). - */ -static constexpr std::chrono::duration CONSUME_TIMEOUT_EVENT_EXPECTED = 1000ms; -/** - * When no event is expected, we can have a very short timeout. A large value here would slow down - * the tests. In the unlikely event of system being too slow, the event may still be present but the - * timeout would complete before it is consumed. This would result in test flakiness. If this - * occurs, the flakiness rate would be high. Since the flakes are treated with high priority, this - * would get noticed and addressed quickly. - */ -static constexpr std::chrono::duration CONSUME_TIMEOUT_NO_EVENT_EXPECTED = 10ms; - static constexpr int expectedWallpaperFlags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; @@ -151,538 +134,60 @@ static KeyEvent getTestKeyEvent() { return event; } -// --- FakeInputDispatcherPolicy --- - -class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface { - struct AnrResult { - sp<IBinder> token{}; - 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; - } - }; - +/** + * Provide a local override for a flag value. The value is restored when the object of this class + * goes out of scope. + * This class is not intended to be used directly, because its usage is cumbersome. + * Instead, a wrapper macro SCOPED_FLAG_OVERRIDE is provided. + */ +class ScopedFlagOverride { public: - FakeInputDispatcherPolicy() = default; - virtual ~FakeInputDispatcherPolicy() = default; - - void assertFilterInputEventWasCalled(const NotifyKeyArgs& args) { - assertFilterInputEventWasCalledInternal([&args](const InputEvent& event) { - ASSERT_EQ(event.getType(), InputEventType::KEY); - EXPECT_EQ(event.getDisplayId(), args.displayId); - - const auto& keyEvent = static_cast<const KeyEvent&>(event); - EXPECT_EQ(keyEvent.getEventTime(), args.eventTime); - EXPECT_EQ(keyEvent.getAction(), args.action); - }); - } - - void assertFilterInputEventWasCalled(const NotifyMotionArgs& args, vec2 point) { - assertFilterInputEventWasCalledInternal([&](const InputEvent& event) { - ASSERT_EQ(event.getType(), InputEventType::MOTION); - EXPECT_EQ(event.getDisplayId(), args.displayId); - - const auto& motionEvent = static_cast<const MotionEvent&>(event); - EXPECT_EQ(motionEvent.getEventTime(), args.eventTime); - EXPECT_EQ(motionEvent.getAction(), args.action); - EXPECT_NEAR(motionEvent.getX(0), point.x, MotionEvent::ROUNDING_PRECISION); - EXPECT_NEAR(motionEvent.getY(0), point.y, MotionEvent::ROUNDING_PRECISION); - EXPECT_NEAR(motionEvent.getRawX(0), point.x, MotionEvent::ROUNDING_PRECISION); - EXPECT_NEAR(motionEvent.getRawY(0), point.y, MotionEvent::ROUNDING_PRECISION); - }); - } - - void assertFilterInputEventWasNotCalled() { - std::scoped_lock lock(mLock); - ASSERT_EQ(nullptr, mFilteredEvent); - } - - void assertNotifyConfigurationChangedWasCalled(nsecs_t when) { - std::scoped_lock lock(mLock); - ASSERT_TRUE(mConfigurationChangedTime) - << "Timed out waiting for configuration changed call"; - ASSERT_EQ(*mConfigurationChangedTime, when); - mConfigurationChangedTime = std::nullopt; - } - - void assertNotifySwitchWasCalled(const NotifySwitchArgs& args) { - std::scoped_lock lock(mLock); - ASSERT_TRUE(mLastNotifySwitch); - // We do not check id because it is not exposed to the policy - EXPECT_EQ(args.eventTime, mLastNotifySwitch->eventTime); - EXPECT_EQ(args.policyFlags, mLastNotifySwitch->policyFlags); - EXPECT_EQ(args.switchValues, mLastNotifySwitch->switchValues); - EXPECT_EQ(args.switchMask, mLastNotifySwitch->switchMask); - mLastNotifySwitch = std::nullopt; - } - - void assertOnPointerDownEquals(const sp<IBinder>& touchedToken) { - std::scoped_lock lock(mLock); - ASSERT_EQ(touchedToken, mOnPointerDownToken); - mOnPointerDownToken.clear(); - } - - void assertOnPointerDownWasNotCalled() { - std::scoped_lock lock(mLock); - ASSERT_TRUE(mOnPointerDownToken == nullptr) - << "Expected onPointerDownOutsideFocus to not have been called"; - } - - // This function must be called soon after the expected ANR timer starts, - // because we are also checking how much time has passed. - void assertNotifyNoFocusedWindowAnrWasCalled( - std::chrono::nanoseconds timeout, - const std::shared_ptr<InputApplicationHandle>& expectedApplication) { - std::unique_lock lock(mLock); - android::base::ScopedLockAssertion assumeLocked(mLock); - std::shared_ptr<InputApplicationHandle> application; - ASSERT_NO_FATAL_FAILURE( - application = getAnrTokenLockedInterruptible(timeout, mAnrApplications, lock)); - ASSERT_EQ(expectedApplication, application); - } - - void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout, - const sp<WindowInfoHandle>& window) { - LOG_ALWAYS_FATAL_IF(window == nullptr, "window should not be null"); - assertNotifyWindowUnresponsiveWasCalled(timeout, window->getToken(), - window->getInfo()->ownerPid); - } - - void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout, - const sp<IBinder>& expectedToken, - std::optional<gui::Pid> expectedPid) { - std::unique_lock lock(mLock); - android::base::ScopedLockAssertion assumeLocked(mLock); - AnrResult result; - ASSERT_NO_FATAL_FAILURE(result = - getAnrTokenLockedInterruptible(timeout, mAnrWindows, lock)); - ASSERT_EQ(expectedToken, result.token); - ASSERT_EQ(expectedPid, result.pid); - } - - /** Wrap call with ASSERT_NO_FATAL_FAILURE() to ensure the return value is valid. */ - sp<IBinder> getUnresponsiveWindowToken(std::chrono::nanoseconds timeout) { - std::unique_lock lock(mLock); - android::base::ScopedLockAssertion assumeLocked(mLock); - AnrResult result = getAnrTokenLockedInterruptible(timeout, mAnrWindows, lock); - const auto& [token, _] = result; - return token; - } - - void assertNotifyWindowResponsiveWasCalled(const sp<IBinder>& expectedToken, - std::optional<gui::Pid> expectedPid) { - std::unique_lock lock(mLock); - android::base::ScopedLockAssertion assumeLocked(mLock); - AnrResult result; - ASSERT_NO_FATAL_FAILURE( - result = getAnrTokenLockedInterruptible(0s, mResponsiveWindows, lock)); - ASSERT_EQ(expectedToken, result.token); - ASSERT_EQ(expectedPid, result.pid); - } - - /** Wrap call with ASSERT_NO_FATAL_FAILURE() to ensure the return value is valid. */ - sp<IBinder> getResponsiveWindowToken() { - std::unique_lock lock(mLock); - android::base::ScopedLockAssertion assumeLocked(mLock); - AnrResult result = getAnrTokenLockedInterruptible(0s, mResponsiveWindows, lock); - const auto& [token, _] = result; - return token; - } - - void assertNotifyAnrWasNotCalled() { - std::scoped_lock lock(mLock); - ASSERT_TRUE(mAnrApplications.empty()); - ASSERT_TRUE(mAnrWindows.empty()); - ASSERT_TRUE(mResponsiveWindows.empty()) - << "ANR was not called, but please also consume the 'connection is responsive' " - "signal"; - } - - PointerCaptureRequest assertSetPointerCaptureCalled(const sp<WindowInfoHandle>& window, - bool enabled) { - std::unique_lock lock(mLock); - base::ScopedLockAssertion assumeLocked(mLock); - - if (!mPointerCaptureChangedCondition - .wait_for(lock, 100ms, [this, enabled, window]() REQUIRES(mLock) { - if (enabled) { - return mPointerCaptureRequest->isEnable() && - mPointerCaptureRequest->window == window->getToken(); - } else { - return !mPointerCaptureRequest->isEnable(); - } - })) { - ADD_FAILURE() << "Timed out waiting for setPointerCapture(" << window->getName() << ", " - << enabled << ") to be called."; - return {}; - } - auto request = *mPointerCaptureRequest; - mPointerCaptureRequest.reset(); - return request; - } - - void assertSetPointerCaptureNotCalled() { - std::unique_lock lock(mLock); - base::ScopedLockAssertion assumeLocked(mLock); - - if (mPointerCaptureChangedCondition.wait_for(lock, 100ms) != std::cv_status::timeout) { - FAIL() << "Expected setPointerCapture(request) to not be called, but was called. " - "enabled = " - << std::to_string(mPointerCaptureRequest->isEnable()); - } - mPointerCaptureRequest.reset(); - } - - void assertDropTargetEquals(const InputDispatcherInterface& dispatcher, - const sp<IBinder>& targetToken) { - dispatcher.waitForIdle(); - std::scoped_lock lock(mLock); - ASSERT_TRUE(mNotifyDropWindowWasCalled); - ASSERT_EQ(targetToken, mDropTargetWindowToken); - mNotifyDropWindowWasCalled = false; - } - - void assertNotifyInputChannelBrokenWasCalled(const sp<IBinder>& token) { - std::unique_lock lock(mLock); - base::ScopedLockAssertion assumeLocked(mLock); - std::optional<sp<IBinder>> receivedToken = - getItemFromStorageLockedInterruptible(100ms, mBrokenInputChannels, lock, - mNotifyInputChannelBroken); - ASSERT_TRUE(receivedToken.has_value()) << "Did not receive the broken channel token"; - ASSERT_EQ(token, *receivedToken); - } - - /** - * Set policy timeout. A value of zero means next key will not be intercepted. - */ - void setInterceptKeyTimeout(std::chrono::milliseconds timeout) { - mInterceptKeyTimeout = timeout; - } - - std::chrono::nanoseconds getKeyWaitingForEventsTimeout() override { return 500ms; } - - void setStaleEventTimeout(std::chrono::nanoseconds timeout) { mStaleEventTimeout = timeout; } - - 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"; - } - - /** - * 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) { - ASSERT_EQ(std::make_pair(deviceId, uids), mNotifiedInteractions.popWithTimeout(100ms)); - } - - void assertNotifyDeviceInteractionWasNotCalled() { - ASSERT_FALSE(mNotifiedInteractions.popWithTimeout(10ms)); - } - - void setUnhandledKeyHandler(std::function<std::optional<KeyEvent>(const KeyEvent&)> handler) { - std::scoped_lock lock(mLock); - mUnhandledKeyHandler = handler; - } - - void assertUnhandledKeyReported(int32_t keycode) { - std::unique_lock lock(mLock); - base::ScopedLockAssertion assumeLocked(mLock); - std::optional<int32_t> unhandledKeycode = - getItemFromStorageLockedInterruptible(100ms, mReportedUnhandledKeycodes, lock, - mNotifyUnhandledKey); - ASSERT_TRUE(unhandledKeycode) << "Expected unhandled key to be reported"; - ASSERT_EQ(unhandledKeycode, keycode); - } - - void assertUnhandledKeyNotReported() { - std::unique_lock lock(mLock); - base::ScopedLockAssertion assumeLocked(mLock); - std::optional<int32_t> unhandledKeycode = - getItemFromStorageLockedInterruptible(10ms, mReportedUnhandledKeycodes, lock, - mNotifyUnhandledKey); - ASSERT_FALSE(unhandledKeycode) << "Expected unhandled key NOT to be reported"; + ScopedFlagOverride(std::function<bool()> read, std::function<void(bool)> write, bool value) + : mInitialValue(read()), mWriteValue(write) { + mWriteValue(value); } + ~ScopedFlagOverride() { mWriteValue(mInitialValue); } private: - std::mutex mLock; - std::unique_ptr<InputEvent> mFilteredEvent GUARDED_BY(mLock); - std::optional<nsecs_t> mConfigurationChangedTime GUARDED_BY(mLock); - sp<IBinder> mOnPointerDownToken GUARDED_BY(mLock); - std::optional<NotifySwitchArgs> mLastNotifySwitch GUARDED_BY(mLock); - - std::condition_variable mPointerCaptureChangedCondition; - - std::optional<PointerCaptureRequest> mPointerCaptureRequest GUARDED_BY(mLock); - - // ANR handling - std::queue<std::shared_ptr<InputApplicationHandle>> mAnrApplications GUARDED_BY(mLock); - std::queue<AnrResult> mAnrWindows GUARDED_BY(mLock); - std::queue<AnrResult> mResponsiveWindows GUARDED_BY(mLock); - std::condition_variable mNotifyAnr; - std::queue<sp<IBinder>> mBrokenInputChannels GUARDED_BY(mLock); - std::condition_variable mNotifyInputChannelBroken; - - sp<IBinder> mDropTargetWindowToken GUARDED_BY(mLock); - bool mNotifyDropWindowWasCalled GUARDED_BY(mLock) = false; - - std::condition_variable mNotifyUserActivity; - std::queue<UserActivityPokeEvent> mUserActivityPokeEvents; - - std::chrono::milliseconds mInterceptKeyTimeout = 0ms; - - std::chrono::nanoseconds mStaleEventTimeout = 1000ms; - - BlockingQueue<std::pair<int32_t /*deviceId*/, std::set<gui::Uid>>> mNotifiedInteractions; - - std::condition_variable mNotifyUnhandledKey; - std::queue<int32_t> mReportedUnhandledKeycodes GUARDED_BY(mLock); - std::function<std::optional<KeyEvent>(const KeyEvent&)> mUnhandledKeyHandler GUARDED_BY(mLock); - - // All three ANR-related callbacks behave the same way, so we use this generic function to wait - // for a specific container to become non-empty. When the container is non-empty, return the - // first entry from the container and erase it. - template <class T> - T getAnrTokenLockedInterruptible(std::chrono::nanoseconds timeout, std::queue<T>& storage, - std::unique_lock<std::mutex>& lock) REQUIRES(mLock) { - // If there is an ANR, Dispatcher won't be idle because there are still events - // in the waitQueue that we need to check on. So we can't wait for dispatcher to be idle - // before checking if ANR was called. - // Since dispatcher is not guaranteed to call notifyNoFocusedWindowAnr right away, we need - // to provide it some time to act. 100ms seems reasonable. - std::chrono::duration timeToWait = timeout + 100ms; // provide some slack - const std::chrono::time_point start = std::chrono::steady_clock::now(); - std::optional<T> token = - getItemFromStorageLockedInterruptible(timeToWait, storage, lock, mNotifyAnr); - if (!token.has_value()) { - ADD_FAILURE() << "Did not receive the ANR callback"; - return {}; - } - - const std::chrono::duration waited = std::chrono::steady_clock::now() - start; - // Ensure that the ANR didn't get raised too early. We can't be too strict here because - // the dispatcher started counting before this function was called - if (std::chrono::abs(timeout - waited) > 100ms) { - ADD_FAILURE() << "ANR was raised too early or too late. Expected " - << std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count() - << "ms, but waited " - << std::chrono::duration_cast<std::chrono::milliseconds>(waited).count() - << "ms instead"; - } - return *token; - } - - template <class T> - std::optional<T> getItemFromStorageLockedInterruptible(std::chrono::nanoseconds timeout, - std::queue<T>& storage, - std::unique_lock<std::mutex>& lock, - std::condition_variable& condition) - REQUIRES(mLock) { - condition.wait_for(lock, timeout, - [&storage]() REQUIRES(mLock) { return !storage.empty(); }); - if (storage.empty()) { - return std::nullopt; - } - T item = storage.front(); - storage.pop(); - return std::make_optional(item); - } - - void notifyConfigurationChanged(nsecs_t when) override { - std::scoped_lock lock(mLock); - mConfigurationChangedTime = when; - } - - void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<gui::Pid> pid, - const std::string&) override { - std::scoped_lock lock(mLock); - mAnrWindows.push({connectionToken, pid}); - mNotifyAnr.notify_all(); - } - - void notifyWindowResponsive(const sp<IBinder>& connectionToken, - std::optional<gui::Pid> pid) override { - std::scoped_lock lock(mLock); - mResponsiveWindows.push({connectionToken, pid}); - mNotifyAnr.notify_all(); - } - - void notifyNoFocusedWindowAnr( - const std::shared_ptr<InputApplicationHandle>& applicationHandle) override { - std::scoped_lock lock(mLock); - mAnrApplications.push(applicationHandle); - mNotifyAnr.notify_all(); - } - - void notifyInputChannelBroken(const sp<IBinder>& connectionToken) override { - std::scoped_lock lock(mLock); - mBrokenInputChannels.push(connectionToken); - mNotifyInputChannelBroken.notify_all(); - } - - void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override {} - - void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType, - InputDeviceSensorAccuracy accuracy, nsecs_t timestamp, - const std::vector<float>& values) override {} - - void notifySensorAccuracy(int deviceId, InputDeviceSensorType sensorType, - InputDeviceSensorAccuracy accuracy) override {} - - void notifyVibratorState(int32_t deviceId, bool isOn) override {} - - bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override { - std::scoped_lock lock(mLock); - switch (inputEvent.getType()) { - case InputEventType::KEY: { - const KeyEvent& keyEvent = static_cast<const KeyEvent&>(inputEvent); - mFilteredEvent = std::make_unique<KeyEvent>(keyEvent); - break; - } - - case InputEventType::MOTION: { - const MotionEvent& motionEvent = static_cast<const MotionEvent&>(inputEvent); - mFilteredEvent = std::make_unique<MotionEvent>(motionEvent); - break; - } - default: { - ADD_FAILURE() << "Should only filter keys or motions"; - break; - } - } - return true; - } - - void interceptKeyBeforeQueueing(const KeyEvent& inputEvent, uint32_t&) override { - if (inputEvent.getAction() == AKEY_EVENT_ACTION_UP) { - // Clear intercept state when we handled the event. - mInterceptKeyTimeout = 0ms; - } - } - - 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(); - // Clear intercept state so we could dispatch the event in next wake. - mInterceptKeyTimeout = 0ms; - return delay; - } - - std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent& event, - uint32_t) override { - std::scoped_lock lock(mLock); - mReportedUnhandledKeycodes.emplace(event.getKeyCode()); - mNotifyUnhandledKey.notify_all(); - return mUnhandledKeyHandler != nullptr ? mUnhandledKeyHandler(event) : std::nullopt; - } - - void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask, - uint32_t policyFlags) override { - std::scoped_lock lock(mLock); - /** We simply reconstruct NotifySwitchArgs in policy because InputDispatcher is - * essentially a passthrough for notifySwitch. - */ - mLastNotifySwitch = - NotifySwitchArgs(InputEvent::nextId(), when, policyFlags, switchValues, switchMask); - } - - void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override { - std::scoped_lock lock(mLock); - mNotifyUserActivity.notify_all(); - mUserActivityPokeEvents.push({eventTime, eventType, displayId}); - } - - bool isStaleEvent(nsecs_t currentTime, nsecs_t eventTime) override { - return std::chrono::nanoseconds(currentTime - eventTime) >= mStaleEventTimeout; - } - - void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override { - std::scoped_lock lock(mLock); - mOnPointerDownToken = newToken; - } - - void setPointerCapture(const PointerCaptureRequest& request) override { - std::scoped_lock lock(mLock); - mPointerCaptureRequest = {request}; - mPointerCaptureChangedCondition.notify_all(); - } - - void notifyDropWindow(const sp<IBinder>& token, float x, float y) override { - std::scoped_lock lock(mLock); - mNotifyDropWindowWasCalled = true; - mDropTargetWindowToken = token; - } + const bool mInitialValue; + std::function<void(bool)> mWriteValue; +}; - void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp, - const std::set<gui::Uid>& uids) override { - ASSERT_TRUE(mNotifiedInteractions.emplace(deviceId, uids)); - } +typedef bool (*readFlagValueFunction)(); +typedef void (*writeFlagValueFunction)(bool); - void assertFilterInputEventWasCalledInternal( - const std::function<void(const InputEvent&)>& verify) { - std::scoped_lock lock(mLock); - ASSERT_NE(nullptr, mFilteredEvent) << "Expected filterInputEvent() to have been called."; - verify(*mFilteredEvent); - mFilteredEvent = nullptr; - } +/** + * Use this macro to locally override a flag value. + * Example usage: + * SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); + * Note: this works by creating a local variable in your current scope. Don't call this twice for + * the same flag, because the variable names will clash! + */ +#define SCOPED_FLAG_OVERRIDE(NAME, VALUE) \ + readFlagValueFunction read##NAME = com::android::input::flags::NAME; \ + writeFlagValueFunction write##NAME = com::android::input::flags::NAME; \ + ScopedFlagOverride override##NAME(read##NAME, write##NAME, (VALUE)) - gui::Uid getPackageUid(std::string) override { return gui::Uid::INVALID; } -}; } // namespace // --- 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; + std::shared_ptr<VerifyingTrace> mVerifyingTrace; void SetUp() override { - gVerifyingTrace->reset(); + mVerifyingTrace = std::make_shared<VerifyingTrace>(); + FakeWindowHandle::sOnEventReceivedCallback = [this](const auto& _1, const auto& _2) { + handleEventReceivedByWindow(_1, _2); + }; + mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>(); mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy, std::make_unique<FakeInputTracingBackend>( - gVerifyingTrace)); + mVerifyingTrace)); mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false); // Start InputDispatcher thread @@ -690,12 +195,35 @@ protected: } void TearDown() override { - ASSERT_NO_FATAL_FAILURE(gVerifyingTrace->verifyExpectedEventsTraced()); + ASSERT_NO_FATAL_FAILURE(mVerifyingTrace->verifyExpectedEventsTraced()); + FakeWindowHandle::sOnEventReceivedCallback = nullptr; + ASSERT_EQ(OK, mDispatcher->stop()); mFakePolicy.reset(); mDispatcher.reset(); } + void handleEventReceivedByWindow(const std::unique_ptr<InputEvent>& event, + const gui::WindowInfo& info) { + if (!event) { + return; + } + + switch (event->getType()) { + case InputEventType::KEY: { + mVerifyingTrace->expectKeyDispatchTraced(static_cast<KeyEvent&>(*event), info.id); + break; + } + case InputEventType::MOTION: { + mVerifyingTrace->expectMotionDispatchTraced(static_cast<MotionEvent&>(*event), + info.id); + break; + } + default: + break; + } + } + /** * Used for debugging when writing the test */ @@ -907,604 +435,6 @@ TEST_F(InputDispatcherTest, NotifySwitch_CallsPolicy) { namespace { static constexpr std::chrono::duration INJECT_EVENT_TIMEOUT = 500ms; -// Default input dispatching timeout if there is no focused application or paused window -// from which to determine an appropriate dispatching timeout. -static const std::chrono::duration DISPATCHING_TIMEOUT = std::chrono::milliseconds( - android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS * - android::base::HwTimeoutMultiplier()); - -class FakeInputReceiver { -public: - explicit FakeInputReceiver(std::unique_ptr<InputChannel> clientChannel, const std::string name) - : mConsumer(std::move(clientChannel)), mName(name) {} - - 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 std::move(event); - } - - /** - * Receive an event without acknowledging it. - * Return the sequence number that could later be used to send finished signal. - */ - std::pair<std::optional<uint32_t>, std::unique_ptr<InputEvent>> receiveEvent( - std::chrono::milliseconds timeout) { - uint32_t consumeSeq; - 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, - &rawEventPtr); - event = std::unique_ptr<InputEvent>(rawEventPtr); - std::chrono::duration elapsed = std::chrono::steady_clock::now() - start; - if (elapsed > timeout) { - break; - } - } - - if (status == WOULD_BLOCK) { - // Just means there's no event available. - return std::make_pair(std::nullopt, nullptr); - } - - if (status != OK) { - ADD_FAILURE() << mName.c_str() << ": consumer consume should return OK."; - return std::make_pair(std::nullopt, nullptr); - } - if (event == nullptr) { - ADD_FAILURE() << "Consumed correctly, but received NULL event from consumer"; - } - return std::make_pair(consumeSeq, std::move(event)); - } - - /** - * To be used together with "receiveEvent" to complete the consumption of an event. - */ - void finishEvent(uint32_t consumeSeq, bool handled = true) { - const status_t status = mConsumer.sendFinishedSignal(consumeSeq, handled); - ASSERT_EQ(OK, status) << mName.c_str() << ": consumer sendFinishedSignal should return OK."; - } - - void sendTimeline(int32_t inputEventId, std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) { - const status_t status = mConsumer.sendTimeline(inputEventId, timeline); - ASSERT_EQ(OK, status); - } - - void consumeEvent(InputEventType expectedEventType, int32_t expectedAction, - std::optional<int32_t> expectedDisplayId, - std::optional<int32_t> expectedFlags) { - 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(expectedEventType, event->getType()) - << mName.c_str() << " expected " << ftl::enum_string(expectedEventType) - << " event, got " << *event; - - if (expectedDisplayId.has_value()) { - EXPECT_EQ(expectedDisplayId, event->getDisplayId()); - } - - switch (expectedEventType) { - case InputEventType::KEY: { - const KeyEvent& keyEvent = static_cast<const KeyEvent&>(*event); - ASSERT_THAT(keyEvent, WithKeyAction(expectedAction)); - if (expectedFlags.has_value()) { - EXPECT_EQ(expectedFlags.value(), keyEvent.getFlags()); - } - break; - } - case InputEventType::MOTION: { - const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event); - ASSERT_THAT(motionEvent, WithMotionAction(expectedAction)); - if (expectedFlags.has_value()) { - EXPECT_EQ(expectedFlags.value(), motionEvent.getFlags()); - } - break; - } - case InputEventType::FOCUS: { - FAIL() << "Use 'consumeFocusEvent' for FOCUS events"; - } - case InputEventType::CAPTURE: { - FAIL() << "Use 'consumeCaptureEvent' for CAPTURE events"; - } - case InputEventType::TOUCH_MODE: { - FAIL() << "Use 'consumeTouchModeEvent' for TOUCH_MODE events"; - } - case InputEventType::DRAG: { - FAIL() << "Use 'consumeDragEvent' for DRAG events"; - } - } - } - - 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."; - return nullptr; - } - - if (event->getType() != InputEventType::MOTION) { - ADD_FAILURE() << mName << " expected a MotionEvent, got " << *event; - return nullptr; - } - return std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event.release())); - } - - void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) { - 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) { - 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()) - << "Instead of FocusEvent, got " << *event; - - 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()); - } - - void consumeCaptureEvent(bool hasCapture) { - 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()) - << "Instead of CaptureEvent, got " << *event; - - ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) - << mName.c_str() << ": event displayId should always be NONE."; - - const auto& captureEvent = static_cast<const CaptureEvent&>(*event); - EXPECT_EQ(hasCapture, captureEvent.getPointerCaptureEnabled()); - } - - void consumeDragEvent(bool isExiting, float x, float y) { - 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; - - EXPECT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) - << mName.c_str() << ": event displayId should always be NONE."; - - const auto& dragEvent = static_cast<const DragEvent&>(*event); - EXPECT_EQ(isExiting, dragEvent.isExiting()); - EXPECT_EQ(x, dragEvent.getX()); - EXPECT_EQ(y, dragEvent.getY()); - } - - void consumeTouchModeEvent(bool inTouchMode) { - 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()) - << "Instead of TouchModeEvent, got " << *event; - - ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) - << mName.c_str() << ": event displayId should always be NONE."; - const auto& touchModeEvent = static_cast<const TouchModeEvent&>(*event); - EXPECT_EQ(inTouchMode, touchModeEvent.isInTouchMode()); - } - - void assertNoEvents(std::chrono::milliseconds timeout) { - std::unique_ptr<InputEvent> event = consume(timeout); - if (event == nullptr) { - return; - } - if (event->getType() == InputEventType::KEY) { - KeyEvent& keyEvent = static_cast<KeyEvent&>(*event); - ADD_FAILURE() << "Received key event " << keyEvent; - } else if (event->getType() == InputEventType::MOTION) { - MotionEvent& motionEvent = static_cast<MotionEvent&>(*event); - ADD_FAILURE() << "Received motion event " << motionEvent; - } else if (event->getType() == InputEventType::FOCUS) { - FocusEvent& focusEvent = static_cast<FocusEvent&>(*event); - ADD_FAILURE() << "Received focus event, hasFocus = " - << (focusEvent.getHasFocus() ? "true" : "false"); - } else if (event->getType() == InputEventType::CAPTURE) { - const auto& captureEvent = static_cast<CaptureEvent&>(*event); - ADD_FAILURE() << "Received capture event, pointerCaptureEnabled = " - << (captureEvent.getPointerCaptureEnabled() ? "true" : "false"); - } else if (event->getType() == InputEventType::TOUCH_MODE) { - const auto& touchModeEvent = static_cast<TouchModeEvent&>(*event); - ADD_FAILURE() << "Received touch mode event, inTouchMode = " - << (touchModeEvent.isInTouchMode() ? "true" : "false"); - } - FAIL() << mName.c_str() - << ": should not have received any events, so consume() should return NULL"; - } - - sp<IBinder> getToken() { return mConsumer.getChannel()->getConnectionToken(); } - - int getChannelFd() { return mConsumer.getChannel()->getFd(); } - -private: - InputConsumer mConsumer; - DynamicInputEventFactory mEventFactory; - - std::string mName; -}; - -class FakeWindowHandle : public WindowInfoHandle { -public: - static const int32_t WIDTH = 600; - static const int32_t HEIGHT = 800; - - FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle, - const std::unique_ptr<InputDispatcher>& dispatcher, const std::string name, - int32_t displayId, bool createInputChannel = true) - : mName(name) { - sp<IBinder> token; - if (createInputChannel) { - base::Result<std::unique_ptr<InputChannel>> channel = - dispatcher->createInputChannel(name); - token = (*channel)->getConnectionToken(); - mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name); - } - - inputApplicationHandle->updateInfo(); - mInfo.applicationInfo = *inputApplicationHandle->getInfo(); - - mInfo.token = token; - mInfo.id = sId++; - mInfo.name = name; - mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT; - mInfo.alpha = 1.0; - mInfo.frame = Rect(0, 0, WIDTH, HEIGHT); - mInfo.transform.set(0, 0); - mInfo.globalScaleFactor = 1.0; - mInfo.touchableRegion.clear(); - mInfo.addTouchableRegion(Rect(0, 0, WIDTH, HEIGHT)); - mInfo.ownerPid = WINDOW_PID; - mInfo.ownerUid = WINDOW_UID; - mInfo.displayId = displayId; - mInfo.inputConfig = WindowInfo::InputConfig::DEFAULT; - } - - sp<FakeWindowHandle> clone(int32_t displayId) { - sp<FakeWindowHandle> handle = sp<FakeWindowHandle>::make(mInfo.name + "(Mirror)"); - handle->mInfo = mInfo; - handle->mInfo.displayId = displayId; - handle->mInfo.id = sId++; - handle->mInputReceiver = mInputReceiver; - return handle; - } - - void setTouchable(bool touchable) { - mInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, !touchable); - } - - void setFocusable(bool focusable) { - mInfo.setInputConfig(WindowInfo::InputConfig::NOT_FOCUSABLE, !focusable); - } - - void setVisible(bool visible) { - mInfo.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !visible); - } - - void setDispatchingTimeout(std::chrono::nanoseconds timeout) { - mInfo.dispatchingTimeout = timeout; - } - - void setPaused(bool paused) { - mInfo.setInputConfig(WindowInfo::InputConfig::PAUSE_DISPATCHING, paused); - } - - void setPreventSplitting(bool preventSplitting) { - mInfo.setInputConfig(WindowInfo::InputConfig::PREVENT_SPLITTING, preventSplitting); - } - - void setSlippery(bool slippery) { - mInfo.setInputConfig(WindowInfo::InputConfig::SLIPPERY, slippery); - } - - void setWatchOutsideTouch(bool watchOutside) { - mInfo.setInputConfig(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH, watchOutside); - } - - void setSpy(bool spy) { mInfo.setInputConfig(WindowInfo::InputConfig::SPY, spy); } - - void setInterceptsStylus(bool interceptsStylus) { - mInfo.setInputConfig(WindowInfo::InputConfig::INTERCEPTS_STYLUS, interceptsStylus); - } - - void setDropInput(bool dropInput) { - mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT, dropInput); - } - - void setDropInputIfObscured(bool dropInputIfObscured) { - mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED, dropInputIfObscured); - } - - void setNoInputChannel(bool noInputChannel) { - mInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, noInputChannel); - } - - void setDisableUserActivity(bool disableUserActivity) { - 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; } - - void setApplicationToken(sp<IBinder> token) { mInfo.applicationInfo.token = token; } - - void setFrame(const Rect& frame, const ui::Transform& displayTransform = ui::Transform()) { - mInfo.frame = frame; - mInfo.touchableRegion.clear(); - mInfo.addTouchableRegion(frame); - - const Rect logicalDisplayFrame = displayTransform.transform(frame); - ui::Transform translate; - translate.set(-logicalDisplayFrame.left, -logicalDisplayFrame.top); - mInfo.transform = translate * displayTransform; - } - - void setTouchableRegion(const Region& region) { mInfo.touchableRegion = region; } - - void setIsWallpaper(bool isWallpaper) { - mInfo.setInputConfig(WindowInfo::InputConfig::IS_WALLPAPER, isWallpaper); - } - - void setDupTouchToWallpaper(bool hasWallpaper) { - mInfo.setInputConfig(WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER, hasWallpaper); - } - - void setTrustedOverlay(bool trustedOverlay) { - mInfo.setInputConfig(WindowInfo::InputConfig::TRUSTED_OVERLAY, trustedOverlay); - } - - void setWindowTransform(float dsdx, float dtdx, float dtdy, float dsdy) { - mInfo.transform.set(dsdx, dtdx, dtdy, dsdy); - } - - void setWindowScale(float xScale, float yScale) { setWindowTransform(xScale, 0, 0, yScale); } - - void setWindowOffset(float offsetX, float offsetY) { mInfo.transform.set(offsetX, offsetY); } - - std::unique_ptr<KeyEvent> consumeKey(bool handled = true) { - std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED, handled); - if (event == nullptr) { - ADD_FAILURE() << "No event"; - return nullptr; - } - if (event->getType() != InputEventType::KEY) { - ADD_FAILURE() << "Instead of key event, got " << event; - return nullptr; - } - return std::unique_ptr<KeyEvent>(static_cast<KeyEvent*>(event.release())); - } - - void consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) { - std::unique_ptr<KeyEvent> keyEvent = consumeKey(); - ASSERT_NE(nullptr, keyEvent); - ASSERT_THAT(*keyEvent, matcher); - } - - void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) { - consumeKeyEvent(AllOf(WithKeyAction(ACTION_DOWN), WithDisplayId(expectedDisplayId), - WithFlags(expectedFlags))); - } - - void consumeKeyUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) { - consumeKeyEvent(AllOf(WithKeyAction(ACTION_UP), WithDisplayId(expectedDisplayId), - WithFlags(expectedFlags))); - } - - void consumeMotionCancel(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, - int32_t expectedFlags = 0) { - consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(expectedDisplayId), - WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED))); - } - - void consumeMotionMove(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, - int32_t expectedFlags = 0) { - consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithDisplayId(expectedDisplayId), WithFlags(expectedFlags))); - } - - void consumeMotionDown(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, - int32_t expectedFlags = 0) { - consumeAnyMotionDown(expectedDisplayId, expectedFlags); - } - - void consumeAnyMotionDown(std::optional<int32_t> expectedDisplayId = std::nullopt, - std::optional<int32_t> expectedFlags = std::nullopt) { - 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) { - const int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN | - (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); - consumeMotionEvent(AllOf(WithMotionAction(action), WithDisplayId(expectedDisplayId), - WithFlags(expectedFlags))); - } - - void consumeMotionPointerUp(int32_t pointerIdx, int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, - int32_t expectedFlags = 0) { - const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP | - (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); - consumeMotionEvent(AllOf(WithMotionAction(action), WithDisplayId(expectedDisplayId), - WithFlags(expectedFlags))); - } - - void consumeMotionUp(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, - int32_t expectedFlags = 0) { - consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDisplayId(expectedDisplayId), - WithFlags(expectedFlags))); - } - - void consumeMotionOutside(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, - int32_t expectedFlags = 0) { - consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_OUTSIDE), - WithDisplayId(expectedDisplayId), WithFlags(expectedFlags))); - } - - void consumeMotionOutsideWithZeroedCoords() { - consumeMotionEvent( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_OUTSIDE), WithRawCoords(0, 0))); - } - - void consumeFocusEvent(bool hasFocus, bool inTouchMode = true) { - ASSERT_NE(mInputReceiver, nullptr) - << "Cannot consume events from a window with no receiver"; - mInputReceiver->consumeFocusEvent(hasFocus, inTouchMode); - } - - void consumeCaptureEvent(bool hasCapture) { - ASSERT_NE(mInputReceiver, nullptr) - << "Cannot consume events from a window with no receiver"; - mInputReceiver->consumeCaptureEvent(hasCapture); - } - - 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 consumeDragEvent(bool isExiting, float x, float y) { - mInputReceiver->consumeDragEvent(isExiting, x, y); - } - - void consumeTouchModeEvent(bool inTouchMode) { - ASSERT_NE(mInputReceiver, nullptr) - << "Cannot consume events from a window with no receiver"; - mInputReceiver->consumeTouchModeEvent(inTouchMode); - } - - std::pair<std::optional<uint32_t>, std::unique_ptr<InputEvent>> receiveEvent() { - return receive(); - } - - void finishEvent(uint32_t sequenceNum) { - ASSERT_NE(mInputReceiver, nullptr) << "Invalid receive event on window with no receiver"; - mInputReceiver->finishEvent(sequenceNum); - } - - void sendTimeline(int32_t inputEventId, std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) { - ASSERT_NE(mInputReceiver, nullptr) << "Invalid receive event on window with no receiver"; - mInputReceiver->sendTimeline(inputEventId, timeline); - } - - 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(timeout); - } - - sp<IBinder> getToken() { return mInfo.token; } - - const std::string& getName() { return mName; } - - void setOwnerInfo(gui::Pid ownerPid, gui::Uid ownerUid) { - mInfo.ownerPid = ownerPid; - mInfo.ownerUid = ownerUid; - } - - gui::Pid getPid() const { return mInfo.ownerPid; } - - void destroyReceiver() { mInputReceiver = nullptr; } - - 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}; class FakeMonitorReceiver { public: @@ -2363,7 +1293,9 @@ TEST_F(InputDispatcherTest, HoverEventInconsistentPolicy) { * This test reproduces a crash where there is a mismatch between the downTime and eventTime. * In the buggy implementation, a tap on the right window would cause a crash. */ -TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { +TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); @@ -2459,6 +1391,99 @@ TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { } /** + * Two windows: a window on the left and a window on the right. + * Mouse is hovered from the right window into the left window. + * Next, we tap on the left window, where the cursor was last seen. + * The second tap is done onto the right window. + * The mouse and tap are from two different devices. + * We technically don't need to set the downtime / eventtime for these events, but setting these + * explicitly helps during debugging. + * This test reproduces a crash where there is a mismatch between the downTime and eventTime. + * In the buggy implementation, a tap on the right window would cause a crash. + */ +TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> leftWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 200, 200)); + + sp<FakeWindowHandle> rightWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightWindow->setFrame(Rect(200, 0, 400, 200)); + + mDispatcher->onWindowInfosChanged( + {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0}); + // All times need to start at the current time, otherwise the dispatcher will drop the events as + // stale. + const nsecs_t baseTime = systemTime(SYSTEM_TIME_MONOTONIC); + const int32_t mouseDeviceId = 6; + const int32_t touchDeviceId = 4; + // Move the cursor from right + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .downTime(baseTime + 10) + .eventTime(baseTime + 20) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(100)) + .build()); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + + // .. to the left window + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .downTime(baseTime + 10) + .eventTime(baseTime + 30) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(100)) + .build()); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + // Now tap the left window + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .downTime(baseTime + 40) + .eventTime(baseTime + 40) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + + // release tap + mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .downTime(baseTime + 40) + .eventTime(baseTime + 50) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP)); + + // Tap the window on the right + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .downTime(baseTime + 60) + .eventTime(baseTime + 60) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + + // release tap + mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .downTime(baseTime + 60) + .eventTime(baseTime + 70) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP)); + + // No more events + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + +/** * Start hovering in a window. While this hover is still active, make another window appear on top. * The top, obstructing window has no input channel, so it's not supposed to receive input. * While the top window is present, the hovering is stopped. @@ -2606,6 +1631,7 @@ using InputDispatcherMultiDeviceTest = InputDispatcherTest; * touch is dropped, because stylus should be preferred over touch. */ TEST_F(InputDispatcherMultiDeviceTest, StylusDownBlocksTouchDown) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -2648,11 +1674,60 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusDownBlocksTouchDown) { } /** + * One window. Stylus down on the window. Next, touch from another device goes down. Ensure that + * touch is not dropped, because multiple devices are allowed to be active in the same window. + */ +TEST_F(InputDispatcherMultiDeviceTest, StylusDownDoesNotBlockTouchDown) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + constexpr int32_t touchDeviceId = 4; + constexpr int32_t stylusDeviceId = 2; + + // Stylus down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId))); + + // Touch down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + + // Touch move + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + + // Stylus move + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId), + WithCoords(101, 111))); + + window->assertNoEvents(); +} + +/** * One window and one spy window. Stylus down on the window. Next, touch from another device goes * down. Ensure that touch is dropped, because stylus should be preferred over touch. * Similar test as above, but with added SPY window. */ TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyBlocksTouchDown) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -2706,10 +1781,74 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyBlocksTouchDown) { } /** + * One window and one spy window. Stylus down on the window. Next, touch from another device goes + * down. Ensure that touch is not dropped, because multiple devices can be active at the same time. + * Similar test as above, but with added SPY window. + */ +TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyDoesNotBlockTouchDown) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp<FakeWindowHandle> spyWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 200, 200)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0}); + + constexpr int32_t touchDeviceId = 4; + constexpr int32_t stylusDeviceId = 2; + + // Stylus down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId))); + + // Touch down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + + // Touch move + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + + // Subsequent stylus movements are delivered correctly + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId), + WithCoords(101, 111))); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId), + WithCoords(101, 111))); + + window->assertNoEvents(); + spyWindow->assertNoEvents(); +} + +/** * One window. Stylus hover on the window. Next, touch from another device goes down. Ensure that * touch is dropped, because stylus hover takes precedence. */ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverBlocksTouchDown) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -2757,10 +1896,65 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverBlocksTouchDown) { } /** + * One window. Stylus hover on the window. Next, touch from another device goes down. Ensure that + * touch is not dropped, because stylus hover and touch can be both active at the same time. + */ +TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDoesNotBlockTouchDown) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + constexpr int32_t touchDeviceId = 4; + constexpr int32_t stylusDeviceId = 2; + + // Stylus down on the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110)) + .build()); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId))); + + // Touch down on window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145)) + .build()); + // Touch move on window + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + + // Subsequent stylus movements are delivered correctly + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), + WithDeviceId(stylusDeviceId), WithCoords(101, 111))); + + // and subsequent touches continue to work + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + window->assertNoEvents(); +} + +/** * One window. Touch down on the window. Then, stylus hover on the window from another device. * Ensure that touch is canceled, because stylus hover should take precedence. */ TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusHover) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -2810,11 +2004,66 @@ TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusHover) { } /** + * One window. Touch down on the window. Then, stylus hover on the window from another device. + * Ensure that touch is not canceled, because stylus hover can be active at the same time as touch. + */ +TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusHover) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + constexpr int32_t touchDeviceId = 4; + constexpr int32_t stylusDeviceId = 2; + + // Touch down on window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145)) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + + // Stylus hover on the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110)) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111)) + .build()); + // Stylus hover movement is received normally + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER), + WithDeviceId(stylusDeviceId), WithCoords(100, 110))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), + WithDeviceId(stylusDeviceId), WithCoords(101, 111))); + + // Subsequent touch movements also work + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId), + WithCoords(142, 147))); + + window->assertNoEvents(); +} + +/** * One window. Stylus down on the window. Then, stylus from another device goes down. Ensure that * the latest stylus takes over. That is, old stylus should be canceled and the new stylus should * become active. */ TEST_F(InputDispatcherMultiDeviceTest, LatestStylusWins) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -2862,10 +2111,59 @@ TEST_F(InputDispatcherMultiDeviceTest, LatestStylusWins) { } /** + * One window. Stylus down on the window. Then, stylus from another device goes down. Ensure that + * both stylus devices can function simultaneously. + */ +TEST_F(InputDispatcherMultiDeviceTest, TwoStylusDevicesActiveAtTheSameTime) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + constexpr int32_t stylusDeviceId1 = 3; + constexpr int32_t stylusDeviceId2 = 5; + + // Touch down on window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId1) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(99).y(100)) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId1) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(101)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId1))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId1))); + + // Second stylus down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId2) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(9).y(10)) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId2) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(10).y(11)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId2))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId2))); + + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId1) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(102)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId1))); + window->assertNoEvents(); +} + +/** * One window. Touch down on the window. Then, stylus down on the window from another device. * Ensure that is canceled, because stylus down should be preferred over touch. */ TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusDown) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -2906,13 +2204,65 @@ TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusDown) { } /** + * One window. Touch down on the window. Then, stylus down on the window from another device. + * Ensure that both touch and stylus are functioning independently. + */ +TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusDown) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + constexpr int32_t touchDeviceId = 4; + constexpr int32_t stylusDeviceId = 2; + + // Touch down on window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145)) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + + // Stylus down on the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId))); + + // Subsequent stylus movements are delivered correctly + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId), + WithCoords(101, 111))); + + // Touch continues to work too + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(148).y(149)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); +} + +/** * Two windows: a window on the left and a window on the right. * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains * down. Then, on the left window, also place second touch pointer down. * This test tries to reproduce a crash. * In the buggy implementation, second pointer down on the left window would cause a crash. */ -TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch) { +TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); @@ -2991,6 +2341,88 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch) { /** * Two windows: a window on the left and a window on the right. + * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains + * down. Then, on the left window, also place second touch pointer down. + * This test tries to reproduce a crash. + * In the buggy implementation, second pointer down on the left window would cause a crash. + */ +TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> leftWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 200, 200)); + + sp<FakeWindowHandle> rightWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightWindow->setFrame(Rect(200, 0, 400, 200)); + + mDispatcher->onWindowInfosChanged( + {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0}); + + const int32_t touchDeviceId = 4; + const int32_t mouseDeviceId = 6; + + // Start hovering over the left window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + + // Mouse down on left window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId))); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // First touch pointer down on right window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + leftWindow->assertNoEvents(); + + rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + // Second touch pointer down on left window + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100)) + .build()); + // Since this is now a new splittable pointer going down on the left window, and it's coming + // from a different device, it will be split and delivered to left window separately. + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + // This MOVE event is not necessary (doesn't carry any new information), but it's there in the + // current implementation. + const std::map<int32_t, PointF> expectedPointers{{0, PointF{100, 100}}}; + rightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithPointers(expectedPointers))); + + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + +/** + * Two windows: a window on the left and a window on the right. * Mouse is hovered on the left window and stylus is hovered on the right window. */ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHover) { @@ -3050,7 +2482,8 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHover) { * Stylus down on the left window and remains down. Touch goes down on the right and remains down. * Check the stream that's received by the spy. */ -TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) { +TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> spyWindow = @@ -3120,6 +2553,83 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) { } /** + * Three windows: a window on the left and a window on the right. + * And a spy window that's positioned above all of them. + * Stylus down on the left window and remains down. Touch goes down on the right and remains down. + * Check the stream that's received by the spy. + */ +TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + + sp<FakeWindowHandle> spyWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 400, 400)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + + sp<FakeWindowHandle> leftWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 200, 200)); + + sp<FakeWindowHandle> rightWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + + rightWindow->setFrame(Rect(200, 0, 400, 200)); + + mDispatcher->onWindowInfosChanged( + {{*spyWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0}); + + const int32_t stylusDeviceId = 1; + const int32_t touchDeviceId = 2; + + // Stylus down on the left window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId))); + + // Touch down on the right window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + leftWindow->assertNoEvents(); + rightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + + // Stylus movements continue. They should be delivered to the left window and to the spy window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110)) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId))); + + // Further touch MOVE events keep going to the right window and to the spy + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(310).y(110)) + .build()); + rightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + + spyWindow->assertNoEvents(); + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + +/** * Three windows: a window on the left, a window on the right, and a spy window positioned above * both. * Check hover in left window and touch down in the right window. @@ -3128,6 +2638,7 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) { * respectively. */ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlocksTouchWithSpy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> spyWindow = @@ -3195,6 +2706,84 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlocksTouchWithSpy) { } /** + * Three windows: a window on the left, a window on the right, and a spy window positioned above + * both. + * Check hover in left window and touch down in the right window. + * At first, spy should receive hover. Next, spy should receive touch. + * At the same time, left and right should be getting independent streams of hovering and touch, + * respectively. + */ +TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverDoesNotBlockTouchWithSpy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + + sp<FakeWindowHandle> spyWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 400, 400)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + + sp<FakeWindowHandle> leftWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 200, 200)); + + sp<FakeWindowHandle> rightWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightWindow->setFrame(Rect(200, 0, 400, 200)); + + mDispatcher->onWindowInfosChanged( + {{*spyWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0}); + + const int32_t stylusDeviceId = 1; + const int32_t touchDeviceId = 2; + + // Stylus hover on the left window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId))); + + // Touch down on the right window. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + leftWindow->assertNoEvents(); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + rightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + + // Stylus movements continue. They should be delivered to the left window and the spy. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110)) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId))); + + // Touch movements continue. They should be delivered to the right window and the spy + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(301).y(101)) + .build()); + rightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + + spyWindow->assertNoEvents(); + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + +/** * On a single window, use two different devices: mouse and touch. * Touch happens first, with two pointers going down, and then the first pointer leaving. * Mouse is clicked next, which causes the touch stream to be aborted with ACTION_CANCEL. @@ -3202,7 +2791,8 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlocksTouchWithSpy) { * because the mouse is currently down, and a POINTER_DOWN event from the touchscreen does not * represent a new gesture. */ -TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown) { +TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -3275,10 +2865,89 @@ TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown) { } /** + * On a single window, use two different devices: mouse and touch. + * Touch happens first, with two pointers going down, and then the first pointer leaving. + * Mouse is clicked next, which should not interfere with the touch stream. + * Finally, a second touch pointer goes down again. Ensure the second touch pointer is also + * delivered correctly. + */ +TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 400, 400)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + const int32_t touchDeviceId = 4; + const int32_t mouseDeviceId = 6; + + // First touch pointer down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + // Second touch pointer down + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100)) + .build()); + // First touch pointer lifts. The second one remains down + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_0_UP)); + + // Mouse down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(320).y(100)) + .build()); + + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(320).y(100)) + .build()); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // Second touch pointer down. + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(POINTER_0_DOWN), WithDeviceId(touchDeviceId), + WithPointerCount(2u))); + + // Mouse movements should continue to work + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(330).y(110)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(mouseDeviceId))); + + window->assertNoEvents(); +} + +/** * Inject a touch down and then send a new event via 'notifyMotion'. Ensure the new event cancels * the injected event. */ -TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent) { +TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -3311,6 +2980,40 @@ TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent) { } /** + * Inject a touch down and then send a new event via 'notifyMotion'. Ensure the new event runs + * parallel to the injected event. + */ +TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 400, 400)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + const int32_t touchDeviceId = 4; + // Pretend a test injects an ACTION_DOWN mouse event, but forgets to lift up the touch after + // completion. + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(*mDispatcher, + MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50)) + .build())); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(VIRTUAL_KEYBOARD_ID))); + + // Now a real touch comes. The injected pointer will remain, and the new gesture will also be + // allowed through. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); +} + +/** * This test is similar to the test above, but the sequence of injected events is different. * * Two windows: a window on the left and a window on the right. @@ -3324,7 +3027,8 @@ TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent) { * This test reproduces a crash where there is a mismatch between the downTime and eventTime. * In the buggy implementation, second finger down on the left window would cause a crash. */ -TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch) { +TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); @@ -3396,11 +3100,88 @@ TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch) { } /** + * This test is similar to the test above, but the sequence of injected events is different. + * + * Two windows: a window on the left and a window on the right. + * Mouse is hovered over the left window. + * Next, we tap on the left window, where the cursor was last seen. + * + * After that, we send one finger down onto the right window, and then a second finger down onto + * the left window. + * The touch is split, so this last gesture should cause 2 ACTION_DOWN events, one in the right + * window (first), and then another on the left window (second). + * This test reproduces a crash where there is a mismatch between the downTime and eventTime. + * In the buggy implementation, second finger down on the left window would cause a crash. + */ +TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> leftWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 200, 200)); + + sp<FakeWindowHandle> rightWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightWindow->setFrame(Rect(200, 0, 400, 200)); + + mDispatcher->onWindowInfosChanged( + {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0}); + + const int32_t mouseDeviceId = 6; + const int32_t touchDeviceId = 4; + // Hover over the left window. Keep the cursor there. + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50)) + .build()); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + + // Tap on left window + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + + mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithDeviceId(touchDeviceId))); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithDeviceId(touchDeviceId))); + + // First finger down on right window + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + + // Second finger down on the left window + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_MOVE)); + + // No more events + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + +/** * Start hovering with a stylus device, and then tap with a touch device. Ensure no crash occurs. * While the touch is down, new hover events from the stylus device should be ignored. After the * touch is gone, stylus hovering should start working again. */ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverIgnoresTouchTap) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -3462,6 +3243,61 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverIgnoresTouchTap) { } /** + * Start hovering with a stylus device, and then tap with a touch device. Ensure no crash occurs. + * While the touch is down, hovering from the stylus is not affected. After the touch is gone, + * check that the stylus hovering continues to work. + */ +TEST_F(InputDispatcherMultiDeviceTest, StylusHoverWithTouchTap) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + const int32_t stylusDeviceId = 5; + const int32_t touchDeviceId = 4; + // Start hovering with stylus + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + + // Finger down on the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + + // Continue hovering with stylus. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(60).y(60)) + .build()); + // Hovers continue to work + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId))); + + // Lift up the finger + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDeviceId(touchDeviceId))); + + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(70).y(70)) + .build()); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId))); + window->assertNoEvents(); +} + +/** * If stylus is down anywhere on the screen, then touches should not be delivered to windows that * have InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH. * @@ -3707,7 +3543,8 @@ TEST_F(InputDispatcherTest, StaleStylusHoverGestureIsComplete) { * ACTION_DOWN event because that's a new gesture, and pilfering should no longer be active. * While the mouse is down, new move events from the touch device should be ignored. */ -TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { +TEST_F(InputDispatcherTest, TouchPilferAndMouseMove_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); @@ -3804,6 +3641,114 @@ TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { } /** + * Start hovering with a mouse, and then tap with a touch device. Pilfer the touch stream. + * Next, click with the mouse device. Both windows (spy and regular) should receive the new mouse + * ACTION_DOWN event because that's a new gesture, and pilfering should no longer be active. + * While the mouse is down, new move events from the touch device should continue to work. + */ +TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> spyWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 200, 200)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0}); + + const int32_t mouseDeviceId = 7; + const int32_t touchDeviceId = 4; + + // Hover a bit with mouse first + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + + // Start touching + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .build()); + + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(55).y(55)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + window->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + + // Pilfer the stream + EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken())); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId))); + // Hover is not pilfered! Only touch. + + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(60).y(60)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + + // Mouse down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // Mouse move! + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110)) + .build()); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(mouseDeviceId))); + + // Touch move! + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(65).y(65)) + .build()); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + + // No more events + spyWindow->assertNoEvents(); + window->assertNoEvents(); +} + +/** * On the display, have a single window, and also an area where there's no window. * First pointer touches the "no window" area of the screen. Second pointer touches the window. * Make sure that the window receives the second pointer, and first pointer is simply ignored. @@ -4016,7 +3961,8 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { * The clicking of mouse is a new ACTION_DOWN event. Since it's from a different device, the * currently active gesture should be canceled, and the new one should proceed. */ -TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) { +TEST_F(InputDispatcherTest, TwoPointersDownMouseClick_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -4070,6 +4016,65 @@ TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) { window->assertNoEvents(); } +/** + * Put two fingers down (and don't release them) and click the mouse button. + * The clicking of mouse is a new ACTION_DOWN event. Since it's from a different device, the + * currently active gesture should not be canceled, and the new one should proceed in parallel. + */ +TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 600, 800)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + const int32_t touchDeviceId = 4; + const int32_t mouseDeviceId = 6; + + // Two pointers down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + + // Send a series of mouse events for a mouse click + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) + .build()); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // Try to send more touch events while the mouse is down. Since it's a continuation of an + // already active gesture, it should be sent normally. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(101).y(101)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(121).y(121)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + window->assertNoEvents(); +} + TEST_F(InputDispatcherTest, HoverWithSpyWindows) { std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); @@ -4102,7 +4107,8 @@ TEST_F(InputDispatcherTest, HoverWithSpyWindows) { spyWindow->assertNoEvents(); } -TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) { +TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> spyWindow = @@ -4208,6 +4214,102 @@ TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) { spyWindow->assertNoEvents(); } +TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + + sp<FakeWindowHandle> spyWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 600, 800)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 600, 800)); + + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0}); + + // Send mouse cursor to the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + + // Move mouse cursor + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110)) + .build()); + + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithSource(AINPUT_SOURCE_MOUSE))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithSource(AINPUT_SOURCE_MOUSE))); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE))); + // Touch down on the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(200)) + .build()); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + // pilfer the motion, retaining the gesture on the spy window. + EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken())); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + // Mouse hover is not pilfered + + // Touch UP on the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(200)) + .build()); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_UP), WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + // Previously, a touch was pilfered. However, that gesture was just finished. Now, we are going + // to send a new gesture. It should again go to both windows (spy and the window below), just + // like the first gesture did, before pilfering. The window configuration has not changed. + + // One more tap - DOWN + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(250)) + .build()); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + // Touch UP on the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(250)) + .build()); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_UP), WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_UP), WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + // Mouse movement continues normally as well + // Move mouse cursor + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(120).y(130)) + .build()); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE))); + + window->assertNoEvents(); + spyWindow->assertNoEvents(); +} + // This test is different from the test above that HOVER_ENTER and HOVER_EXIT events are injected // directly in this test. TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { @@ -4333,7 +4435,8 @@ TEST_F_WITH_FLAGS(InputDispatcherTest, InvalidA11yHoverStreamDoesNotCrash, /** * If mouse is hovering when the touch goes down, the hovering should be stopped via HOVER_EXIT. */ -TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) { +TEST_F(InputDispatcherTest, TouchDownAfterMouseHover_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -4364,11 +4467,43 @@ TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) { } /** + * If mouse is hovering when the touch goes down, the hovering should not be stopped. + */ +TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 100, 100)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + const int32_t mouseDeviceId = 7; + const int32_t touchDeviceId = 4; + + // Start hovering with the mouse + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(10).y(10)) + .build()); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + + // Touch goes down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); +} + +/** * Inject a mouse hover event followed by a tap from touchscreen. * The tap causes a HOVER_EXIT event to be generated because the current event * stream's source has been switched. */ -TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) { +TEST_F(InputDispatcherTest, MouseHoverAndTouchTap_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); @@ -4402,6 +4537,45 @@ TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) { WithSource(AINPUT_SOURCE_TOUCHSCREEN)))); } +/** + * Send a mouse hover event followed by a tap from touchscreen. + * The tap causes a HOVER_EXIT event to be generated because the current event + * stream's source has been switched. + */ +TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 100, 100)); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50)) + .build()); + + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithSource(AINPUT_SOURCE_MOUSE))); + + // Tap on the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(10).y(10)) + .build()); + + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithSource(AINPUT_SOURCE_MOUSE))); + + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(10).y(10)) + .build()); + + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); +} + TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) { std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> windowDefaultDisplay = @@ -12007,7 +12181,8 @@ TEST_F(InputDispatcherPilferPointersTest, CanReceivePointersAfterPilfer) { * Pilfer from spy window. * Check that the pilfering only affects the pointers that are actually being received by the spy. */ -TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer) { +TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); sp<FakeWindowHandle> spy = createSpy(); spy->setFrame(Rect(0, 0, 200, 200)); sp<FakeWindowHandle> leftWindow = createForeground(); @@ -12065,6 +12240,83 @@ TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer) { rightWindow->assertNoEvents(); } +/** + * A window on the left and a window on the right. Also, a spy window that's above all of the + * windows, and spanning both left and right windows. + * Send simultaneous motion streams from two different devices, one to the left window, and another + * to the right window. + * Pilfer from spy window. + * Check that the pilfering affects all of the pointers that are actually being received by the spy. + * The spy should receive both the touch and the stylus events after pilfer. + */ +TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + sp<FakeWindowHandle> spy = createSpy(); + spy->setFrame(Rect(0, 0, 200, 200)); + sp<FakeWindowHandle> leftWindow = createForeground(); + leftWindow->setFrame(Rect(0, 0, 100, 100)); + + sp<FakeWindowHandle> rightWindow = createForeground(); + rightWindow->setFrame(Rect(100, 0, 200, 100)); + + constexpr int32_t stylusDeviceId = 1; + constexpr int32_t touchDeviceId = 2; + + mDispatcher->onWindowInfosChanged( + {{*spy->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0}); + + // Stylus down on left window and spy + mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId))); + spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId))); + + // Finger down on right window and spy + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50)) + .build()); + rightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + + // Act: pilfer from spy. Spy is currently receiving touch events. + EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken())); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId))); + rightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId))); + + // Continue movements from both stylus and touch. Touch and stylus will be delivered to spy + // Instead of sending the two MOVE events for each input device together, and then receiving + // them both, process them one at at time. InputConsumer is always in the batching mode, which + // means that the two MOVE events will be initially put into a batch. Once the events are + // batched, the 'consume' call may result in any of the MOVE events to be sent first (depending + // on the implementation of InputConsumer), which would mean that the order of the received + // events could be different depending on whether there are 1 or 2 events pending in the + // InputChannel at the time the test calls 'consume'. To make assertions simpler here, and to + // avoid this confusing behaviour, send and receive each MOVE event separately. + mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(52)) + .build()); + spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId))); + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(52)) + .build()); + spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId))); + + spy->assertNoEvents(); + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + TEST_F(InputDispatcherPilferPointersTest, NoPilferingWithHoveringPointers) { auto window = createForeground(); auto spy = createSpy(); @@ -12529,7 +12781,8 @@ TEST_F(InputDispatcherPointerInWindowTest, PointerInWindowWithSplitTouch) { /*pointerId=*/0)); } -TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) { +TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse_legacy) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window", @@ -12599,4 +12852,76 @@ TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) { /*pointerId=*/0)); } +/** + * TODO(b/313689709) - correctly support multiple mouse devices, because they should be controlling + * the same cursor, and therefore have a shared motion event stream. + */ +TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + + sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window", + ADISPLAY_ID_DEFAULT); + left->setFrame(Rect(0, 0, 100, 100)); + sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher, + "Right Window", ADISPLAY_ID_DEFAULT); + right->setFrame(Rect(100, 0, 200, 100)); + + mDispatcher->onWindowInfosChanged({{*left->getInfo(), *right->getInfo()}, {}, 0, 0}); + + ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + /*pointerId=*/0)); + ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + /*pointerId=*/0)); + + // Hover move into the window. + mDispatcher->notifyMotion( + MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(50).y(50)) + .rawXCursorPosition(50) + .rawYCursorPosition(50) + .deviceId(DEVICE_ID) + .build()); + + left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + /*pointerId=*/0)); + + // Move the mouse with another device + mDispatcher->notifyMotion( + MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(51).y(50)) + .rawXCursorPosition(51) + .rawYCursorPosition(50) + .deviceId(SECOND_DEVICE_ID) + .build()); + left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + + // TODO(b/313689709): InputDispatcher's touch state is not updated, even though the window gets + // a HOVER_EXIT from the first device. + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + /*pointerId=*/0)); + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, + SECOND_DEVICE_ID, + /*pointerId=*/0)); + + // Move the mouse outside the window. Document the current behavior, where the window does not + // receive HOVER_EXIT even though the mouse left the window. + mDispatcher->notifyMotion( + MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(150).y(50)) + .rawXCursorPosition(150) + .rawYCursorPosition(50) + .deviceId(SECOND_DEVICE_ID) + .build()); + + right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + /*pointerId=*/0)); + ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, + SECOND_DEVICE_ID, + /*pointerId=*/0)); +} + } // namespace android::inputdispatcher diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 1d46c9a1e2..367bc70a1c 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -9790,163 +9790,13 @@ TEST_F(MultiTouchInputMapperTest_ExternalDevice, Viewports_Fallback) { ASSERT_EQ(SECONDARY_DISPLAY_ID, motionArgs.displayId); } -TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) { - // we need a pointer controller for mouse mode of touchpad (start pointer at 0,0) +TEST_F(MultiTouchInputMapperTest, Process_TouchpadPointer) { std::shared_ptr<FakePointerController> fakePointerController = std::make_shared<FakePointerController>(); fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); fakePointerController->setPosition(0, 0); - // prepare device and capture - prepareDisplay(ui::ROTATION_0); - prepareAxes(POSITION | ID | SLOT); - mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0); - mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0); - mFakePolicy->setPointerCapture(/*window=*/sp<BBinder>::make()); - mFakePolicy->setPointerController(fakePointerController); - MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>(); - - // captured touchpad should be a touchpad source - NotifyDeviceResetArgs resetArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); - - InputDeviceInfo deviceInfo = mDevice->getDeviceInfo(); - - const InputDeviceInfo::MotionRange* relRangeX = - deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, AINPUT_SOURCE_TOUCHPAD); - ASSERT_NE(relRangeX, nullptr); - ASSERT_EQ(relRangeX->min, -(RAW_X_MAX - RAW_X_MIN)); - ASSERT_EQ(relRangeX->max, RAW_X_MAX - RAW_X_MIN); - const InputDeviceInfo::MotionRange* relRangeY = - deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, AINPUT_SOURCE_TOUCHPAD); - ASSERT_NE(relRangeY, nullptr); - ASSERT_EQ(relRangeY->min, -(RAW_Y_MAX - RAW_Y_MIN)); - ASSERT_EQ(relRangeY->max, RAW_Y_MAX - RAW_Y_MIN); - - // run captured pointer tests - note that this is unscaled, so input listener events should be - // identical to what the hardware sends (accounting for any - // calibration). - // FINGER 0 DOWN - processSlot(mapper, 0); - processId(mapper, 1); - processPosition(mapper, 100 + RAW_X_MIN, 100 + RAW_Y_MIN); - processKey(mapper, BTN_TOUCH, 1); - processSync(mapper); - - // expect coord[0] to contain initial location of touch 0 - NotifyMotionArgs args; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); - ASSERT_EQ(1U, args.getPointerCount()); - ASSERT_EQ(0, args.pointerProperties[0].id); - ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, args.source); - ASSERT_NO_FATAL_FAILURE( - assertPointerCoords(args.pointerCoords[0], 100, 100, 1, 0, 0, 0, 0, 0, 0, 0)); - - // FINGER 1 DOWN - processSlot(mapper, 1); - processId(mapper, 2); - processPosition(mapper, 560 + RAW_X_MIN, 154 + RAW_Y_MIN); - processSync(mapper); - - // expect coord[0] to contain previous location, coord[1] to contain new touch 1 location - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(ACTION_POINTER_1_DOWN, args.action); - ASSERT_EQ(2U, args.getPointerCount()); - ASSERT_EQ(0, args.pointerProperties[0].id); - ASSERT_EQ(1, args.pointerProperties[1].id); - ASSERT_NO_FATAL_FAILURE( - assertPointerCoords(args.pointerCoords[0], 100, 100, 1, 0, 0, 0, 0, 0, 0, 0)); - ASSERT_NO_FATAL_FAILURE( - assertPointerCoords(args.pointerCoords[1], 560, 154, 1, 0, 0, 0, 0, 0, 0, 0)); - - // FINGER 1 MOVE - processPosition(mapper, 540 + RAW_X_MIN, 690 + RAW_Y_MIN); - processSync(mapper); - - // expect coord[0] to contain previous location, coord[1] to contain new touch 1 location - // from move - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE( - assertPointerCoords(args.pointerCoords[0], 100, 100, 1, 0, 0, 0, 0, 0, 0, 0)); - ASSERT_NO_FATAL_FAILURE( - assertPointerCoords(args.pointerCoords[1], 540, 690, 1, 0, 0, 0, 0, 0, 0, 0)); - - // FINGER 0 MOVE - processSlot(mapper, 0); - processPosition(mapper, 50 + RAW_X_MIN, 800 + RAW_Y_MIN); - processSync(mapper); - - // expect coord[0] to contain new touch 0 location, coord[1] to contain previous location - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE( - assertPointerCoords(args.pointerCoords[0], 50, 800, 1, 0, 0, 0, 0, 0, 0, 0)); - ASSERT_NO_FATAL_FAILURE( - assertPointerCoords(args.pointerCoords[1], 540, 690, 1, 0, 0, 0, 0, 0, 0, 0)); - - // BUTTON DOWN - processKey(mapper, BTN_LEFT, 1); - processSync(mapper); - - // touchinputmapper design sends a move before button press - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action); - - // BUTTON UP - processKey(mapper, BTN_LEFT, 0); - processSync(mapper); - - // touchinputmapper design sends a move after button release - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - - // FINGER 0 UP - processId(mapper, -1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | 0x0000, args.action); - - // FINGER 1 MOVE - processSlot(mapper, 1); - processPosition(mapper, 320 + RAW_X_MIN, 900 + RAW_Y_MIN); - processSync(mapper); - - // expect coord[0] to contain new location of touch 1, and properties[0].id to contain 1 - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_EQ(1U, args.getPointerCount()); - ASSERT_EQ(1, args.pointerProperties[0].id); - ASSERT_NO_FATAL_FAILURE( - assertPointerCoords(args.pointerCoords[0], 320, 900, 1, 0, 0, 0, 0, 0, 0, 0)); - - // FINGER 1 UP - processId(mapper, -1); - processKey(mapper, BTN_TOUCH, 0); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); - - // non captured touchpad should be a mouse source - mFakePolicy->setPointerCapture(/*window=*/nullptr); - configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); -} - -TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) { - std::shared_ptr<FakePointerController> fakePointerController = - std::make_shared<FakePointerController>(); - fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); - fakePointerController->setPosition(0, 0); - - // prepare device and capture + // prepare device prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT); mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0); @@ -10004,7 +9854,7 @@ TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) { ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); } -TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) { +TEST_F(MultiTouchInputMapperTest, Touchpad_GetSources) { std::shared_ptr<FakePointerController> fakePointerController = std::make_shared<FakePointerController>(); @@ -10017,11 +9867,6 @@ TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) { // uncaptured touchpad should be a pointer device ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); - - // captured touchpad should be a touchpad device - mFakePolicy->setPointerCapture(/*window=*/sp<BBinder>::make()); - configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE); - ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); } // --- BluetoothMultiTouchInputMapperTest --- diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp index 3b2565e973..7d1b23cf9f 100644 --- a/services/inputflinger/tests/PointerChoreographer_test.cpp +++ b/services/inputflinger/tests/PointerChoreographer_test.cpp @@ -1772,4 +1772,107 @@ TEST_P(StylusTestFixture, SetPointerIconVisibilityHidesPointerForStylus) { ASSERT_FALSE(pc->isPointerShown()); } +TEST_F(PointerChoreographerTest, DrawingTabletCanReportMouseEvent) { + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); + + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE)}}); + // There should be no controller created when a drawing tablet is connected + assertPointerControllerNotCreated(); + + // But if it ends up reporting a mouse event, then the mouse controller will be created + // dynamically. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .pointer(MOUSE_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_TRUE(pc->isPointerShown()); + + // The controller is removed when the drawing tablet is removed + mChoreographer.notifyInputDevicesChanged({/*id=*/0, {}}); + assertPointerControllerRemoved(pc); +} + +TEST_F(PointerChoreographerTest, MultipleDrawingTabletsReportMouseEvents) { + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); + + // First drawing tablet is added + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE)}}); + assertPointerControllerNotCreated(); + + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .pointer(MOUSE_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_TRUE(pc->isPointerShown()); + + // Second drawing tablet is added + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE), + generateTestDeviceInfo(SECOND_DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE)}}); + assertPointerControllerNotRemoved(pc); + + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .pointer(MOUSE_POINTER) + .deviceId(SECOND_DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + + // First drawing tablet is removed + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE)}}); + assertPointerControllerNotRemoved(pc); + + // Second drawing tablet is removed + mChoreographer.notifyInputDevicesChanged({/*id=*/0, {}}); + assertPointerControllerRemoved(pc); +} + +TEST_F(PointerChoreographerTest, MouseAndDrawingTabletReportMouseEvents) { + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); + + // Mouse and drawing tablet connected + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE), + generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + auto pc = assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_TRUE(pc->isPointerShown()); + + // Drawing tablet reports a mouse event + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, DRAWING_TABLET_SOURCE) + .pointer(MOUSE_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + + // Remove the mouse device + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE)}}); + + // The mouse controller should not be removed, because the drawing tablet has produced a + // mouse event, so we are treating it as a mouse too. + assertPointerControllerNotRemoved(pc); + + mChoreographer.notifyInputDevicesChanged({/*id=*/0, {}}); + assertPointerControllerRemoved(pc); +} + } // namespace android diff --git a/services/inputflinger/tests/fuzzers/Android.bp b/services/inputflinger/tests/fuzzers/Android.bp index 81c335326a..48e19546c5 100644 --- a/services/inputflinger/tests/fuzzers/Android.bp +++ b/services/inputflinger/tests/fuzzers/Android.bp @@ -178,7 +178,12 @@ cc_fuzz { shared_libs: [ "libinputreporter", ], + static_libs: [ + "libgmock", + "libgtest", + ], srcs: [ + ":inputdispatcher_common_test_sources", "InputDispatcherFuzzer.cpp", ], } diff --git a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp index dc5a2130e7..7335fb7500 100644 --- a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp @@ -18,7 +18,7 @@ #include <fuzzer/FuzzedDataProvider.h> #include "../FakeApplicationHandle.h" #include "../FakeInputDispatcherPolicy.h" -#include "../FakeWindowHandle.h" +#include "../FakeWindows.h" #include "FuzzedInputStream.h" #include "dispatcher/InputDispatcher.h" #include "input/InputVerifier.h" @@ -88,7 +88,8 @@ void scrambleWindow(FuzzedDataProvider& fdp, FakeWindowHandle& window) { } // namespace -sp<FakeWindowHandle> generateFuzzedWindow(FuzzedDataProvider& fdp, InputDispatcher& dispatcher, +sp<FakeWindowHandle> generateFuzzedWindow(FuzzedDataProvider& fdp, + std::unique_ptr<InputDispatcher>& dispatcher, int32_t displayId) { static size_t windowNumber = 0; std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); @@ -102,7 +103,7 @@ sp<FakeWindowHandle> generateFuzzedWindow(FuzzedDataProvider& fdp, InputDispatch void randomizeWindows( std::unordered_map<int32_t, std::vector<sp<FakeWindowHandle>>>& windowsPerDisplay, - FuzzedDataProvider& fdp, InputDispatcher& dispatcher) { + FuzzedDataProvider& fdp, std::unique_ptr<InputDispatcher>& dispatcher) { const int32_t displayId = fdp.ConsumeIntegralInRange<int32_t>(0, MAX_RANDOM_DISPLAYS - 1); std::vector<sp<FakeWindowHandle>>& windows = windowsPerDisplay[displayId]; @@ -142,10 +143,10 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { NotifyStreamProvider streamProvider(fdp); FakeInputDispatcherPolicy fakePolicy; - InputDispatcher dispatcher(fakePolicy); - dispatcher.setInputDispatchMode(/*enabled=*/true, /*frozen=*/false); + auto dispatcher = std::make_unique<InputDispatcher>(fakePolicy); + dispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false); // Start InputDispatcher thread - dispatcher.start(); + dispatcher->start(); std::unordered_map<int32_t, std::vector<sp<FakeWindowHandle>>> windowsPerDisplay; @@ -155,7 +156,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { [&]() -> void { std::optional<NotifyMotionArgs> motion = streamProvider.nextMotion(); if (motion) { - dispatcher.notifyMotion(*motion); + dispatcher->notifyMotion(*motion); } }, [&]() -> void { @@ -169,7 +170,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { } } - dispatcher.onWindowInfosChanged( + dispatcher->onWindowInfosChanged( {windowInfos, {}, /*vsyncId=*/0, /*timestamp=*/0}); }, // Consume on all the windows @@ -187,7 +188,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { })(); } - dispatcher.stop(); + dispatcher->stop(); return 0; } diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h index e2d17ee502..86bcf20677 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h @@ -143,7 +143,7 @@ public: compositionengine::OutputLayer* getBlurLayer() const; - bool hasUnsupportedDataspace() const; + bool hasKnownColorShift() const; bool hasProtectedLayers() const; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h index dc3821ca43..5e3e3d8a31 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h @@ -74,6 +74,7 @@ enum class LayerStateField : uint32_t { BlurRegions = 1u << 18, HasProtectedContent = 1u << 19, CachingHint = 1u << 20, + DimmingEnabled = 1u << 21, }; // clang-format on @@ -248,6 +249,10 @@ public: ui::Dataspace getDataspace() const { return mOutputDataspace.get(); } + hardware::graphics::composer::hal::PixelFormat getPixelFormat() const { + return mPixelFormat.get(); + } + float getHdrSdrRatio() const { return getOutputLayer()->getLayerFE().getCompositionState()->currentHdrSdrRatio; }; @@ -258,6 +263,8 @@ public: gui::CachingHint getCachingHint() const { return mCachingHint.get(); } + bool isDimmingEnabled() const { return mIsDimmingEnabled.get(); } + float getFps() const { return getOutputLayer()->getLayerFE().getCompositionState()->fps; } void dump(std::string& result) const; @@ -498,7 +505,10 @@ private: return std::vector<std::string>{toString(cachingHint)}; }}; - static const constexpr size_t kNumNonUniqueFields = 19; + OutputLayerState<bool, LayerStateField::DimmingEnabled> mIsDimmingEnabled{ + [](auto layer) { return layer->getLayerFE().getCompositionState()->dimmingEnabled; }}; + + static const constexpr size_t kNumNonUniqueFields = 20; std::array<StateInterface*, kNumNonUniqueFields> getNonUniqueFields() { std::array<const StateInterface*, kNumNonUniqueFields> constFields = @@ -516,7 +526,7 @@ private: &mAlpha, &mLayerMetadata, &mVisibleRegion, &mOutputDataspace, &mPixelFormat, &mColorTransform, &mCompositionType, &mSidebandStream, &mBuffer, &mSolidColor, &mBackgroundBlurRadius, &mBlurRegions, - &mFrameNumber, &mIsProtected, &mCachingHint}; + &mFrameNumber, &mIsProtected, &mCachingHint, &mIsDimmingEnabled}; } }; diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp index 1f53588412..ea9442da06 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp @@ -27,8 +27,7 @@ #include <renderengine/DisplaySettings.h> #include <renderengine/RenderEngine.h> #include <ui/DebugUtils.h> -#include <utils/Trace.h> - +#include <ui/HdrRenderTypeUtils.h> #include <utils/Trace.h> namespace android::compositionengine::impl::planner { @@ -306,7 +305,7 @@ bool CachedSet::requiresHolePunch() const { return false; } - if (hasUnsupportedDataspace()) { + if (hasKnownColorShift()) { return false; } @@ -366,12 +365,21 @@ compositionengine::OutputLayer* CachedSet::getBlurLayer() const { return mBlurLayer ? mBlurLayer->getOutputLayer() : nullptr; } -bool CachedSet::hasUnsupportedDataspace() const { +bool CachedSet::hasKnownColorShift() const { return std::any_of(mLayers.cbegin(), mLayers.cend(), [](const Layer& layer) { auto dataspace = layer.getState()->getDataspace(); - const auto transfer = static_cast<ui::Dataspace>(dataspace & ui::Dataspace::TRANSFER_MASK); - if (transfer == ui::Dataspace::TRANSFER_ST2084 || transfer == ui::Dataspace::TRANSFER_HLG) { - // Skip HDR. + + // Layers are never dimmed when rendering a cached set, meaning that we may ask HWC to + // dim a cached set. But this means that we can never cache any HDR layers so that we + // don't accidentally dim those layers. + const auto hdrType = getHdrRenderType(dataspace, layer.getState()->getPixelFormat(), + layer.getState()->getHdrSdrRatio()); + if (hdrType != HdrRenderType::SDR) { + return true; + } + + // Layers that have dimming disabled pretend that they're HDR. + if (!layer.getState()->isDimmingEnabled()) { return true; } @@ -380,10 +388,6 @@ bool CachedSet::hasUnsupportedDataspace() const { // to avoid flickering/color differences. return true; } - // TODO(b/274804887): temp fix of overdimming issue, skip caching if hsdr/sdr ratio > 1.01f - if (layer.getState()->getHdrSdrRatio() > 1.01f) { - return true; - } return false; }); } diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp index 0a5c43a99b..4bafed2c8e 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp @@ -439,7 +439,7 @@ std::vector<Flattener::Run> Flattener::findCandidateRuns(time_point now) const { if (!layerDeniedFromCaching && layerIsInactive && (firstLayer || runHasFirstLayer || !layerHasBlur) && - !currentSet->hasUnsupportedDataspace()) { + !currentSet->hasKnownColorShift()) { if (isPartOfRun) { builder.increment(); } else { diff --git a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp index 0e3fdbb0dc..10dc9276d2 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <common/FlagManager.h> #include <compositionengine/impl/planner/LayerState.h> namespace { @@ -70,6 +71,10 @@ size_t LayerState::getHash() const { if (field->getField() == LayerStateField::Buffer) { continue; } + if (FlagManager::getInstance().cache_when_source_crop_layer_only_moved() && + field->getField() == LayerStateField::SourceCrop) { + continue; + } android::hashCombineSingleHashed(hash, field->getHash()); } diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp index d2eff75fb6..54ee0efa11 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp @@ -1339,6 +1339,55 @@ TEST_F(FlattenerTest, flattenLayers_skipsHDR2) { EXPECT_EQ(nullptr, overrideBuffer3); } +TEST_F(FlattenerTest, flattenLayers_skipsLayersDisablingDimming) { + auto& layerState1 = mTestLayers[0]->layerState; + const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; + + auto& layerState2 = mTestLayers[1]->layerState; + const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; + + // The third layer disables dimming, which means it should not be cached + auto& layerState3 = mTestLayers[2]->layerState; + const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; + mTestLayers[2]->layerFECompositionState.dimmingEnabled = false; + mTestLayers[2]->layerState->update(&mTestLayers[2]->outputLayer); + + const std::vector<const LayerState*> layers = { + layerState1.get(), + layerState2.get(), + layerState3.get(), + }; + + initializeFlattener(layers); + + mTime += 200ms; + initializeOverrideBuffer(layers); + EXPECT_EQ(getNonBufferHash(layers), + mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); + + // This will render a CachedSet. + EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)) + .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE)))); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); + + // We've rendered a CachedSet, but we haven't merged it in. + EXPECT_EQ(nullptr, overrideBuffer1); + EXPECT_EQ(nullptr, overrideBuffer2); + EXPECT_EQ(nullptr, overrideBuffer3); + + // This time we merge the CachedSet in, so we have a new hash, and we should + // only have two sets. + EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0); + initializeOverrideBuffer(layers); + EXPECT_NE(getNonBufferHash(layers), + mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); + + EXPECT_NE(nullptr, overrideBuffer1); + EXPECT_EQ(overrideBuffer1, overrideBuffer2); + EXPECT_EQ(nullptr, overrideBuffer3); +} + TEST_F(FlattenerTest, flattenLayers_skipsColorLayers) { auto& layerState1 = mTestLayers[0]->layerState; const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp index 044917ead9..03758b345a 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp @@ -18,6 +18,7 @@ #define LOG_TAG "LayerStateTest" #include <aidl/android/hardware/graphics/common/BufferUsage.h> +#include <common/include/common/test/FlagUtils.h> #include <compositionengine/impl/OutputLayer.h> #include <compositionengine/impl/planner/LayerState.h> #include <compositionengine/mock/LayerFE.h> @@ -26,6 +27,7 @@ #include <log/log.h> #include "android/hardware_buffer.h" +#include "com_android_graphics_surfaceflinger_flags.h" #include "compositionengine/LayerFECompositionState.h" #include <aidl/android/hardware/graphics/composer3/Composition.h> @@ -304,6 +306,16 @@ TEST_F(LayerStateTest, getCompositionType_forcedClient) { EXPECT_EQ(Composition::CLIENT, mLayerState->getCompositionType()); } +TEST_F(LayerStateTest, getHdrSdrRatio) { + OutputLayerCompositionState outputLayerCompositionState; + LayerFECompositionState layerFECompositionState; + layerFECompositionState.currentHdrSdrRatio = 2.f; + setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, + layerFECompositionState); + mLayerState = std::make_unique<LayerState>(&mOutputLayer); + EXPECT_EQ(2.f, mLayerState->getHdrSdrRatio()); +} + TEST_F(LayerStateTest, updateCompositionType) { OutputLayerCompositionState outputLayerCompositionState; LayerFECompositionState layerFECompositionState; @@ -454,6 +466,9 @@ TEST_F(LayerStateTest, updateSourceCrop) { } TEST_F(LayerStateTest, compareSourceCrop) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags:: + cache_when_source_crop_layer_only_moved, + false); OutputLayerCompositionState outputLayerCompositionState; outputLayerCompositionState.sourceCrop = sFloatRectOne; LayerFECompositionState layerFECompositionState; @@ -1033,6 +1048,47 @@ TEST_F(LayerStateTest, compareCachingHint) { EXPECT_TRUE(otherLayerState->compare(*mLayerState)); } +TEST_F(LayerStateTest, updateDimmingEnabled) { + OutputLayerCompositionState outputLayerCompositionState; + LayerFECompositionState layerFECompositionState; + layerFECompositionState.dimmingEnabled = true; + setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, + layerFECompositionState); + mLayerState = std::make_unique<LayerState>(&mOutputLayer); + EXPECT_TRUE(mLayerState->isDimmingEnabled()); + + mock::OutputLayer newOutputLayer; + sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make(); + LayerFECompositionState layerFECompositionStateTwo; + layerFECompositionStateTwo.dimmingEnabled = false; + setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState, + layerFECompositionStateTwo); + ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer); + EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::DimmingEnabled), updates); + EXPECT_FALSE(mLayerState->isDimmingEnabled()); +} + +TEST_F(LayerStateTest, compareDimmingEnabled) { + OutputLayerCompositionState outputLayerCompositionState; + LayerFECompositionState layerFECompositionState; + layerFECompositionState.dimmingEnabled = true; + setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, + layerFECompositionState); + mLayerState = std::make_unique<LayerState>(&mOutputLayer); + mock::OutputLayer newOutputLayer; + sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make(); + LayerFECompositionState layerFECompositionStateTwo; + layerFECompositionStateTwo.dimmingEnabled = false; + setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState, + layerFECompositionStateTwo); + auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer); + + verifyNonUniqueDifferingFields(*mLayerState, *otherLayerState, LayerStateField::DimmingEnabled); + + EXPECT_TRUE(mLayerState->compare(*otherLayerState)); + EXPECT_TRUE(otherLayerState->compare(*mLayerState)); +} + TEST_F(LayerStateTest, dumpDoesNotCrash) { OutputLayerCompositionState outputLayerCompositionState; LayerFECompositionState layerFECompositionState; diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp index 35d0ffb6e9..a1210b4e21 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp @@ -18,6 +18,9 @@ #undef LOG_TAG #define LOG_TAG "PredictorTest" +#include <common/include/common/test/FlagUtils.h> +#include "com_android_graphics_surfaceflinger_flags.h" + #include <compositionengine/impl/planner/Predictor.h> #include <compositionengine/mock/LayerFE.h> #include <compositionengine/mock/OutputLayer.h> @@ -127,6 +130,9 @@ TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchDifferentCompositionTypes } TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInSingleLayer) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags:: + cache_when_source_crop_layer_only_moved, + false); mock::OutputLayer outputLayerOne; sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make(); OutputLayerCompositionState outputLayerCompositionStateOne{ @@ -158,6 +164,9 @@ TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInSingleLayer) } TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInMultiLayerStack) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags:: + cache_when_source_crop_layer_only_moved, + false); mock::OutputLayer outputLayerOne; sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make(); OutputLayerCompositionState outputLayerCompositionStateOne{ @@ -304,6 +313,9 @@ TEST_F(LayerStackTest, getApproximateMatch_alwaysMatchesClientComposition) { } TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchMultipleApproximations) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags:: + cache_when_source_crop_layer_only_moved, + false); mock::OutputLayer outputLayerOne; sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make(); OutputLayerCompositionState outputLayerCompositionStateOne{ @@ -347,6 +359,9 @@ struct PredictionTest : public testing::Test { }; TEST_F(LayerStackTest, reorderingChangesNonBufferHash) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags:: + cache_when_source_crop_layer_only_moved, + false); mock::OutputLayer outputLayerOne; sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make(); OutputLayerCompositionState outputLayerCompositionStateOne{ @@ -467,6 +482,9 @@ TEST_F(PredictorTest, getPredictedPlan_recordCandidateAndRetrieveExactMatch) { } TEST_F(PredictorTest, getPredictedPlan_recordCandidateAndRetrieveApproximateMatch) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags:: + cache_when_source_crop_layer_only_moved, + false); mock::OutputLayer outputLayerOne; sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make(); OutputLayerCompositionState outputLayerCompositionStateOne{ @@ -504,6 +522,9 @@ TEST_F(PredictorTest, getPredictedPlan_recordCandidateAndRetrieveApproximateMatc } TEST_F(PredictorTest, recordMissedPlan_skipsApproximateMatch) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags:: + cache_when_source_crop_layer_only_moved, + false); mock::OutputLayer outputLayerOne; sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make(); OutputLayerCompositionState outputLayerCompositionStateOne{ diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 62f8fb16f0..45ab7ddc4d 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -560,10 +560,8 @@ auto DisplayDevice::setDesiredMode(display::DisplayModeRequest&& desiredMode) -> return DesiredModeAction::InitiateRenderRateSwitch; } - // Set the render frame rate to the active physical refresh rate to schedule the next - // frame as soon as possible. setActiveMode(activeMode.modePtr->getId(), activeMode.modePtr->getVsyncRate(), - activeMode.modePtr->getVsyncRate()); + activeMode.modePtr->getPeakFps()); // Initiate a mode change. mDesiredModeOpt = std::move(desiredMode); diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index 0966fe0496..7daeefe874 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -1028,6 +1028,8 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, const LayerSnapshot& parentSnapshot, const LayerHierarchy::TraversalPath& path, const Args& args) { + using InputConfig = gui::WindowInfo::InputConfig; + if (requested.windowInfoHandle) { snapshot.inputInfo = *requested.windowInfoHandle->getInfo(); } else { @@ -1056,6 +1058,11 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, snapshot.dropInputMode = gui::DropInputMode::NONE; } + if (snapshot.isSecure || + parentSnapshot.inputInfo.inputConfig.test(InputConfig::SENSITIVE_FOR_TRACING)) { + snapshot.inputInfo.inputConfig |= InputConfig::SENSITIVE_FOR_TRACING; + } + updateVisibility(snapshot, snapshot.isVisible); if (!needsInputInfo(snapshot, requested)) { return; @@ -1068,14 +1075,14 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, auto displayInfo = displayInfoOpt.value_or(sDefaultInfo); if (!requested.windowInfoHandle) { - snapshot.inputInfo.inputConfig = gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL; + snapshot.inputInfo.inputConfig = InputConfig::NO_INPUT_CHANNEL; } fillInputFrameInfo(snapshot.inputInfo, displayInfo.transform, snapshot); if (noValidDisplay) { // Do not let the window receive touches if it is not associated with a valid display // transform. We still allow the window to receive keys and prevent ANRs. - snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::NOT_TOUCHABLE; + snapshot.inputInfo.inputConfig |= InputConfig::NOT_TOUCHABLE; } snapshot.inputInfo.alpha = snapshot.color.a; @@ -1085,7 +1092,7 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, // If the window will be blacked out on a display because the display does not have the secure // flag and the layer has the secure flag set, then drop input. if (!displayInfo.isSecure && snapshot.isSecure) { - snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT; + snapshot.inputInfo.inputConfig |= InputConfig::DROP_INPUT; } if (requested.touchCropId != UNASSIGNED_LAYER_ID || path.isClone()) { @@ -1102,7 +1109,7 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, // Inherit the trusted state from the parent hierarchy, but don't clobber the trusted state // if it was set by WM for a known system overlay if (snapshot.isTrustedOverlay) { - snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::TRUSTED_OVERLAY; + snapshot.inputInfo.inputConfig |= InputConfig::TRUSTED_OVERLAY; } snapshot.inputInfo.contentSize = snapshot.croppedBufferSize.getSize(); @@ -1110,10 +1117,10 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, // If the layer is a clone, we need to crop the input region to cloned root to prevent // touches from going outside the cloned area. if (path.isClone()) { - snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::CLONE; + snapshot.inputInfo.inputConfig |= InputConfig::CLONE; // Cloned layers shouldn't handle watch outside since their z order is not determined by // WM or the client. - snapshot.inputInfo.inputConfig.clear(gui::WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH); + snapshot.inputInfo.inputConfig.clear(InputConfig::WATCH_OUTSIDE_TOUCH); } } diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index 867f3af4b1..028bd19a60 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -585,11 +585,13 @@ bool RequestedLayerState::isSimpleBufferUpdate(const layer_state_t& s) const { return false; } - static constexpr uint64_t deniedFlags = layer_state_t::eProducerDisconnect | - layer_state_t::eLayerChanged | layer_state_t::eRelativeLayerChanged | - layer_state_t::eTransparentRegionChanged | layer_state_t::eFlagsChanged | - layer_state_t::eBlurRegionsChanged | layer_state_t::eLayerStackChanged | - layer_state_t::eAutoRefreshChanged | layer_state_t::eReparent; + const uint64_t deniedFlags = layer_state_t::eProducerDisconnect | layer_state_t::eLayerChanged | + layer_state_t::eRelativeLayerChanged | layer_state_t::eTransparentRegionChanged | + layer_state_t::eFlagsChanged | layer_state_t::eBlurRegionsChanged | + layer_state_t::eLayerStackChanged | layer_state_t::eReparent | + (FlagManager::getInstance().latch_unsignaled_with_auto_refresh_changed() + ? 0 + : layer_state_t::eAutoRefreshChanged); if (s.what & deniedFlags) { ATRACE_FORMAT_INSTANT("%s: false [has denied flags 0x%" PRIx64 "]", __func__, s.what & deniedFlags); diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index cd2120e990..073bad3c02 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -15,7 +15,7 @@ */ // TODO(b/129481165): remove the #pragma below and fix conversion issues -#include "TransactionCallbackInvoker.h" + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wconversion" @@ -24,8 +24,6 @@ #define LOG_TAG "Layer" #define ATRACE_TAG ATRACE_TAG_GRAPHICS -#include "Layer.h" - #include <android-base/properties.h> #include <android-base/stringprintf.h> #include <binder/IPCThreadState.h> @@ -40,7 +38,6 @@ #include <ftl/enum.h> #include <ftl/fake_guard.h> #include <gui/BufferItem.h> -#include <gui/LayerDebugInfo.h> #include <gui/Surface.h> #include <gui/TraceUtils.h> #include <math.h> @@ -74,10 +71,12 @@ #include "FrameTracer/FrameTracer.h" #include "FrontEnd/LayerCreationArgs.h" #include "FrontEnd/LayerHandle.h" +#include "Layer.h" #include "LayerProtoHelper.h" #include "MutexUtils.h" #include "SurfaceFlinger.h" #include "TimeStats/TimeStats.h" +#include "TransactionCallbackInvoker.h" #include "TunnelModeEnabledReporter.h" #include "Utils/FenceUtils.h" @@ -1566,53 +1565,6 @@ void Layer::updateTransformHint(ui::Transform::RotationFlags transformHint) { // debugging // ---------------------------------------------------------------------------- -// TODO(marissaw): add new layer state info to layer debugging -gui::LayerDebugInfo Layer::getLayerDebugInfo(const DisplayDevice* display) const { - using namespace std::string_literals; - - gui::LayerDebugInfo info; - const State& ds = getDrawingState(); - info.mName = getName(); - sp<Layer> parent = mDrawingParent.promote(); - info.mParentName = parent ? parent->getName() : "none"s; - info.mType = getType(); - - info.mVisibleRegion = getVisibleRegion(display); - info.mSurfaceDamageRegion = surfaceDamageRegion; - info.mLayerStack = getLayerStack().id; - info.mX = ds.transform.tx(); - info.mY = ds.transform.ty(); - info.mZ = ds.z; - info.mCrop = ds.crop; - info.mColor = ds.color; - info.mFlags = ds.flags; - info.mPixelFormat = getPixelFormat(); - info.mDataSpace = static_cast<android_dataspace>(getDataSpace()); - info.mMatrix[0][0] = ds.transform[0][0]; - info.mMatrix[0][1] = ds.transform[0][1]; - info.mMatrix[1][0] = ds.transform[1][0]; - info.mMatrix[1][1] = ds.transform[1][1]; - { - sp<const GraphicBuffer> buffer = getBuffer(); - if (buffer != 0) { - info.mActiveBufferWidth = buffer->getWidth(); - info.mActiveBufferHeight = buffer->getHeight(); - info.mActiveBufferStride = buffer->getStride(); - info.mActiveBufferFormat = buffer->format; - } else { - info.mActiveBufferWidth = 0; - info.mActiveBufferHeight = 0; - info.mActiveBufferStride = 0; - info.mActiveBufferFormat = 0; - } - } - info.mNumQueuedFrames = getQueuedFrameCount(); - info.mIsOpaque = isOpaque(ds); - info.mContentDirty = contentDirty; - info.mStretchEffect = getStretchEffect(); - return info; -} - void Layer::miniDumpHeader(std::string& result) { result.append(kDumpTableRowLength, '-'); result.append("\n"); @@ -2937,26 +2889,13 @@ void Layer::prepareReleaseCallbacks(ftl::Future<FenceResult> futureFenceResult, ch->previousReleaseFences.emplace_back(std::move(futureFenceResult)); ch->name = mName; } else { - // 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::vector<ftl::Future<FenceResult>> 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 (auto& futureReleaseFence : mAdditionalPreviousReleaseFences) { - auto result = futureReleaseFence.wait_for(0s); - if (result != std::future_status::ready) { - mergedFences.emplace_back(std::move(futureReleaseFence)); - continue; - } - mergeFence(getDebugName(), futureReleaseFence.get().value_or(Fence::NO_FENCE), - prevFence); - } - if (prevFence != nullptr) { - mergedFences.emplace_back(ftl::yield(FenceResult(std::move(prevFence)))); - } - mAdditionalPreviousReleaseFences.swap(mergedFences); + // 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. + // Older fences for the same layer stack can be dropped when a new fence arrives. + // An assumption here is that RenderEngine performs work sequentially, so an + // incoming fence will not fire before an existing fence. + mAdditionalPreviousReleaseFences.emplace_or_replace(layerStack, + std::move(futureFenceResult)); } if (mBufferInfo.mBuffer) { @@ -3506,10 +3445,10 @@ bool Layer::setTransactionCompletedListeners(const std::vector<sp<CallbackHandle handle->previousFrameNumber = mDrawingState.previousFrameNumber; if (FlagManager::getInstance().ce_fence_promise() && mPreviousReleaseBufferEndpoint == handle->listener) { - // Add fences from previous screenshots now so that they can be dispatched to the + // Add fence from previous screenshot now so that it can be dispatched to the // client. - for (auto& futureReleaseFence : mAdditionalPreviousReleaseFences) { - handle->previousReleaseFences.emplace_back(std::move(futureReleaseFence)); + for (auto& [_, future] : mAdditionalPreviousReleaseFences) { + handle->previousReleaseFences.emplace_back(std::move(future)); } mAdditionalPreviousReleaseFences.clear(); } else if (FlagManager::getInstance().screenshot_fence_preservation() && @@ -3853,8 +3792,10 @@ bool Layer::isSimpleBufferUpdate(const layer_state_t& s) const { const uint64_t deniedFlags = layer_state_t::eProducerDisconnect | layer_state_t::eLayerChanged | layer_state_t::eRelativeLayerChanged | layer_state_t::eTransparentRegionChanged | layer_state_t::eFlagsChanged | layer_state_t::eBlurRegionsChanged | - layer_state_t::eLayerStackChanged | layer_state_t::eAutoRefreshChanged | - layer_state_t::eReparent; + layer_state_t::eLayerStackChanged | layer_state_t::eReparent | + (FlagManager::getInstance().latch_unsignaled_with_auto_refresh_changed() + ? 0 + : layer_state_t::eAutoRefreshChanged); if ((s.what & requiredFlags) != requiredFlags) { ATRACE_FORMAT_INSTANT("%s: false [missing required flags 0x%" PRIx64 "]", __func__, diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index d283d6fda9..c094aa1656 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -18,6 +18,7 @@ #include <android/gui/DropInputMode.h> #include <android/gui/ISurfaceComposerClient.h> +#include <ftl/small_map.h> #include <gui/BufferQueue.h> #include <gui/LayerState.h> #include <gui/WindowInfo.h> @@ -25,9 +26,11 @@ #include <math/vec4.h> #include <sys/types.h> #include <ui/BlurRegion.h> +#include <ui/DisplayMap.h> #include <ui/FloatRect.h> #include <ui/FrameStats.h> #include <ui/GraphicBuffer.h> +#include <ui/LayerStack.h> #include <ui/PixelFormat.h> #include <ui/Region.h> #include <ui/StretchEffect.h> @@ -71,10 +74,6 @@ class OutputLayer; struct LayerFECompositionState; } -namespace gui { -class LayerDebugInfo; -} - namespace frametimeline { class SurfaceFrame; } // namespace frametimeline @@ -703,8 +702,6 @@ public: inline const State& getDrawingState() const { return mDrawingState; } inline State& getDrawingState() { return mDrawingState; } - gui::LayerDebugInfo getLayerDebugInfo(const DisplayDevice*) const; - void miniDumpLegacy(std::string& result, const DisplayDevice&) const; void miniDump(std::string& result, const frontend::LayerSnapshot&, const DisplayDevice&) const; void dumpFrameStats(std::string& result) const; @@ -958,8 +955,11 @@ public: // screenshots asynchronously. There may be no buffer update for the // layer, but the layer will still be composited on the screen in every // frame. Kepping track of these fences ensures that they are not dropped - // and can be dispatched to the client at a later time. - std::vector<ftl::Future<FenceResult>> mAdditionalPreviousReleaseFences; + // and can be dispatched to the client at a later time. Older fences are + // dropped when a layer stack receives a new fence. + // TODO(b/300533018): Track fence per multi-instance RenderEngine + ftl::SmallMap<ui::LayerStack, ftl::Future<FenceResult>, ui::kDisplayCapacity> + mAdditionalPreviousReleaseFences; // Exposed so SurfaceFlinger can assert that it's held const sp<SurfaceFlinger> mFlinger; diff --git a/services/surfaceflinger/OWNERS b/services/surfaceflinger/OWNERS index 0aee7d497c..ffc1dd7979 100644 --- a/services/surfaceflinger/OWNERS +++ b/services/surfaceflinger/OWNERS @@ -2,7 +2,6 @@ adyabr@google.com alecmouri@google.com -chaviw@google.com domlaskowski@google.com jreck@google.com lpy@google.com @@ -10,5 +9,6 @@ pdwilliams@google.com racarr@google.com ramindani@google.com rnlee@google.com +sallyqi@google.com scroggo@google.com vishnun@google.com diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index 96eccf290f..6b654499a2 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -235,7 +235,8 @@ binder::Status EventThreadConnection::getLatestVsyncEventData( ParcelableVsyncEventData* outVsyncEventData) { ATRACE_CALL(); outVsyncEventData->vsync = - mEventThread->getLatestVsyncEventData(sp<EventThreadConnection>::fromExisting(this)); + mEventThread->getLatestVsyncEventData(sp<EventThreadConnection>::fromExisting(this), + systemTime()); return binder::Status::ok(); } @@ -387,8 +388,8 @@ void EventThread::requestNextVsync(const sp<EventThreadConnection>& connection) } } -VsyncEventData EventThread::getLatestVsyncEventData( - const sp<EventThreadConnection>& connection) const { +VsyncEventData EventThread::getLatestVsyncEventData(const sp<EventThreadConnection>& connection, + nsecs_t now) const { // Resync so that the vsync is accurate with hardware. getLatestVsyncEventData is an alternate // way to get vsync data (instead of posting callbacks to Choreographer). mCallback.resync(); @@ -399,11 +400,10 @@ VsyncEventData EventThread::getLatestVsyncEventData( const auto [presentTime, deadline] = [&]() -> std::pair<nsecs_t, nsecs_t> { std::lock_guard<std::mutex> lock(mMutex); const auto vsyncTime = mVsyncSchedule->getTracker().nextAnticipatedVSyncTimeFrom( - systemTime() + mWorkDuration.get().count() + mReadyDuration.count()); + now + mWorkDuration.get().count() + mReadyDuration.count()); return {vsyncTime, vsyncTime - mReadyDuration.count()}; }(); - generateFrameTimeline(vsyncEventData, frameInterval.ns(), systemTime(SYSTEM_TIME_MONOTONIC), - presentTime, deadline); + generateFrameTimeline(vsyncEventData, frameInterval.ns(), now, presentTime, deadline); if (FlagManager::getInstance().vrr_config()) { mCallback.onExpectedPresentTimePosted(TimePoint::fromNs(presentTime)); } diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h index 90e61a984b..f772126349 100644 --- a/services/surfaceflinger/Scheduler/EventThread.h +++ b/services/surfaceflinger/Scheduler/EventThread.h @@ -127,8 +127,8 @@ public: virtual void setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& connection) = 0; // Requests the next vsync. If resetIdleTimer is set to true, it resets the idle timer. virtual void requestNextVsync(const sp<EventThreadConnection>& connection) = 0; - virtual VsyncEventData getLatestVsyncEventData( - const sp<EventThreadConnection>& connection) const = 0; + virtual VsyncEventData getLatestVsyncEventData(const sp<EventThreadConnection>& connection, + nsecs_t now) const = 0; virtual void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) = 0; @@ -160,8 +160,8 @@ public: status_t registerDisplayEventConnection(const sp<EventThreadConnection>& connection) override; void setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& connection) override; void requestNextVsync(const sp<EventThreadConnection>& connection) override; - VsyncEventData getLatestVsyncEventData( - const sp<EventThreadConnection>& connection) const override; + VsyncEventData getLatestVsyncEventData(const sp<EventThreadConnection>& connection, + nsecs_t now) const override; void enableSyntheticVsync(bool) override; diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 32279aeda2..c83d81f4d9 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -250,18 +250,6 @@ void Scheduler::onFrameSignal(ICompositor& compositor, VsyncId vsyncId, mPacesetterFrameDurationFractionToSkip = 0.f; } - if (FlagManager::getInstance().vrr_config()) { - const auto minFramePeriod = pacesetterPtr->schedulePtr->minFramePeriod(); - const auto presentFenceForPastVsync = - pacesetterPtr->targeterPtr->target().presentFenceForPastVsync(minFramePeriod); - const auto lastConfirmedPresentTime = presentFenceForPastVsync->getSignalTime(); - if (lastConfirmedPresentTime != Fence::SIGNAL_TIME_PENDING && - lastConfirmedPresentTime != Fence::SIGNAL_TIME_INVALID) { - pacesetterPtr->schedulePtr->getTracker() - .onFrameBegin(expectedVsyncTime, TimePoint::fromNs(lastConfirmedPresentTime)); - } - } - const auto resultsPerDisplay = compositor.composite(pacesetterPtr->displayId, targeters); if (FlagManager::getInstance().vrr_config()) { compositor.sendNotifyExpectedPresentHint(pacesetterPtr->displayId); diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index a661292f9d..8ce61d86c6 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -68,6 +68,13 @@ public: void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) final EXCLUDES(mMutex); + bool isCurrentMode(const ftl::NonNull<DisplayModePtr>& modePtr) const EXCLUDES(mMutex) { + std::lock_guard lock(mMutex); + return mDisplayModePtr->getId() == modePtr->getId() && + mDisplayModePtr->getVsyncRate().getPeriodNsecs() == + mRateMap.find(idealPeriod())->second.slope; + } + void setRenderRate(Fps, bool applyImmediately) final EXCLUDES(mMutex); void onFrameBegin(TimePoint expectedPresentTime, TimePoint lastConfirmedPresentTime) final diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp index 9b8f310e16..8038364453 100644 --- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp @@ -141,8 +141,7 @@ void VSyncReactor::onDisplayModeChanged(ftl::NonNull<DisplayModePtr> modePtr, bo std::lock_guard lock(mMutex); mLastHwVsync.reset(); - if (!mSupportKernelIdleTimer && - modePtr->getVsyncRate().getPeriodNsecs() == mTracker.currentPeriod() && !force) { + if (!mSupportKernelIdleTimer && mTracker.isCurrentMode(modePtr) && !force) { endPeriodTransition(); setIgnorePresentFencesInternal(false); mMoreSamplesNeeded = false; diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h index 8787cdb82f..134d28e1e5 100644 --- a/services/surfaceflinger/Scheduler/VSyncTracker.h +++ b/services/surfaceflinger/Scheduler/VSyncTracker.h @@ -71,6 +71,11 @@ public: */ virtual Period minFramePeriod() const = 0; + /** + * Checks if the sourced mode is equal to the mode in the tracker. + */ + virtual bool isCurrentMode(const ftl::NonNull<DisplayModePtr>& modePtr) const = 0; + /* Inform the tracker that the samples it has are not accurate for prediction. */ virtual void resetModel() = 0; diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h index a5bb6c216f..d6a3f62ed1 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h @@ -71,6 +71,7 @@ public: bool isFramePending() const { return mFramePending; } bool didMissFrame() const { return mFrameMissed; } bool didMissHwcFrame() const { return mHwcFrameMissed && !mGpuFrameMissed; } + TimePoint lastSignaledFrameTime() const { return mLastSignaledFrameTime; }; protected: explicit FrameTarget(const std::string& displayLabel); @@ -98,6 +99,7 @@ protected: FenceTimePtr fenceTime = FenceTime::NO_FENCE; }; std::array<FenceWithFenceTime, 2> mPresentFences; + TimePoint mLastSignaledFrameTime; private: friend class FrameTargeterTestBase; diff --git a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp index 68c277d499..83355688f3 100644 --- a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp +++ b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp @@ -113,6 +113,7 @@ void FrameTargeter::beginFrame(const BeginFrameArgs& args, const IVsyncSource& v mFrameMissed = mFramePending || [&] { const nsecs_t pastPresentTime = pastPresentFence->getSignalTime(); if (pastPresentTime < 0) return false; + mLastSignaledFrameTime = TimePoint::fromNs(pastPresentTime); const nsecs_t frameMissedSlop = vsyncPeriod.ns() / 2; return lastScheduledPresentTime.ns() < pastPresentTime - frameMissedSlop; }(); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 734058a371..21f1cb3e4a 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -65,7 +65,6 @@ #include <gui/BufferQueue.h> #include <gui/DebugEGLImageTracker.h> #include <gui/IProducerListener.h> -#include <gui/LayerDebugInfo.h> #include <gui/LayerMetadata.h> #include <gui/LayerState.h> #include <gui/Surface.h> @@ -73,6 +72,7 @@ #include <gui/TraceUtils.h> #include <hidl/ServiceManagement.h> #include <layerproto/LayerProtoParser.h> +#include <linux/sched/types.h> #include <log/log.h> #include <private/android_filesystem_config.h> #include <private/gui/SyncFeatures.h> @@ -793,6 +793,8 @@ void chooseRenderEngineType(renderengine::RenderEngineCreationArgs::Builder& bui char prop[PROPERTY_VALUE_MAX]; property_get(PROPERTY_DEBUG_RENDERENGINE_BACKEND, prop, ""); + // TODO: b/293371537 - Once GraphiteVk is deemed relatively stable, log a warning that + // PROPERTY_DEBUG_RENDERENGINE_BACKEND is deprecated if (strcmp(prop, "skiagl") == 0) { builder.setThreaded(renderengine::RenderEngine::Threaded::NO) .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::GL); @@ -807,8 +809,14 @@ void chooseRenderEngineType(renderengine::RenderEngineCreationArgs::Builder& bui .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::VK); } else { const auto kVulkan = renderengine::RenderEngine::GraphicsApi::VK; - const bool useVulkan = FlagManager::getInstance().vulkan_renderengine() && - renderengine::RenderEngine::canSupport(kVulkan); + const bool canSupportVulkan = renderengine::RenderEngine::canSupport(kVulkan); + const bool useGraphite = + canSupportVulkan && FlagManager::getInstance().graphite_renderengine(); + const bool useVulkan = useGraphite || + (canSupportVulkan && FlagManager::getInstance().vulkan_renderengine()); + + builder.setSkiaBackend(useGraphite ? renderengine::RenderEngine::SkiaBackend::GRAPHITE + : renderengine::RenderEngine::SkiaBackend::GANESH); builder.setGraphicsApi(useVulkan ? kVulkan : renderengine::RenderEngine::GraphicsApi::GL); } } @@ -1834,19 +1842,6 @@ status_t SurfaceFlinger::isWideColorDisplay(const sp<IBinder>& displayToken, return NO_ERROR; } -status_t SurfaceFlinger::getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers) { - outLayers->clear(); - auto future = mScheduler->schedule([=, this] { - const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()); - mDrawingState.traverseInZOrder([&](Layer* layer) { - outLayers->push_back(layer->getLayerDebugInfo(display.get())); - }); - }); - - future.wait(); - return NO_ERROR; -} - status_t SurfaceFlinger::getCompositionPreference( Dataspace* outDataspace, ui::PixelFormat* outPixelFormat, Dataspace* outWideColorGamutDataspace, @@ -2079,10 +2074,17 @@ status_t SurfaceFlinger::getDisplayDecorationSupport( sp<IDisplayEventConnection> SurfaceFlinger::createDisplayEventConnection( gui::ISurfaceComposer::VsyncSource vsyncSource, EventRegistrationFlags eventRegistration, const sp<IBinder>& layerHandle) { - const auto cycle = vsyncSource == gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger - ? scheduler::Cycle::LastComposite - : scheduler::Cycle::Render; + const auto cycle = [&] { + if (FlagManager::getInstance().deprecate_vsync_sf()) { + ALOGW_IF(vsyncSource == gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger, + "requested unsupported config eVsyncSourceSurfaceFlinger"); + return scheduler::Cycle::Render; + } + return vsyncSource == gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger + ? scheduler::Cycle::LastComposite + : scheduler::Cycle::Render; + }(); return mScheduler->createDisplayEventConnection(cycle, eventRegistration, layerHandle); } @@ -2251,7 +2253,7 @@ bool SurfaceFlinger::updateLayerSnapshotsLegacy(VsyncId vsyncId, nsecs_t frameTi outTransactionsAreEmpty = !needsTraversal; const bool shouldCommit = (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal; if (shouldCommit) { - commitTransactions(); + commitTransactionsLegacy(); } bool mustComposite = latchBuffers() || shouldCommit; @@ -2375,8 +2377,14 @@ bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, mLayerHierarchyBuilder.update(mLayerLifecycleManager); } + // Keep a copy of the drawing state (that is going to be overwritten + // by commitTransactionsLocked) outside of mStateLock so that the side + // effects of the State assignment don't happen with mStateLock held, + // which can cause deadlocks. + State drawingState(mDrawingState); + Mutex::Autolock lock(mStateLock); bool mustComposite = false; - mustComposite |= applyAndCommitDisplayTransactionStates(update.transactions); + mustComposite |= applyAndCommitDisplayTransactionStatesLocked(update.transactions); { ATRACE_NAME("LayerSnapshotBuilder:update"); @@ -2415,7 +2423,7 @@ bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, bool newDataLatched = false; if (!mLegacyFrontEndEnabled) { ATRACE_NAME("DisplayCallbackAndStatsUpdates"); - mustComposite |= applyTransactions(update.transactions, vsyncId); + mustComposite |= applyTransactionsLocked(update.transactions, vsyncId); traverseLegacyLayers([&](Layer* layer) { layer->commitTransaction(); }); const nsecs_t latchTime = systemTime(); bool unused = false; @@ -2606,6 +2614,14 @@ bool SurfaceFlinger::commit(PhysicalDisplayId pacesetterId, flushTransactions, transactionsAreEmpty); } + // Tell VsyncTracker that we are going to present this frame before scheduling + // setTransactionFlags which will schedule another SF frame. This was if the tracker + // needs to adjust the vsync timeline, it will be done before the next frame. + if (FlagManager::getInstance().vrr_config() && mustComposite) { + mScheduler->getVsyncSchedule()->getTracker().onFrameBegin( + pacesetterFrameTarget.expectedPresentTime(), + pacesetterFrameTarget.lastSignaledFrameTime()); + } if (transactionFlushNeeded()) { setTransactionFlags(eTransactionFlushNeeded); } @@ -3163,7 +3179,8 @@ void SurfaceFlinger::onCompositionPresented(PhysicalDisplayId pacesetterId, if (mLayerLifecycleManagerEnabled) { mLayerSnapshotBuilder.forEachVisibleSnapshot( [&, compositionDisplay = compositionDisplay]( - std::unique_ptr<frontend::LayerSnapshot>& snapshot) { + std::unique_ptr<frontend::LayerSnapshot>& + snapshot) FTL_FAKE_GUARD(kMainThreadContext) { auto it = mLegacyLayers.find(snapshot->sequence); LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s", @@ -3222,7 +3239,7 @@ void SurfaceFlinger::onCompositionPresented(PhysicalDisplayId pacesetterId, if (mNumTrustedPresentationListeners > 0) { // We avoid any reverse traversal upwards so this shouldn't be too expensive - traverseLegacyLayers([&](Layer* layer) { + traverseLegacyLayers([&](Layer* layer) FTL_FAKE_GUARD(kMainThreadContext) { if (!layer->hasTrustedPresentationListener()) { return; } @@ -3282,6 +3299,19 @@ void SurfaceFlinger::computeLayerBounds() { void SurfaceFlinger::commitTransactions() { ATRACE_CALL(); + mDebugInTransaction = systemTime(); + + // Here we're guaranteed that some transaction flags are set + // so we can call commitTransactionsLocked unconditionally. + // We clear the flags with mStateLock held to guarantee that + // mCurrentState won't change until the transaction is committed. + mScheduler->modulateVsync({}, &VsyncModulator::onTransactionCommit); + commitTransactionsLocked(clearTransactionFlags(eTransactionMask)); + mDebugInTransaction = 0; +} + +void SurfaceFlinger::commitTransactionsLegacy() { + ATRACE_CALL(); // Keep a copy of the drawing state (that is going to be overwritten // by commitTransactionsLocked) outside of mStateLock so that the side @@ -4122,7 +4152,7 @@ void SurfaceFlinger::buildWindowInfos(std::vector<WindowInfo>& outWindowInfos, outWindowInfos.push_back(snapshot.inputInfo); }); } else { - mDrawingState.traverseInReverseZOrder([&](Layer* layer) { + mDrawingState.traverseInReverseZOrder([&](Layer* layer) FTL_FAKE_GUARD(kMainThreadContext) { if (!layer->needsInputInfo()) return; const auto opt = mFrontEndDisplayInfos.get(layer->getLayerStack()) @@ -4827,6 +4857,8 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC if (listener && (flushState.queueProcessTime - transaction.postTime) > std::chrono::nanoseconds(4s).count()) { + // Used to add a stalled transaction which uses an internal lock. + ftl::FakeGuard guard(kMainThreadContext); mTransactionHandler .onTransactionQueueStalled(transaction.id, {.pid = layer->getOwnerPid(), @@ -4849,97 +4881,107 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC const TransactionHandler::TransactionFlushState& flushState) { using TransactionReadiness = TransactionHandler::TransactionReadiness; auto ready = TransactionReadiness::Ready; - flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const ResolvedComposerState& - resolvedState) -> bool { - const frontend::RequestedLayerState* layer = - mLayerLifecycleManager.getLayerFromId(resolvedState.layerId); - const auto& transaction = *flushState.transaction; - const auto& s = resolvedState.state; - // check for barrier frames - if (s.bufferData->hasBarrier) { - // The current producerId is already a newer producer than the buffer that has a - // barrier. This means the incoming buffer is older and we can release it here. We - // don't wait on the barrier since we know that's stale information. - if (layer->barrierProducerId > s.bufferData->producerId) { - if (s.bufferData->releaseBufferListener) { - uint32_t currentMaxAcquiredBufferCount = - getMaxAcquiredBufferCountForCurrentRefreshRate(layer->ownerUid.val()); - ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64, - layer->name.c_str(), s.bufferData->frameNumber); - s.bufferData->releaseBufferListener - ->onReleaseBuffer({resolvedState.externalTexture->getBuffer()->getId(), - s.bufferData->frameNumber}, - s.bufferData->acquireFence - ? s.bufferData->acquireFence - : Fence::NO_FENCE, - currentMaxAcquiredBufferCount); - } + flushState.transaction->traverseStatesWithBuffersWhileTrue( + [&](const ResolvedComposerState& resolvedState) FTL_FAKE_GUARD( + kMainThreadContext) -> bool { + const frontend::RequestedLayerState* layer = + mLayerLifecycleManager.getLayerFromId(resolvedState.layerId); + const auto& transaction = *flushState.transaction; + const auto& s = resolvedState.state; + // check for barrier frames + if (s.bufferData->hasBarrier) { + // The current producerId is already a newer producer than the buffer that has a + // barrier. This means the incoming buffer is older and we can release it here. + // We don't wait on the barrier since we know that's stale information. + if (layer->barrierProducerId > s.bufferData->producerId) { + if (s.bufferData->releaseBufferListener) { + uint32_t currentMaxAcquiredBufferCount = + getMaxAcquiredBufferCountForCurrentRefreshRate( + layer->ownerUid.val()); + ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64, + layer->name.c_str(), s.bufferData->frameNumber); + s.bufferData->releaseBufferListener + ->onReleaseBuffer({resolvedState.externalTexture->getBuffer() + ->getId(), + s.bufferData->frameNumber}, + s.bufferData->acquireFence + ? s.bufferData->acquireFence + : Fence::NO_FENCE, + currentMaxAcquiredBufferCount); + } - // Delete the entire state at this point and not just release the buffer because - // everything associated with the Layer in this Transaction is now out of date. - ATRACE_FORMAT("DeleteStaleBuffer %s barrierProducerId:%d > %d", layer->name.c_str(), - layer->barrierProducerId, s.bufferData->producerId); - return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL; - } + // Delete the entire state at this point and not just release the buffer + // because everything associated with the Layer in this Transaction is now + // out of date. + ATRACE_FORMAT("DeleteStaleBuffer %s barrierProducerId:%d > %d", + layer->name.c_str(), layer->barrierProducerId, + s.bufferData->producerId); + return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL; + } - if (layer->barrierFrameNumber < s.bufferData->barrierFrameNumber) { - const bool willApplyBarrierFrame = - flushState.bufferLayersReadyToPresent.contains(s.surface.get()) && - ((flushState.bufferLayersReadyToPresent.get(s.surface.get()) >= - s.bufferData->barrierFrameNumber)); - if (!willApplyBarrierFrame) { - ATRACE_FORMAT("NotReadyBarrier %s barrierFrameNumber:%" PRId64 " > %" PRId64, - layer->name.c_str(), layer->barrierFrameNumber, - s.bufferData->barrierFrameNumber); - ready = TransactionReadiness::NotReadyBarrier; - return TraverseBuffersReturnValues::STOP_TRAVERSAL; + if (layer->barrierFrameNumber < s.bufferData->barrierFrameNumber) { + const bool willApplyBarrierFrame = + flushState.bufferLayersReadyToPresent.contains(s.surface.get()) && + ((flushState.bufferLayersReadyToPresent.get(s.surface.get()) >= + s.bufferData->barrierFrameNumber)); + if (!willApplyBarrierFrame) { + ATRACE_FORMAT("NotReadyBarrier %s barrierFrameNumber:%" PRId64 + " > %" PRId64, + layer->name.c_str(), layer->barrierFrameNumber, + s.bufferData->barrierFrameNumber); + ready = TransactionReadiness::NotReadyBarrier; + return TraverseBuffersReturnValues::STOP_TRAVERSAL; + } + } } - } - } - // If backpressure is enabled and we already have a buffer to commit, keep - // the transaction in the queue. - const bool hasPendingBuffer = - flushState.bufferLayersReadyToPresent.contains(s.surface.get()); - if (layer->backpressureEnabled() && hasPendingBuffer && transaction.isAutoTimestamp) { - ATRACE_FORMAT("hasPendingBuffer %s", layer->name.c_str()); - ready = TransactionReadiness::NotReady; - return TraverseBuffersReturnValues::STOP_TRAVERSAL; - } + // If backpressure is enabled and we already have a buffer to commit, keep + // the transaction in the queue. + const bool hasPendingBuffer = + flushState.bufferLayersReadyToPresent.contains(s.surface.get()); + if (layer->backpressureEnabled() && hasPendingBuffer && + transaction.isAutoTimestamp) { + ATRACE_FORMAT("hasPendingBuffer %s", layer->name.c_str()); + ready = TransactionReadiness::NotReady; + return TraverseBuffersReturnValues::STOP_TRAVERSAL; + } - const bool acquireFenceAvailable = s.bufferData && - s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) && - s.bufferData->acquireFence; - const bool fenceSignaled = !acquireFenceAvailable || - s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled; - if (!fenceSignaled) { - // check fence status - const bool allowLatchUnsignaled = shouldLatchUnsignaled(s, transaction.states.size(), - flushState.firstTransaction) && - layer->isSimpleBufferUpdate(s); - if (allowLatchUnsignaled) { - ATRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s", layer->name.c_str()); - ready = TransactionReadiness::NotReadyUnsignaled; - } else { - ready = TransactionReadiness::NotReady; - auto& listener = s.bufferData->releaseBufferListener; - if (listener && - (flushState.queueProcessTime - transaction.postTime) > - std::chrono::nanoseconds(4s).count()) { - mTransactionHandler - .onTransactionQueueStalled(transaction.id, - {.pid = layer->ownerPid.val(), - .layerId = layer->id, - .layerName = layer->name, - .bufferId = s.bufferData->getId(), - .frameNumber = s.bufferData->frameNumber}); + const bool acquireFenceAvailable = s.bufferData && + s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) && + s.bufferData->acquireFence; + const bool fenceSignaled = !acquireFenceAvailable || + s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled; + if (!fenceSignaled) { + // check fence status + const bool allowLatchUnsignaled = + shouldLatchUnsignaled(s, transaction.states.size(), + flushState.firstTransaction) && + layer->isSimpleBufferUpdate(s); + if (allowLatchUnsignaled) { + ATRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s", + layer->name.c_str()); + ready = TransactionReadiness::NotReadyUnsignaled; + } else { + ready = TransactionReadiness::NotReady; + auto& listener = s.bufferData->releaseBufferListener; + if (listener && + (flushState.queueProcessTime - transaction.postTime) > + std::chrono::nanoseconds(4s).count()) { + mTransactionHandler + .onTransactionQueueStalled(transaction.id, + {.pid = layer->ownerPid.val(), + .layerId = layer->id, + .layerName = layer->name, + .bufferId = s.bufferData->getId(), + .frameNumber = + s.bufferData->frameNumber}); + } + ATRACE_FORMAT("fence unsignaled %s", layer->name.c_str()); + return TraverseBuffersReturnValues::STOP_TRAVERSAL; + } } - ATRACE_FORMAT("fence unsignaled %s", layer->name.c_str()); - return TraverseBuffersReturnValues::STOP_TRAVERSAL; - } - } - return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL; - }); + return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL; + }); return ready; } @@ -5160,7 +5202,13 @@ status_t SurfaceFlinger::setTransactionState( }(state.flags); const auto frameHint = state.isFrameActive() ? FrameHint::kActive : FrameHint::kNone; - mTransactionHandler.queueTransaction(std::move(state)); + { + // Transactions are added via a lockless queue and does not need to be added from the main + // thread. + ftl::FakeGuard guard(kMainThreadContext); + mTransactionHandler.queueTransaction(std::move(state)); + } + for (const auto& [displayId, data] : mNotifyExpectedPresentMap) { if (data.hintStatus.load() == NotifyExpectedPresentHintStatus::ScheduleOnTx) { scheduleNotifyExpectedPresentHint(displayId, VsyncId{frameTimelineInfo.vsyncId}); @@ -5205,17 +5253,19 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin desiredPresentTime, isAutoTimestamp, postTime, transactionId); } - if ((flags & eAnimation) && resolvedState.state.surface) { - if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) { - const auto layerProps = scheduler::LayerProps{ - .visible = layer->isVisible(), - .bounds = layer->getBounds(), - .transform = layer->getTransform(), - .setFrameRateVote = layer->getFrameRateForLayerTree(), - .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(), - .isFrontBuffered = layer->isFrontBuffered(), - }; - layer->recordLayerHistoryAnimationTx(layerProps, now); + if (!mLayerLifecycleManagerEnabled) { + if ((flags & eAnimation) && resolvedState.state.surface) { + if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) { + const auto layerProps = scheduler::LayerProps{ + .visible = layer->isVisible(), + .bounds = layer->getBounds(), + .transform = layer->getTransform(), + .setFrameRateVote = layer->getFrameRateForLayerTree(), + .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(), + .isFrontBuffered = layer->isFrontBuffered(), + }; + layer->recordLayerHistoryAnimationTx(layerProps, now); + } } } } @@ -5251,9 +5301,8 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin return needsTraversal; } -bool SurfaceFlinger::applyAndCommitDisplayTransactionStates( +bool SurfaceFlinger::applyAndCommitDisplayTransactionStatesLocked( std::vector<TransactionState>& transactions) { - Mutex::Autolock lock(mStateLock); bool needsTraversal = false; uint32_t transactionFlags = 0; for (auto& transaction : transactions) { @@ -6003,7 +6052,11 @@ void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32 mDestroyedHandles.emplace_back(layerId, layer->getDebugName()); } - mTransactionHandler.onLayerDestroyed(layerId); + { + // Used to remove stalled transactions which uses an internal lock. + ftl::FakeGuard guard(kMainThreadContext); + mTransactionHandler.onLayerDestroyed(layerId); + } Mutex::Autolock lock(mStateLock); markLayerPendingRemovalLocked(layer); @@ -6036,7 +6089,8 @@ void SurfaceFlinger::initializeDisplays() { if (mLegacyFrontEndEnabled) { applyTransactions(transactions, VsyncId{0}); } else { - applyAndCommitDisplayTransactionStates(transactions); + Mutex::Autolock lock(mStateLock); + applyAndCommitDisplayTransactionStatesLocked(transactions); } { @@ -6654,7 +6708,11 @@ void SurfaceFlinger::dumpOffscreenLayersProto(perfetto::protos::LayersProto& lay } perfetto::protos::LayersProto SurfaceFlinger::dumpProtoFromMainThread(uint32_t traceFlags) { - return mScheduler->schedule([=, this] { return dumpDrawingStateProto(traceFlags); }).get(); + return mScheduler + ->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) { + return dumpDrawingStateProto(traceFlags); + }) + .get(); } void SurfaceFlinger::dumpOffscreenLayers(std::string& result) { @@ -6703,17 +6761,18 @@ void SurfaceFlinger::dumpHwcLayersMinidump(std::string& result) const { Layer::miniDumpHeader(result); const DisplayDevice& ref = *display; - mLayerSnapshotBuilder.forEachVisibleSnapshot([&](const frontend::LayerSnapshot& snapshot) { - if (!snapshot.hasSomethingToDraw() || - ref.getLayerStack() != snapshot.outputFilter.layerStack) { - return; - } - auto it = mLegacyLayers.find(snapshot.sequence); - LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(), - "Couldnt find layer object for %s", - snapshot.getDebugString().c_str()); - it->second->miniDump(result, snapshot, ref); - }); + mLayerSnapshotBuilder.forEachVisibleSnapshot( + [&](const frontend::LayerSnapshot& snapshot) FTL_FAKE_GUARD(kMainThreadContext) { + if (!snapshot.hasSomethingToDraw() || + ref.getLayerStack() != snapshot.outputFilter.layerStack) { + return; + } + auto it = mLegacyLayers.find(snapshot.sequence); + LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(), + "Couldnt find layer object for %s", + snapshot.getDebugString().c_str()); + it->second->miniDump(result, snapshot, ref); + }); result.append("\n"); } } @@ -7749,20 +7808,6 @@ status_t SurfaceFlinger::setSchedAttr(bool enabled) { return NO_ERROR; } - // Currently, there is no wrapper in bionic: b/183240349. - struct sched_attr { - uint32_t size; - uint32_t sched_policy; - uint64_t sched_flags; - int32_t sched_nice; - uint32_t sched_priority; - uint64_t sched_runtime; - uint64_t sched_deadline; - uint64_t sched_period; - uint32_t sched_util_min; - uint32_t sched_util_max; - }; - sched_attr attr = {}; attr.size = sizeof(attr); @@ -7817,17 +7862,19 @@ void SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, status_t validate = validateScreenshotPermissions(args); if (validate != OK) { + ALOGD("Permission denied to captureDisplay"); invokeScreenCaptureError(validate, captureListener); return; } if (!args.displayToken) { + ALOGD("Invalid display token to captureDisplay"); invokeScreenCaptureError(BAD_VALUE, captureListener); return; } if (args.captureSecureLayers && !hasCaptureBlackoutContentPermission()) { - ALOGE("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT"); + ALOGD("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT"); invokeScreenCaptureError(PERMISSION_DENIED, captureListener); return; } @@ -7840,6 +7887,7 @@ void SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, Mutex::Autolock lock(mStateLock); sp<DisplayDevice> display = getDisplayDeviceLocked(args.displayToken); if (!display) { + ALOGD("Unable to find display device for captureDisplay"); invokeScreenCaptureError(NAME_NOT_FOUND, captureListener); return; } @@ -7856,7 +7904,7 @@ void SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, if (excludeLayer != UNASSIGNED_LAYER_ID) { excludeLayerIds.emplace(excludeLayer); } else { - ALOGW("Invalid layer handle passed as excludeLayer to captureDisplay"); + ALOGD("Invalid layer handle passed as excludeLayer to captureDisplay"); invokeScreenCaptureError(NAME_NOT_FOUND, captureListener); return; } @@ -7894,6 +7942,7 @@ void SurfaceFlinger::captureDisplay(DisplayId displayId, const CaptureArgs& args const auto display = getDisplayDeviceLocked(displayId); if (!display) { + ALOGD("Unable to find display device for captureDisplay"); invokeScreenCaptureError(NAME_NOT_FOUND, captureListener); return; } @@ -7911,7 +7960,7 @@ void SurfaceFlinger::captureDisplay(DisplayId displayId, const CaptureArgs& args constexpr auto kMaxTextureSize = 16384; if (size.width <= 0 || size.height <= 0 || size.width >= kMaxTextureSize || size.height >= kMaxTextureSize) { - ALOGE("capture display resolved to invalid size %d x %d", size.width, size.height); + ALOGD("captureDisplay resolved to invalid size %d x %d", size.width, size.height); invokeScreenCaptureError(BAD_VALUE, captureListener); return; } @@ -7958,6 +8007,7 @@ void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, status_t validate = validateScreenshotPermissions(args); if (validate != OK) { + ALOGD("Permission denied to captureLayers"); invokeScreenCaptureError(validate, captureListener); return; } @@ -7969,7 +8019,7 @@ void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, ui::Dataspace dataspace = args.dataspace; if (args.captureSecureLayers && !hasCaptureBlackoutContentPermission()) { - ALOGE("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT"); + ALOGD("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT"); invokeScreenCaptureError(PERMISSION_DENIED, captureListener); return; } @@ -7979,7 +8029,7 @@ void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, parent = LayerHandle::getLayer(args.layerHandle); if (parent == nullptr) { - ALOGE("captureLayers called with an invalid or removed parent"); + ALOGD("captureLayers called with an invalid or removed parent"); invokeScreenCaptureError(NAME_NOT_FOUND, captureListener); return; } @@ -7998,6 +8048,7 @@ void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, if (crop.isEmpty() || args.frameScaleX <= 0.0f || args.frameScaleY <= 0.0f) { // Error out if the layer has no source bounds (i.e. they are boundless) and a source // crop was not specified, or an invalid frame scale was provided. + ALOGD("Boundless layer, unspecified crop, or invalid frame scale to captureLayers"); invokeScreenCaptureError(BAD_VALUE, captureListener); return; } @@ -8008,7 +8059,7 @@ void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, if (excludeLayer != UNASSIGNED_LAYER_ID) { excludeLayerIds.emplace(excludeLayer); } else { - ALOGW("Invalid layer handle passed as excludeLayer to captureLayers"); + ALOGD("Invalid layer handle passed as excludeLayer to captureLayers"); invokeScreenCaptureError(NAME_NOT_FOUND, captureListener); return; } @@ -8017,13 +8068,14 @@ void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, // really small crop or frameScale if (reqSize.width <= 0 || reqSize.height <= 0) { - ALOGW("Failed to captureLayers: crop or scale too small"); + ALOGD("Failed to captureLayers: crop or scale too small"); invokeScreenCaptureError(BAD_VALUE, captureListener); return; } bool childrenOnly = args.childrenOnly; - RenderAreaFuture renderAreaFuture = ftl::defer([=, this]() -> std::unique_ptr<RenderArea> { + RenderAreaFuture renderAreaFuture = ftl::defer([=, this]() FTL_FAKE_GUARD(kMainThreadContext) + -> std::unique_ptr<RenderArea> { ui::Transform layerTransform; Rect layerBufferSize; if (mLayerLifecycleManagerEnabled) { @@ -8082,7 +8134,7 @@ void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, } if (captureListener == nullptr) { - ALOGE("capture screen must provide a capture listener callback"); + ALOGD("capture screen must provide a capture listener callback"); invokeScreenCaptureError(BAD_VALUE, captureListener); return; } @@ -8271,9 +8323,15 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl( Mutex::Autolock lock(mStateLock); const DisplayDevice* display = nullptr; if (parent) { - display = findDisplay([layerStack = parent->getLayerStack()](const auto& display) { - return display.getLayerStack() == layerStack; - }).get(); + const frontend::LayerSnapshot* snapshot = mLayerLifecycleManagerEnabled + ? mLayerSnapshotBuilder.getSnapshot(parent->sequence) + : parent->getLayerSnapshot(); + if (snapshot) { + display = findDisplay([layerStack = snapshot->outputFilter.layerStack]( + const auto& display) { + return display.getLayerStack() == layerStack; + }).get(); + } } if (display == nullptr) { @@ -8989,6 +9047,8 @@ status_t SurfaceFlinger::removeWindowInfosListener( status_t SurfaceFlinger::getStalledTransactionInfo( int pid, std::optional<TransactionHandler::StalledTransactionInfo>& result) { + // Used to add a stalled transaction which uses an internal lock. + ftl::FakeGuard guard(kMainThreadContext); result = mTransactionHandler.getStalledTransactionInfo(pid); return NO_ERROR; } @@ -9189,7 +9249,8 @@ std::vector<std::pair<Layer*, LayerFE*>> SurfaceFlinger::moveSnapshotsToComposit if (mLayerLifecycleManagerEnabled) { nsecs_t currentTime = systemTime(); mLayerSnapshotBuilder.forEachVisibleSnapshot( - [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) { + [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) FTL_FAKE_GUARD( + kMainThreadContext) { if (cursorOnly && snapshot->compositionType != aidl::android::hardware::graphics::composer3::Composition::CURSOR) { @@ -9250,11 +9311,12 @@ SurfaceFlinger::getLayerSnapshotsForScreenshots( std::optional<ui::LayerStack> layerStack, uint32_t uid, std::function<bool(const frontend::LayerSnapshot&, bool& outStopTraversal)> snapshotFilterFn) { - return [&, layerStack, uid]() { + return [&, layerStack, uid]() FTL_FAKE_GUARD(kMainThreadContext) { std::vector<std::pair<Layer*, sp<LayerFE>>> layers; bool stopTraversal = false; mLayerSnapshotBuilder.forEachVisibleSnapshot( - [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) { + [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) FTL_FAKE_GUARD( + kMainThreadContext) { if (stopTraversal) { return; } @@ -9289,7 +9351,8 @@ std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> SurfaceFlinger::getLayerSnapshotsForScreenshots(std::optional<ui::LayerStack> layerStack, uint32_t uid, std::unordered_set<uint32_t> excludeLayerIds) { - return [&, layerStack, uid, excludeLayerIds = std::move(excludeLayerIds)]() { + return [&, layerStack, uid, + excludeLayerIds = std::move(excludeLayerIds)]() FTL_FAKE_GUARD(kMainThreadContext) { if (excludeLayerIds.empty()) { auto getLayerSnapshotsFn = getLayerSnapshotsForScreenshots(layerStack, uid, /*snapshotFilterFn=*/nullptr); @@ -9331,7 +9394,7 @@ SurfaceFlinger::getLayerSnapshotsForScreenshots(uint32_t rootLayerId, uint32_t u bool childrenOnly, const std::optional<FloatRect>& parentCrop) { return [&, rootLayerId, uid, excludeLayerIds = std::move(excludeLayerIds), childrenOnly, - parentCrop]() { + parentCrop]() FTL_FAKE_GUARD(kMainThreadContext) { auto root = mLayerHierarchyBuilder.getPartialHierarchy(rootLayerId, childrenOnly); frontend::LayerSnapshotBuilder::Args args{.root = root, @@ -9842,6 +9905,7 @@ binder::Status SurfaceComposerAIDL::captureDisplayById( std::optional<DisplayId> id = DisplayId::fromValue(static_cast<uint64_t>(displayId)); mFlinger->captureDisplay(*id, args, captureListener); } else { + ALOGD("Permission denied to captureDisplayById"); invokeScreenCaptureError(PERMISSION_DENIED, captureListener); } return binderStatusFromStatusT(NO_ERROR); @@ -9887,22 +9951,6 @@ binder::Status SurfaceComposerAIDL::onPullAtom(int32_t atomId, gui::PullAtomData return binderStatusFromStatusT(status); } -binder::Status SurfaceComposerAIDL::getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers) { - if (!outLayers) { - return binderStatusFromStatusT(UNEXPECTED_NULL); - } - - IPCThreadState* ipc = IPCThreadState::self(); - const int pid = ipc->getCallingPid(); - const int uid = ipc->getCallingUid(); - if ((uid != AID_SHELL) && !PermissionCache::checkPermission(sDump, pid, uid)) { - ALOGE("Layer debug info permission denied for pid=%d, uid=%d", pid, uid); - return binderStatusFromStatusT(PERMISSION_DENIED); - } - status_t status = mFlinger->getLayerDebugInfo(outLayers); - return binderStatusFromStatusT(status); -} - binder::Status SurfaceComposerAIDL::getCompositionPreference(gui::CompositionPreference* outPref) { ui::Dataspace dataspace; ui::PixelFormat pixelFormat; diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index c106abda37..44fa80607d 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -38,7 +38,6 @@ #include <gui/FrameTimestamps.h> #include <gui/ISurfaceComposer.h> #include <gui/ITransactionCompletedListener.h> -#include <gui/LayerDebugInfo.h> #include <gui/LayerState.h> #include <layerproto/LayerProtoHeader.h> #include <math/mat4.h> @@ -599,7 +598,6 @@ private: status_t overrideHdrTypes(const sp<IBinder>& displayToken, const std::vector<ui::Hdr>& hdrTypes); status_t onPullAtom(const int32_t atomId, std::vector<uint8_t>* pulledData, bool* success); - status_t getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers); status_t getCompositionPreference(ui::Dataspace* outDataspace, ui::PixelFormat* outPixelFormat, ui::Dataspace* outWideColorGamutDataspace, ui::PixelFormat* outWideColorGamutPixelFormat) const; @@ -760,7 +758,8 @@ private: bool force = false) REQUIRES(mStateLock, kMainThreadContext); - void commitTransactions() EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); + void commitTransactionsLegacy() EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); + void commitTransactions() REQUIRES(kMainThreadContext, mStateLock); void commitTransactionsLocked(uint32_t transactionFlags) REQUIRES(mStateLock, kMainThreadContext); void doCommitTransactions() REQUIRES(mStateLock); @@ -771,24 +770,27 @@ private: void updateLayerGeometry(); void updateLayerMetadataSnapshot(); std::vector<std::pair<Layer*, LayerFE*>> moveSnapshotsToCompositionArgs( - compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly); + compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly) + REQUIRES(kMainThreadContext); void moveSnapshotsFromCompositionArgs(compositionengine::CompositionRefreshArgs& refreshArgs, - const std::vector<std::pair<Layer*, LayerFE*>>& layers); + const std::vector<std::pair<Layer*, LayerFE*>>& layers) + REQUIRES(kMainThreadContext); // Return true if we must composite this frame bool updateLayerSnapshotsLegacy(VsyncId vsyncId, nsecs_t frameTimeNs, bool transactionsFlushed, bool& out) REQUIRES(kMainThreadContext); // Return true if we must composite this frame bool updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, bool transactionsFlushed, bool& out) REQUIRES(kMainThreadContext); - void updateLayerHistory(nsecs_t now); + void updateLayerHistory(nsecs_t now) REQUIRES(kMainThreadContext); frontend::Update flushLifecycleUpdates() REQUIRES(kMainThreadContext); - void updateInputFlinger(VsyncId vsyncId, TimePoint frameTime); + void updateInputFlinger(VsyncId vsyncId, TimePoint frameTime) REQUIRES(kMainThreadContext); void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext); void buildWindowInfos(std::vector<gui::WindowInfo>& outWindowInfos, - std::vector<gui::DisplayInfo>& outDisplayInfos); + std::vector<gui::DisplayInfo>& outDisplayInfos) + REQUIRES(kMainThreadContext); void commitInputWindowCommands() REQUIRES(mStateLock); - void updateCursorAsync(); + void updateCursorAsync() REQUIRES(kMainThreadContext); void initScheduler(const sp<const DisplayDevice>&) REQUIRES(kMainThreadContext, mStateLock); @@ -804,18 +806,18 @@ private: const int64_t postTime, bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks, int originPid, int originUid, uint64_t transactionId) - REQUIRES(mStateLock); + REQUIRES(mStateLock, kMainThreadContext); // Flush pending transactions that were presented after desiredPresentTime. // For test only bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext); bool applyTransactions(std::vector<TransactionState>&, VsyncId) REQUIRES(kMainThreadContext); - bool applyAndCommitDisplayTransactionStates(std::vector<TransactionState>& transactions) - REQUIRES(kMainThreadContext); + bool applyAndCommitDisplayTransactionStatesLocked(std::vector<TransactionState>& transactions) + REQUIRES(kMainThreadContext, mStateLock); // Returns true if there is at least one transaction that needs to be flushed - bool transactionFlushNeeded(); - void addTransactionReadyFilters(); + bool transactionFlushNeeded() REQUIRES(kMainThreadContext); + void addTransactionReadyFilters() REQUIRES(kMainThreadContext); TransactionHandler::TransactionReadiness transactionReadyTimelineCheck( const TransactionHandler::TransactionFlushState& flushState) REQUIRES(kMainThreadContext); @@ -832,7 +834,7 @@ private: uint32_t updateLayerCallbacksAndStats(const FrameTimelineInfo&, ResolvedComposerState&, int64_t desiredPresentTime, bool isAutoTimestamp, int64_t postTime, uint64_t transactionId) - REQUIRES(mStateLock); + REQUIRES(mStateLock, kMainThreadContext); uint32_t getTransactionFlags() const; // Sets the masked bits, and schedules a commit if needed. @@ -848,7 +850,7 @@ private: static LatchUnsignaledConfig getLatchUnsignaledConfig(); bool shouldLatchUnsignaled(const layer_state_t&, size_t numStates, bool firstTransaction) const; bool applyTransactionsLocked(std::vector<TransactionState>& transactions, VsyncId) - REQUIRES(mStateLock); + REQUIRES(mStateLock, kMainThreadContext); uint32_t setDisplayStateLocked(const DisplayState& s) REQUIRES(mStateLock); uint32_t addInputWindowCommands(const InputWindowCommands& inputWindowCommands) REQUIRES(mStateLock); @@ -1137,9 +1139,10 @@ private: void dumpHwcLayersMinidumpLockedLegacy(std::string& result) const REQUIRES(mStateLock); void appendSfConfigString(std::string& result) const; - void listLayers(std::string& result) const; - void dumpStats(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock); - void clearStats(const DumpArgs& args, std::string& result); + void listLayers(std::string& result) const REQUIRES(kMainThreadContext); + void dumpStats(const DumpArgs& args, std::string& result) const + REQUIRES(mStateLock, kMainThreadContext); + void clearStats(const DumpArgs& args, std::string& result) REQUIRES(kMainThreadContext); void dumpTimeStats(const DumpArgs& args, bool asProto, std::string& result) const; void dumpFrameTimeline(const DumpArgs& args, std::string& result) const; void logFrameStats(TimePoint now) REQUIRES(kMainThreadContext); @@ -1157,7 +1160,8 @@ private: void dumpFrontEnd(std::string& result) REQUIRES(kMainThreadContext); void dumpVisibleFrontEnd(std::string& result) REQUIRES(mStateLock, kMainThreadContext); - perfetto::protos::LayersProto dumpDrawingStateProto(uint32_t traceFlags) const; + perfetto::protos::LayersProto dumpDrawingStateProto(uint32_t traceFlags) const + REQUIRES(kMainThreadContext); void dumpOffscreenLayersProto(perfetto::protos::LayersProto& layersProto, uint32_t traceFlags = LayerTracing::TRACE_ALL) const; google::protobuf::RepeatedPtrField<perfetto::protos::DisplayProto> dumpDisplayProto() const; @@ -1205,7 +1209,8 @@ private: ui::Rotation getPhysicalDisplayOrientation(DisplayId, bool isPrimary) const REQUIRES(mStateLock); - void traverseLegacyLayers(const LayerVector::Visitor& visitor) const; + void traverseLegacyLayers(const LayerVector::Visitor& visitor) const + REQUIRES(kMainThreadContext); void initBootProperties(); void initTransactionTraceWriter(); @@ -1483,23 +1488,25 @@ private: bool mLayerLifecycleManagerEnabled = false; bool mLegacyFrontEndEnabled = true; - frontend::LayerLifecycleManager mLayerLifecycleManager; - frontend::LayerHierarchyBuilder mLayerHierarchyBuilder; - frontend::LayerSnapshotBuilder mLayerSnapshotBuilder; + frontend::LayerLifecycleManager mLayerLifecycleManager GUARDED_BY(kMainThreadContext); + frontend::LayerHierarchyBuilder mLayerHierarchyBuilder GUARDED_BY(kMainThreadContext); + frontend::LayerSnapshotBuilder mLayerSnapshotBuilder GUARDED_BY(kMainThreadContext); - std::vector<std::pair<uint32_t, std::string>> mDestroyedHandles; - std::vector<std::unique_ptr<frontend::RequestedLayerState>> mNewLayers; - std::vector<LayerCreationArgs> mNewLayerArgs; + std::vector<std::pair<uint32_t, std::string>> mDestroyedHandles GUARDED_BY(mCreatedLayersLock); + std::vector<std::unique_ptr<frontend::RequestedLayerState>> mNewLayers + GUARDED_BY(mCreatedLayersLock); + std::vector<LayerCreationArgs> mNewLayerArgs GUARDED_BY(mCreatedLayersLock); // These classes do not store any client state but help with managing transaction callbacks // and stats. - std::unordered_map<uint32_t, sp<Layer>> mLegacyLayers; + std::unordered_map<uint32_t, sp<Layer>> mLegacyLayers GUARDED_BY(kMainThreadContext); - TransactionHandler mTransactionHandler; - ui::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos; - bool mFrontEndDisplayInfosChanged = false; + TransactionHandler mTransactionHandler GUARDED_BY(kMainThreadContext); + ui::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos + GUARDED_BY(kMainThreadContext); + bool mFrontEndDisplayInfosChanged GUARDED_BY(kMainThreadContext) = false; // WindowInfo ids visible during the last commit. - std::unordered_set<int32_t> mVisibleWindowIds; + std::unordered_set<int32_t> mVisibleWindowIds GUARDED_BY(kMainThreadContext); // Mirroring // Map of displayid to mirrorRoot @@ -1601,7 +1608,6 @@ public: binder::Status overrideHdrTypes(const sp<IBinder>& display, const std::vector<int32_t>& hdrTypes) override; binder::Status onPullAtom(int32_t atomId, gui::PullAtomData* outPullData) override; - binder::Status getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers) override; binder::Status getCompositionPreference(gui::CompositionPreference* outPref) override; binder::Status getDisplayedContentSamplingAttributes( const sp<IBinder>& display, gui::ContentSamplingAttributes* outAttrs) override; diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h index 31cd2d7eb6..89a8f9238b 100644 --- a/services/surfaceflinger/TransactionState.h +++ b/services/surfaceflinger/TransactionState.h @@ -86,7 +86,7 @@ struct TransactionState { } template <typename Visitor> - void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) { + void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) NO_THREAD_SAFETY_ANALYSIS { for (auto state = states.begin(); state != states.end();) { if (state->state.hasBufferChanges() && state->externalTexture && state->state.surface) { int result = visitor(*state); diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp index 6507abad4d..5a54f7d7d3 100644 --- a/services/surfaceflinger/common/FlagManager.cpp +++ b/services/surfaceflinger/common/FlagManager.cpp @@ -139,6 +139,10 @@ void FlagManager::dump(std::string& result) const { DUMP_READ_ONLY_FLAG(protected_if_client); DUMP_READ_ONLY_FLAG(ce_fence_promise); DUMP_READ_ONLY_FLAG(idle_screen_refresh_rate_timeout); + DUMP_READ_ONLY_FLAG(graphite_renderengine); + DUMP_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed); + DUMP_READ_ONLY_FLAG(deprecate_vsync_sf); + #undef DUMP_READ_ONLY_FLAG #undef DUMP_SERVER_FLAG #undef DUMP_FLAG_INTERVAL @@ -227,6 +231,9 @@ FLAG_MANAGER_READ_ONLY_FLAG(restore_blur_step, "debug.renderengine.restore_blur_ FLAG_MANAGER_READ_ONLY_FLAG(dont_skip_on_early_ro, "") FLAG_MANAGER_READ_ONLY_FLAG(protected_if_client, "") FLAG_MANAGER_READ_ONLY_FLAG(ce_fence_promise, ""); +FLAG_MANAGER_READ_ONLY_FLAG(graphite_renderengine, "debug.renderengine.graphite") +FLAG_MANAGER_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed, ""); +FLAG_MANAGER_READ_ONLY_FLAG(deprecate_vsync_sf, ""); /// Trunk stable server flags /// FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "") diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h index 964943ca3f..0c1f9fb2fc 100644 --- a/services/surfaceflinger/common/include/common/FlagManager.h +++ b/services/surfaceflinger/common/include/common/FlagManager.h @@ -77,6 +77,9 @@ public: bool protected_if_client() const; bool ce_fence_promise() const; bool idle_screen_refresh_rate_timeout() const; + bool graphite_renderengine() const; + bool latch_unsignaled_with_auto_refresh_changed() const; + bool deprecate_vsync_sf() const; protected: // overridden for unit tests diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig index a8fd6b7df7..4a609878a2 100644 --- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig +++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig @@ -21,12 +21,16 @@ flag { } } # ce_fence_promise -flag { - name: "dont_skip_on_early_ro2" + flag { + name: "deprecate_vsync_sf" 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" -} # dont_skip_on_early_ro2 + description: "Depracate eVsyncSourceSurfaceFlinger and use vsync_app everywhere" + bug: "162235855" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} # deprecate_vsync_sf flag { name: "frame_rate_category_mrr" @@ -39,4 +43,15 @@ flag { } } # frame_rate_category_mrr +flag { + name: "latch_unsignaled_with_auto_refresh_changed" + namespace: "core_graphics" + description: "Ignore eAutoRefreshChanged with latch unsignaled" + bug: "331513837" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} # latch_unsignaled_with_auto_refresh_changed + # IMPORTANT - please keep alphabetize to reduce merge conflicts diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp index 9b83713f50..3b6a51a224 100644 --- a/services/surfaceflinger/tests/Credentials_test.cpp +++ b/services/surfaceflinger/tests/Credentials_test.cpp @@ -21,7 +21,6 @@ #include <android/gui/ISurfaceComposer.h> #include <gtest/gtest.h> #include <gui/AidlStatusUtil.h> -#include <gui/LayerDebugInfo.h> #include <gui/Surface.h> #include <gui/SurfaceComposerClient.h> #include <private/android_filesystem_config.h> @@ -36,7 +35,6 @@ namespace android { using Transaction = SurfaceComposerClient::Transaction; -using gui::LayerDebugInfo; using gui::aidl_utils::statusTFromBinderStatus; using ui::ColorMode; @@ -292,35 +290,6 @@ TEST_F(CredentialsTest, CaptureLayersTest) { /** * The following tests are for methods accessible directly through SurfaceFlinger. */ -TEST_F(CredentialsTest, GetLayerDebugInfo) { - setupBackgroundSurface(); - sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService()); - - // Historically, only root and shell can access the getLayerDebugInfo which - // is called when we call dumpsys. I don't see a reason why we should change this. - std::vector<LayerDebugInfo> outLayers; - binder::Status status = binder::Status::ok(); - // Check with root. - { - UIDFaker f(AID_ROOT); - status = sf->getLayerDebugInfo(&outLayers); - ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); - } - - // Check as a shell. - { - UIDFaker f(AID_SHELL); - status = sf->getLayerDebugInfo(&outLayers); - ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); - } - - // Check as anyone else. - { - UIDFaker f(AID_BIN); - status = sf->getLayerDebugInfo(&outLayers); - ASSERT_EQ(PERMISSION_DENIED, statusTFromBinderStatus(status)); - } -} TEST_F(CredentialsTest, IsWideColorDisplayBasicCorrectness) { const auto display = getFirstDisplayToken(); diff --git a/services/surfaceflinger/tests/LayerTransactionTest.h b/services/surfaceflinger/tests/LayerTransactionTest.h index c9af432513..5b056d0765 100644 --- a/services/surfaceflinger/tests/LayerTransactionTest.h +++ b/services/surfaceflinger/tests/LayerTransactionTest.h @@ -106,6 +106,10 @@ protected: return colorLayer; } + sp<SurfaceControl> mirrorSurface(SurfaceControl* mirrorFromSurface) { + return mClient->mirrorSurface(mirrorFromSurface); + } + ANativeWindow_Buffer getBufferQueueLayerBuffer(const sp<SurfaceControl>& layer) { // wait for previous transactions (such as setSize) to complete Transaction().apply(true); diff --git a/services/surfaceflinger/tests/ScreenCapture_test.cpp b/services/surfaceflinger/tests/ScreenCapture_test.cpp index 18262f664a..9a78550d00 100644 --- a/services/surfaceflinger/tests/ScreenCapture_test.cpp +++ b/services/surfaceflinger/tests/ScreenCapture_test.cpp @@ -1039,6 +1039,29 @@ TEST_F(ScreenCaptureTest, CaptureHdrLayer) { ASSERT_TRUE(mCapture->capturedHdrLayers()); } +TEST_F(ScreenCaptureTest, captureOffscreenNullSnapshot) { + sp<SurfaceControl> layer; + ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 32, 32, + ISurfaceComposerClient::eFXSurfaceBufferState, + mBGSurfaceControl.get())); + + // A mirrored layer will not have a snapshot. Testing an offscreen mirrored layer + // ensures that the screenshot path handles cases where snapshots are null. + sp<SurfaceControl> mirroredLayer; + ASSERT_NO_FATAL_FAILURE(mirroredLayer = mirrorSurface(layer.get())); + + LayerCaptureArgs captureArgs; + captureArgs.layerHandle = mirroredLayer->getHandle(); + captureArgs.sourceCrop = Rect(0, 0, 1, 1); + + // Screenshot path should only use the children of the layer hierarchy so + // that it will not create a new snapshot. A snapshot would otherwise be + // created to pass on the properties of the parent, which is not needed + // for the purposes of this test since we explicitly want a null snapshot. + captureArgs.childrenOnly = true; + ScreenCapture::captureLayers(&mCapture, captureArgs); +} + // In the following tests we verify successful skipping of a parent layer, // so we use the same verification logic and only change how we mutate // the parent layer to verify that various properties are ignored. diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp index 3eabe1f362..625d2e68d8 100644 --- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp +++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp @@ -493,7 +493,7 @@ TEST_F(EventThreadTest, getLatestVsyncEventData) { EXPECT_CALL(mockTracker, nextAnticipatedVSyncTimeFrom(_, _)) .WillOnce(Return(preferredExpectedPresentationTime)); - VsyncEventData vsyncEventData = mThread->getLatestVsyncEventData(mConnection); + VsyncEventData vsyncEventData = mThread->getLatestVsyncEventData(mConnection, now); // Check EventThread immediately requested a resync. EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value()); diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h index 67e624922c..e8e7667a66 100644 --- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h +++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h @@ -281,6 +281,24 @@ protected: mLifecycleManager.applyTransactions(transactions); } + void setInputInfo(uint32_t id, std::function<void(gui::WindowInfo&)> configureInput) { + 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 = id; + transactions.back().states.front().state.windowInfoHandle = + sp<gui::WindowInfoHandle>::make(); + auto inputInfo = transactions.back().states.front().state.windowInfoHandle->editInfo(); + if (!inputInfo->token) { + inputInfo->token = sp<BBinder>::make(); + } + configureInput(*inputInfo); + + mLifecycleManager.applyTransactions(transactions); + } + void setTouchableRegionCrop(uint32_t id, Region region, uint32_t touchCropId, bool replaceTouchableRegionWithCrop) { std::vector<TransactionState> transactions; diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index 94989aac84..ae9a89c0b6 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -1198,6 +1198,42 @@ TEST_F(LayerSnapshotTest, setSecureRootSnapshot) { EXPECT_TRUE(getSnapshot(11)->isSecure); } +TEST_F(LayerSnapshotTest, setSensitiveForTracingConfigForSecureLayers) { + setFlags(11, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure); + + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + + EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + EXPECT_FALSE(getSnapshot(1)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + EXPECT_FALSE(getSnapshot(12)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + EXPECT_FALSE(getSnapshot(2)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); +} + +TEST_F(LayerSnapshotTest, setSensitiveForTracingFromInputWindowHandle) { + setInputInfo(11, [](auto& inputInfo) { + inputInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING; + }); + + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + + EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + EXPECT_FALSE(getSnapshot(1)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + EXPECT_FALSE(getSnapshot(12)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + EXPECT_FALSE(getSnapshot(2)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); +} + // b/314350323 TEST_F(LayerSnapshotTest, propagateDropInputMode) { setDropInputMode(1, gui::DropInputMode::ALL); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 82023b092a..83e3245740 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -492,9 +492,11 @@ public: auto& getTransactionQueue() { return mFlinger->mTransactionHandler.mLocklessTransactionQueue; } auto& getPendingTransactionQueue() { + ftl::FakeGuard guard(kMainThreadContext); return mFlinger->mTransactionHandler.mPendingTransactionQueues; } size_t getPendingTransactionCount() { + ftl::FakeGuard guard(kMainThreadContext); return mFlinger->mTransactionHandler.mPendingTransactionCount.load(); } @@ -513,7 +515,9 @@ public: } auto setTransactionStateInternal(TransactionState& transaction) { - return mFlinger->mTransactionHandler.queueTransaction(std::move(transaction)); + return FTL_FAKE_GUARD(kMainThreadContext, + mFlinger->mTransactionHandler.queueTransaction( + std::move(transaction))); } auto flushTransactionQueues() { @@ -598,15 +602,20 @@ public: } void injectLegacyLayer(sp<Layer> layer) { - mFlinger->mLegacyLayers[static_cast<uint32_t>(layer->sequence)] = layer; + FTL_FAKE_GUARD(kMainThreadContext, + mFlinger->mLegacyLayers[static_cast<uint32_t>(layer->sequence)] = layer); }; - void releaseLegacyLayer(uint32_t sequence) { mFlinger->mLegacyLayers.erase(sequence); }; + void releaseLegacyLayer(uint32_t sequence) { + FTL_FAKE_GUARD(kMainThreadContext, mFlinger->mLegacyLayers.erase(sequence)); + }; auto setLayerHistoryDisplayArea(uint32_t displayArea) { return mFlinger->mScheduler->onActiveDisplayAreaChanged(displayArea); }; - auto updateLayerHistory(nsecs_t now) { return mFlinger->updateLayerHistory(now); }; + auto updateLayerHistory(nsecs_t now) { + return FTL_FAKE_GUARD(kMainThreadContext, mFlinger->updateLayerHistory(now)); + }; auto setDaltonizerType(ColorBlindnessType type) { mFlinger->mDaltonizer.setType(type); return mFlinger->updateColorMatrixLocked(); diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index 1f2a1edb77..7fb9247ee0 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -17,6 +17,7 @@ #undef LOG_TAG #define LOG_TAG "TransactionApplicationTest" +#include <common/test/FlagUtils.h> #include <compositionengine/Display.h> #include <compositionengine/mock/DisplaySurface.h> #include <gmock/gmock.h> @@ -34,8 +35,11 @@ #include "TestableSurfaceFlinger.h" #include "TransactionState.h" +#include <com_android_graphics_surfaceflinger_flags.h> + namespace android { +using namespace com::android::graphics::surfaceflinger; using testing::_; using testing::Return; @@ -498,6 +502,44 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBu setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); } +TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_AutoRefreshChanged) { + SET_FLAG_FOR_TEST(flags::latch_unsignaled_with_auto_refresh_changed, false); + const sp<IBinder> kApplyToken = + IInterface::asBinder(TransactionCompletedListener::getIInstance()); + const auto kLayerId = 1; + const auto kExpectedTransactionsPending = 1u; + + const auto unsignaledTransaction = + createTransactionInfo(kApplyToken, + { + createComposerState(kLayerId, + fence(Fence::Status::Unsignaled), + layer_state_t::eAutoRefreshChanged | + layer_state_t:: + eBufferChanged), + }); + setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); +} + +TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesUnSignaledInTheQueue_AutoRefreshChanged) { + SET_FLAG_FOR_TEST(flags::latch_unsignaled_with_auto_refresh_changed, true); + const sp<IBinder> kApplyToken = + IInterface::asBinder(TransactionCompletedListener::getIInstance()); + const auto kLayerId = 1; + const auto kExpectedTransactionsPending = 0u; + + const auto unsignaledTransaction = + createTransactionInfo(kApplyToken, + { + createComposerState(kLayerId, + fence(Fence::Status::Unsignaled), + layer_state_t::eAutoRefreshChanged | + layer_state_t:: + eBufferChanged), + }); + setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); +} + TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBufferChangeClubed) { const sp<IBinder> kApplyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp index d701a97b7d..3b095545e9 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp @@ -54,6 +54,7 @@ public: void onFrameBegin(TimePoint, TimePoint) final {} void onFrameMissed(TimePoint) final {} void dump(std::string&) const final {} + bool isCurrentMode(const ftl::NonNull<DisplayModePtr>&) const final { return false; }; protected: std::mutex mutable mMutex; diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp index e3aa4ef77b..51373c11f0 100644 --- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp @@ -230,7 +230,8 @@ TEST_F(VSyncReactorTest, setPeriodCalledOnceConfirmedChange) { TEST_F(VSyncReactorTest, changingPeriodBackAbortsConfirmationProcess) { nsecs_t sampleTime = 0; nsecs_t const newPeriod = 5000; - mReactor.onDisplayModeChanged(displayMode(newPeriod), false); + auto modePtr = displayMode(newPeriod); + mReactor.onDisplayModeChanged(modePtr, false); bool periodFlushed = true; EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); @@ -238,7 +239,9 @@ TEST_F(VSyncReactorTest, changingPeriodBackAbortsConfirmationProcess) { EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); - mReactor.onDisplayModeChanged(displayMode(period), false); + modePtr = displayMode(period); + EXPECT_CALL(*mMockTracker, isCurrentMode(modePtr)).WillOnce(Return(true)); + mReactor.onDisplayModeChanged(modePtr, false); EXPECT_FALSE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); } diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h index e2b0ed1df9..8dd1a34ec4 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h +++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h @@ -55,7 +55,7 @@ public: (override)); MOCK_METHOD(void, requestNextVsync, (const sp<android::EventThreadConnection>&), (override)); MOCK_METHOD(VsyncEventData, getLatestVsyncEventData, - (const sp<android::EventThreadConnection>&), (const, override)); + (const sp<android::EventThreadConnection>&, nsecs_t), (const, override)); MOCK_METHOD(void, requestLatestConfig, (const sp<android::EventThreadConnection>&)); MOCK_METHOD(void, pauseVsyncCallback, (bool)); MOCK_METHOD(void, onNewVsyncSchedule, (std::shared_ptr<scheduler::VsyncSchedule>), (override)); diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h index c311901c7a..4f44d1b4fc 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h +++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h @@ -40,6 +40,7 @@ public: MOCK_METHOD(void, onFrameBegin, (TimePoint, TimePoint), (override)); MOCK_METHOD(void, onFrameMissed, (TimePoint), (override)); MOCK_METHOD(void, dump, (std::string&), (const, override)); + MOCK_METHOD(bool, isCurrentMode, (const ftl::NonNull<DisplayModePtr>&), (const, override)); }; } // namespace android::mock diff --git a/services/vibratorservice/TEST_MAPPING b/services/vibratorservice/TEST_MAPPING index 63a2bd0059..81c37b0a40 100644 --- a/services/vibratorservice/TEST_MAPPING +++ b/services/vibratorservice/TEST_MAPPING @@ -3,9 +3,13 @@ { "name": "libvibratorservice_test", "options": [ - // TODO(b/293603710): Fix flakiness { + // TODO(b/293603710): Fix flakiness "exclude-filter": "VibratorCallbackSchedulerTest#TestScheduleRunsOnlyAfterDelay" + }, + { + // TODO(b/293623689): Fix flakiness + "exclude-filter": "VibratorCallbackSchedulerTest#TestScheduleMultipleCallbacksRunsInDelayOrder" } ] } diff --git a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp index e11a809bc1..42aec7d558 100644 --- a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp +++ b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp @@ -17,7 +17,9 @@ #define LOG_TAG "VibratorHalControllerBenchmarks" #include <benchmark/benchmark.h> +#include <binder/ProcessState.h> #include <vibratorservice/VibratorHalController.h> +#include <future> using ::android::enum_range; using ::android::hardware::vibrator::CompositeEffect; @@ -30,14 +32,90 @@ using ::benchmark::kMicrosecond; using ::benchmark::State; using ::benchmark::internal::Benchmark; +using std::chrono::milliseconds; + using namespace android; using namespace std::chrono_literals; +// Fixed number of iterations for benchmarks that trigger a vibration on the loop. +// They require slow cleanup to ensure a stable state on each run and less noisy metrics. +static constexpr auto VIBRATION_ITERATIONS = 500; + +// Timeout to wait for vibration callback completion. +static constexpr auto VIBRATION_CALLBACK_TIMEOUT = 100ms; + +// Max duration the vibrator can be turned on, in milliseconds. +static constexpr auto MAX_ON_DURATION_MS = milliseconds(UINT16_MAX); + +// Helper to wait for the vibrator to become idle between vibrate bench iterations. +class HalCallback { +public: + HalCallback(std::function<void()>&& waitFn, std::function<void()>&& completeFn) + : mWaitFn(std::move(waitFn)), mCompleteFn(std::move(completeFn)) {} + ~HalCallback() = default; + + std::function<void()> completeFn() const { return mCompleteFn; } + + void waitForComplete() const { mWaitFn(); } + +private: + std::function<void()> mWaitFn; + std::function<void()> mCompleteFn; +}; + +// Helper for vibration callbacks, kept by the Fixture until all pending callbacks are done. +class HalCallbacks { +public: + HalCallback next() { + auto id = mCurrentId++; + mPendingPromises[id] = std::promise<void>(); + mPendingFutures[id] = mPendingPromises[id].get_future(); // Can only be called once. + return HalCallback([&, id]() { waitForComplete(id); }, [&, id]() { onComplete(id); }); + } + + void onComplete(int32_t id) { + mPendingPromises[id].set_value(); + mPendingPromises.erase(id); + } + + void waitForComplete(int32_t id) { + // Wait until the HAL has finished processing previous vibration before starting a new one, + // so the HAL state is consistent on each run and metrics are less noisy. Some of the newest + // HAL implementations are waiting on previous vibration cleanup and might be significantly + // slower, so make sure we measure vibrations on a clean slate. + if (mPendingFutures[id].wait_for(VIBRATION_CALLBACK_TIMEOUT) == std::future_status::ready) { + mPendingFutures.erase(id); + } + } + + void waitForPending() { + // Wait for pending callbacks from the test, possibly skipped with error. + for (auto& [id, future] : mPendingFutures) { + future.wait_for(VIBRATION_CALLBACK_TIMEOUT); + } + mPendingFutures.clear(); + mPendingPromises.clear(); + } + +private: + std::map<int32_t, std::promise<void>> mPendingPromises; + std::map<int32_t, std::future<void>> mPendingFutures; + int32_t mCurrentId; +}; + class VibratorBench : public Fixture { public: - void SetUp(State& /*state*/) override { mController.init(); } + void SetUp(State& /*state*/) override { + android::ProcessState::self()->setThreadPoolMaxThreadCount(1); + android::ProcessState::self()->startThreadPool(); + mController.init(); + } - void TearDown(State& state) override { turnVibratorOff(state); } + void TearDown(State& /*state*/) override { + turnVibratorOff(); + disableExternalControl(); + mCallbacks.waitForPending(); + } static void DefaultConfig(Benchmark* b) { b->Unit(kMicrosecond); } @@ -47,38 +125,59 @@ public: protected: vibrator::HalController mController; + HalCallbacks mCallbacks; + + static void SlowBenchConfig(Benchmark* b) { b->Iterations(VIBRATION_ITERATIONS); } auto getOtherArg(const State& state, std::size_t index) const { return state.range(index + 0); } - bool hasCapabilities(vibrator::Capabilities&& query, State& state) { + vibrator::HalResult<void> turnVibratorOff() { + return mController.doWithRetry<void>([](auto hal) { return hal->off(); }, "off"); + } + + vibrator::HalResult<void> disableExternalControl() { + auto disableExternalControlFn = [](auto hal) { return hal->setExternalControl(false); }; + return mController.doWithRetry<void>(disableExternalControlFn, "setExternalControl false"); + } + + bool shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities query, State& state) { auto result = mController.getInfo().capabilities; if (result.isFailed()) { state.SkipWithError(result.errorMessage()); - return false; + return true; } if (!result.isOk()) { - return false; + state.SkipWithMessage("capability result is unsupported"); + return true; } - return (result.value() & query) == query; + if ((result.value() & query) != query) { + state.SkipWithMessage("missing capability"); + return true; + } + return false; } - void turnVibratorOff(State& state) { - checkHalResult(halCall<void>(mController, [](auto hal) { return hal->off(); }), state); + template <class R> + bool shouldSkipWithError(const vibrator::HalFunction<vibrator::HalResult<R>>& halFn, + const char* label, State& state) { + return shouldSkipWithError(mController.doWithRetry<R>(halFn, label), state); } template <class R> - bool checkHalResult(const vibrator::HalResult<R>& result, State& state) { + bool shouldSkipWithError(const vibrator::HalResult<R>& result, State& state) { if (result.isFailed()) { state.SkipWithError(result.errorMessage()); - return false; + return true; } - return true; + return false; } +}; - template <class R> - vibrator::HalResult<R> halCall(vibrator::HalController& controller, - const vibrator::HalFunction<vibrator::HalResult<R>>& halFn) { - return controller.doWithRetry<R>(halFn, "benchmark"); +class SlowVibratorBench : public VibratorBench { +public: + static void DefaultConfig(Benchmark* b) { + VibratorBench::DefaultConfig(b); + SlowBenchConfig(b); } }; @@ -91,25 +190,32 @@ protected: BENCHMARK_WRAPPER(VibratorBench, init, { for (auto _ : state) { + // Setup state.PauseTiming(); vibrator::HalController controller; state.ResumeTiming(); + + // Test controller.init(); } }); BENCHMARK_WRAPPER(VibratorBench, initCached, { + // First call to cache values. + mController.init(); + for (auto _ : state) { mController.init(); } }); BENCHMARK_WRAPPER(VibratorBench, ping, { + auto pingFn = [](auto hal) { return hal->ping(); }; + for (auto _ : state) { - state.ResumeTiming(); - auto ret = halCall<void>(mController, [](auto hal) { return hal->ping(); }); - state.PauseTiming(); - checkHalResult(ret, state); + if (shouldSkipWithError<void>(pingFn, "ping", state)) { + return; + } } }); @@ -119,164 +225,131 @@ BENCHMARK_WRAPPER(VibratorBench, tryReconnect, { } }); -BENCHMARK_WRAPPER(VibratorBench, on, { - auto duration = 60s; - auto callback = []() {}; +BENCHMARK_WRAPPER(SlowVibratorBench, on, { + auto duration = MAX_ON_DURATION_MS; for (auto _ : state) { - state.ResumeTiming(); - auto ret = - halCall<void>(mController, [&](auto hal) { return hal->on(duration, callback); }); + // Setup state.PauseTiming(); - if (checkHalResult(ret, state)) { - turnVibratorOff(state); - } - } -}); + auto cb = mCallbacks.next(); + auto onFn = [&](auto hal) { return hal->on(duration, cb.completeFn()); }; + state.ResumeTiming(); -BENCHMARK_WRAPPER(VibratorBench, off, { - auto duration = 60s; - auto callback = []() {}; + // Test + if (shouldSkipWithError<void>(onFn, "on", state)) { + return; + } - for (auto _ : state) { + // Cleanup state.PauseTiming(); - auto ret = - halCall<void>(mController, [&](auto hal) { return hal->on(duration, callback); }); - if (!checkHalResult(ret, state)) { - continue; + if (shouldSkipWithError(turnVibratorOff(), state)) { + return; } + cb.waitForComplete(); state.ResumeTiming(); - turnVibratorOff(state); } }); -BENCHMARK_WRAPPER(VibratorBench, setAmplitude, { - if (!hasCapabilities(vibrator::Capabilities::AMPLITUDE_CONTROL, state)) { - state.SkipWithMessage("missing capability"); - return; - } - - auto duration = 60s; - auto callback = []() {}; - auto amplitude = 1.0f; +BENCHMARK_WRAPPER(SlowVibratorBench, off, { + auto duration = MAX_ON_DURATION_MS; for (auto _ : state) { + // Setup state.PauseTiming(); - vibrator::HalController controller; - controller.init(); - auto result = - halCall<void>(controller, [&](auto hal) { return hal->on(duration, callback); }); - if (!checkHalResult(result, state)) { - continue; + auto cb = mCallbacks.next(); + auto onFn = [&](auto hal) { return hal->on(duration, cb.completeFn()); }; + if (shouldSkipWithError<void>(onFn, "on", state)) { + return; } + auto offFn = [&](auto hal) { return hal->off(); }; state.ResumeTiming(); - auto ret = - halCall<void>(controller, [&](auto hal) { return hal->setAmplitude(amplitude); }); - state.PauseTiming(); - if (checkHalResult(ret, state)) { - turnVibratorOff(state); + + // Test + if (shouldSkipWithError<void>(offFn, "off", state)) { + return; } + + // Cleanup + state.PauseTiming(); + cb.waitForComplete(); + state.ResumeTiming(); } }); -BENCHMARK_WRAPPER(VibratorBench, setAmplitudeCached, { - if (!hasCapabilities(vibrator::Capabilities::AMPLITUDE_CONTROL, state)) { - state.SkipWithMessage("missing capability"); +BENCHMARK_WRAPPER(VibratorBench, setAmplitude, { + if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::AMPLITUDE_CONTROL, state)) { return; } - auto duration = 60s; - auto callback = []() {}; + auto duration = MAX_ON_DURATION_MS; auto amplitude = 1.0f; + auto setAmplitudeFn = [&](auto hal) { return hal->setAmplitude(amplitude); }; - auto onResult = - halCall<void>(mController, [&](auto hal) { return hal->on(duration, callback); }); - checkHalResult(onResult, state); - - for (auto _ : state) { - auto ret = - halCall<void>(mController, [&](auto hal) { return hal->setAmplitude(amplitude); }); - checkHalResult(ret, state); - } -}); - -BENCHMARK_WRAPPER(VibratorBench, setExternalControl, { - if (!hasCapabilities(vibrator::Capabilities::EXTERNAL_CONTROL, state)) { - state.SkipWithMessage("missing capability"); + auto onFn = [&](auto hal) { return hal->on(duration, [&]() {}); }; + if (shouldSkipWithError<void>(onFn, "on", state)) { return; } for (auto _ : state) { - state.PauseTiming(); - vibrator::HalController controller; - controller.init(); - state.ResumeTiming(); - auto ret = - halCall<void>(controller, [](auto hal) { return hal->setExternalControl(true); }); - state.PauseTiming(); - if (checkHalResult(ret, state)) { - auto result = halCall<void>(controller, - [](auto hal) { return hal->setExternalControl(false); }); - checkHalResult(result, state); + if (shouldSkipWithError<void>(setAmplitudeFn, "setAmplitude", state)) { + return; } } }); -BENCHMARK_WRAPPER(VibratorBench, setExternalControlCached, { - if (!hasCapabilities(vibrator::Capabilities::EXTERNAL_CONTROL, state)) { - state.SkipWithMessage("missing capability"); +BENCHMARK_WRAPPER(VibratorBench, setExternalControl, { + if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::EXTERNAL_CONTROL, state)) { return; } + auto enableExternalControlFn = [](auto hal) { return hal->setExternalControl(true); }; + for (auto _ : state) { - state.ResumeTiming(); - auto result = - halCall<void>(mController, [](auto hal) { return hal->setExternalControl(true); }); + // Test + if (shouldSkipWithError<void>(enableExternalControlFn, "setExternalControl true", state)) { + return; + } + + // Cleanup state.PauseTiming(); - if (checkHalResult(result, state)) { - auto ret = halCall<void>(mController, - [](auto hal) { return hal->setExternalControl(false); }); - checkHalResult(ret, state); + if (shouldSkipWithError(disableExternalControl(), state)) { + return; } + state.ResumeTiming(); } }); -BENCHMARK_WRAPPER(VibratorBench, setExternalAmplitudeCached, { - if (!hasCapabilities(vibrator::Capabilities::EXTERNAL_AMPLITUDE_CONTROL, state)) { - state.SkipWithMessage("missing capability"); +BENCHMARK_WRAPPER(VibratorBench, setExternalAmplitude, { + auto externalAmplitudeControl = vibrator::Capabilities::EXTERNAL_CONTROL & + vibrator::Capabilities::EXTERNAL_AMPLITUDE_CONTROL; + if (shouldSkipWithMissingCapabilityMessage(externalAmplitudeControl, state)) { return; } auto amplitude = 1.0f; + auto setAmplitudeFn = [&](auto hal) { return hal->setAmplitude(amplitude); }; + auto enableExternalControlFn = [](auto hal) { return hal->setExternalControl(true); }; - auto onResult = - halCall<void>(mController, [](auto hal) { return hal->setExternalControl(true); }); - checkHalResult(onResult, state); + if (shouldSkipWithError<void>(enableExternalControlFn, "setExternalControl true", state)) { + return; + } for (auto _ : state) { - auto ret = - halCall<void>(mController, [&](auto hal) { return hal->setAmplitude(amplitude); }); - checkHalResult(ret, state); + if (shouldSkipWithError<void>(setAmplitudeFn, "setExternalAmplitude", state)) { + return; + } } - - auto offResult = - halCall<void>(mController, [](auto hal) { return hal->setExternalControl(false); }); - checkHalResult(offResult, state); }); BENCHMARK_WRAPPER(VibratorBench, getInfo, { for (auto _ : state) { + // Setup state.PauseTiming(); vibrator::HalController controller; controller.init(); state.ResumeTiming(); - auto result = controller.getInfo(); - checkHalResult(result.capabilities, state); - checkHalResult(result.supportedEffects, state); - checkHalResult(result.supportedPrimitives, state); - checkHalResult(result.primitiveDurations, state); - checkHalResult(result.resonantFrequency, state); - checkHalResult(result.qFactor, state); + + controller.getInfo(); } }); @@ -285,13 +358,7 @@ BENCHMARK_WRAPPER(VibratorBench, getInfoCached, { mController.getInfo(); for (auto _ : state) { - auto result = mController.getInfo(); - checkHalResult(result.capabilities, state); - checkHalResult(result.supportedEffects, state); - checkHalResult(result.supportedPrimitives, state); - checkHalResult(result.primitiveDurations, state); - checkHalResult(result.resonantFrequency, state); - checkHalResult(result.qFactor, state); + mController.getInfo(); } }); @@ -334,9 +401,16 @@ protected: } }; +class SlowVibratorEffectsBench : public VibratorEffectsBench { +public: + static void DefaultConfig(Benchmark* b) { + VibratorBench::DefaultConfig(b); + SlowBenchConfig(b); + } +}; + BENCHMARK_WRAPPER(VibratorEffectsBench, alwaysOnEnable, { - if (!hasCapabilities(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) { - state.SkipWithMessage("missing capability"); + if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) { return; } if (!hasArgs(state)) { @@ -347,24 +421,26 @@ BENCHMARK_WRAPPER(VibratorEffectsBench, alwaysOnEnable, { int32_t id = 1; auto effect = getEffect(state); auto strength = getStrength(state); + auto enableFn = [&](auto hal) { return hal->alwaysOnEnable(id, effect, strength); }; + auto disableFn = [&](auto hal) { return hal->alwaysOnDisable(id); }; for (auto _ : state) { - state.ResumeTiming(); - auto ret = halCall<void>(mController, [&](auto hal) { - return hal->alwaysOnEnable(id, effect, strength); - }); + // Test + if (shouldSkipWithError<void>(enableFn, "alwaysOnEnable", state)) { + return; + } + + // Cleanup state.PauseTiming(); - if (checkHalResult(ret, state)) { - auto disableResult = - halCall<void>(mController, [&](auto hal) { return hal->alwaysOnDisable(id); }); - checkHalResult(disableResult, state); + if (shouldSkipWithError<void>(disableFn, "alwaysOnDisable", state)) { + return; } + state.ResumeTiming(); } }); BENCHMARK_WRAPPER(VibratorEffectsBench, alwaysOnDisable, { - if (!hasCapabilities(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) { - state.SkipWithMessage("missing capability"); + if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) { return; } if (!hasArgs(state)) { @@ -375,23 +451,25 @@ BENCHMARK_WRAPPER(VibratorEffectsBench, alwaysOnDisable, { int32_t id = 1; auto effect = getEffect(state); auto strength = getStrength(state); + auto enableFn = [&](auto hal) { return hal->alwaysOnEnable(id, effect, strength); }; + auto disableFn = [&](auto hal) { return hal->alwaysOnDisable(id); }; for (auto _ : state) { + // Setup state.PauseTiming(); - auto enableResult = halCall<void>(mController, [&](auto hal) { - return hal->alwaysOnEnable(id, effect, strength); - }); - if (!checkHalResult(enableResult, state)) { - continue; + if (shouldSkipWithError<void>(enableFn, "alwaysOnEnable", state)) { + return; } state.ResumeTiming(); - auto disableResult = - halCall<void>(mController, [&](auto hal) { return hal->alwaysOnDisable(id); }); - checkHalResult(disableResult, state); + + // Test + if (shouldSkipWithError<void>(disableFn, "alwaysOnDisable", state)) { + return; + } } }); -BENCHMARK_WRAPPER(VibratorEffectsBench, performEffect, { +BENCHMARK_WRAPPER(SlowVibratorEffectsBench, performEffect, { if (!hasArgs(state)) { state.SkipWithMessage("missing args"); return; @@ -399,22 +477,38 @@ BENCHMARK_WRAPPER(VibratorEffectsBench, performEffect, { auto effect = getEffect(state); auto strength = getStrength(state); - auto callback = []() {}; for (auto _ : state) { + // Setup + state.PauseTiming(); + auto cb = mCallbacks.next(); + auto performFn = [&](auto hal) { + return hal->performEffect(effect, strength, cb.completeFn()); + }; state.ResumeTiming(); - auto ret = halCall<std::chrono::milliseconds>(mController, [&](auto hal) { - return hal->performEffect(effect, strength, callback); - }); + + // Test + if (shouldSkipWithError<milliseconds>(performFn, "performEffect", state)) { + return; + } + + // Cleanup state.PauseTiming(); - if (checkHalResult(ret, state)) { - turnVibratorOff(state); + if (shouldSkipWithError(turnVibratorOff(), state)) { + return; } + cb.waitForComplete(); + state.ResumeTiming(); } }); -class VibratorPrimitivesBench : public VibratorBench { +class SlowVibratorPrimitivesBench : public VibratorBench { public: + static void DefaultConfig(Benchmark* b) { + VibratorBench::DefaultConfig(b); + SlowBenchConfig(b); + } + static void DefaultArgs(Benchmark* b) { vibrator::HalController controller; auto primitivesResult = controller.getInfo().supportedPrimitives; @@ -449,9 +543,8 @@ protected: } }; -BENCHMARK_WRAPPER(VibratorPrimitivesBench, performComposedEffect, { - if (!hasCapabilities(vibrator::Capabilities::COMPOSE_EFFECTS, state)) { - state.SkipWithMessage("missing capability"); +BENCHMARK_WRAPPER(SlowVibratorPrimitivesBench, performComposedEffect, { + if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::COMPOSE_EFFECTS, state)) { return; } if (!hasArgs(state)) { @@ -464,19 +557,29 @@ BENCHMARK_WRAPPER(VibratorPrimitivesBench, performComposedEffect, { effect.scale = 1.0f; effect.delayMs = static_cast<int32_t>(0); - std::vector<CompositeEffect> effects; - effects.push_back(effect); - auto callback = []() {}; + std::vector<CompositeEffect> effects = {effect}; for (auto _ : state) { + // Setup + state.PauseTiming(); + auto cb = mCallbacks.next(); + auto performFn = [&](auto hal) { + return hal->performComposedEffect(effects, cb.completeFn()); + }; state.ResumeTiming(); - auto ret = halCall<std::chrono::milliseconds>(mController, [&](auto hal) { - return hal->performComposedEffect(effects, callback); - }); + + // Test + if (shouldSkipWithError<milliseconds>(performFn, "performComposedEffect", state)) { + return; + } + + // Cleanup state.PauseTiming(); - if (checkHalResult(ret, state)) { - turnVibratorOff(state); + if (shouldSkipWithError(turnVibratorOff(), state)) { + return; } + cb.waitForComplete(); + state.ResumeTiming(); } }); |