Merge "Place recordLayerHistoryAnimationTx behind flag" into main
diff --git a/cmds/servicemanager/Access.cpp b/cmds/servicemanager/Access.cpp
index 711038c..8098724 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 @@
}
#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 77c2cd4..4ee9b90 100644
--- a/cmds/servicemanager/Access.h
+++ b/cmds/servicemanager/Access.h
@@ -36,6 +36,8 @@
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 a828b52..a5c0c60 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -115,18 +115,20 @@
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 @@
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 @@
}
// 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 @@
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 @@
// 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 @@
}
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 @@
}
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 @@
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 @@
}
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 @@
*outReturn = false;
#ifndef VENDORSERVICEMANAGER
- *outReturn = isVintfDeclared(name);
+ *outReturn = isVintfDeclared(ctx, name);
#endif
return Status::ok();
}
@@ -735,18 +742,16 @@
}
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
"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 c19c9da..a91a7dc 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",
+ rustlibs: [
+ "android.gui-rust",
+ "libclap",
],
-
- cflags: [
- "-Wall",
- "-Werror",
- "-Wunused",
- "-Wunreachable-code",
- ],
+ edition: "2021",
}
diff --git a/cmds/sfdo/sfdo.cpp b/cmds/sfdo/sfdo.cpp
deleted file mode 100644
index de0e171..0000000
--- 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 0000000..863df6b
--- /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 39772ae..c3f2fed 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 fb1419f..dc9383a 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -307,7 +307,7 @@
/**
* Parameter for ASurfaceTransaction_setVisibility().
*/
-enum {
+enum ASurfaceTransactionVisibility : int8_t {
ASURFACE_TRANSACTION_VISIBILITY_HIDE = 0,
ASURFACE_TRANSACTION_VISIBILITY_SHOW = 1,
};
@@ -319,7 +319,8 @@
* 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_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 @@
/**
* 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 @@
*/
void ASurfaceTransaction_setBufferTransparency(ASurfaceTransaction* transaction,
ASurfaceControl* surface_control,
- int8_t transparency)
+ enum ASurfaceTransactionTransparency transparency)
__INTRODUCED_IN(29);
/**
@@ -501,7 +502,7 @@
* 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/Input.h b/include/input/Input.h
index 19f4ab3..00757a7 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -26,6 +26,7 @@
#ifdef __linux__
#include <android/os/IInputConstants.h>
#endif
+#include <android/os/PointerIconType.h>
#include <math.h>
#include <stdint.h>
#include <ui/Transform.h>
@@ -1213,43 +1214,41 @@
*
* Due to backwards compatibility and public api constraints, this is a duplicate (but type safe)
* definition of PointerIcon.java.
- *
- * TODO(b/235023317) move this definition to an aidl and statically assign to the below java public
- * api values.
- *
- * WARNING: Keep these definitions in sync with
- * frameworks/base/core/java/android/view/PointerIcon.java
*/
enum class PointerIconStyle : int32_t {
- TYPE_CUSTOM = -1,
- TYPE_NULL = 0,
- TYPE_NOT_SPECIFIED = 1,
- TYPE_ARROW = 1000,
- TYPE_CONTEXT_MENU = 1001,
- TYPE_HAND = 1002,
- TYPE_HELP = 1003,
- TYPE_WAIT = 1004,
- TYPE_CELL = 1006,
- TYPE_CROSSHAIR = 1007,
- TYPE_TEXT = 1008,
- TYPE_VERTICAL_TEXT = 1009,
- TYPE_ALIAS = 1010,
- TYPE_COPY = 1011,
- TYPE_NO_DROP = 1012,
- TYPE_ALL_SCROLL = 1013,
- TYPE_HORIZONTAL_DOUBLE_ARROW = 1014,
- TYPE_VERTICAL_DOUBLE_ARROW = 1015,
- TYPE_TOP_RIGHT_DOUBLE_ARROW = 1016,
- TYPE_TOP_LEFT_DOUBLE_ARROW = 1017,
- TYPE_ZOOM_IN = 1018,
- TYPE_ZOOM_OUT = 1019,
- TYPE_GRAB = 1020,
- TYPE_GRABBING = 1021,
- TYPE_HANDWRITING = 1022,
+ TYPE_CUSTOM = static_cast<int32_t>(::android::os::PointerIconType::CUSTOM),
+ TYPE_NULL = static_cast<int32_t>(::android::os::PointerIconType::TYPE_NULL),
+ TYPE_NOT_SPECIFIED = static_cast<int32_t>(::android::os::PointerIconType::NOT_SPECIFIED),
+ TYPE_ARROW = static_cast<int32_t>(::android::os::PointerIconType::ARROW),
+ TYPE_CONTEXT_MENU = static_cast<int32_t>(::android::os::PointerIconType::CONTEXT_MENU),
+ TYPE_HAND = static_cast<int32_t>(::android::os::PointerIconType::HAND),
+ TYPE_HELP = static_cast<int32_t>(::android::os::PointerIconType::HELP),
+ TYPE_WAIT = static_cast<int32_t>(::android::os::PointerIconType::WAIT),
+ TYPE_CELL = static_cast<int32_t>(::android::os::PointerIconType::CELL),
+ TYPE_CROSSHAIR = static_cast<int32_t>(::android::os::PointerIconType::CROSSHAIR),
+ TYPE_TEXT = static_cast<int32_t>(::android::os::PointerIconType::TEXT),
+ TYPE_VERTICAL_TEXT = static_cast<int32_t>(::android::os::PointerIconType::VERTICAL_TEXT),
+ TYPE_ALIAS = static_cast<int32_t>(::android::os::PointerIconType::ALIAS),
+ TYPE_COPY = static_cast<int32_t>(::android::os::PointerIconType::COPY),
+ TYPE_NO_DROP = static_cast<int32_t>(::android::os::PointerIconType::NO_DROP),
+ TYPE_ALL_SCROLL = static_cast<int32_t>(::android::os::PointerIconType::ALL_SCROLL),
+ TYPE_HORIZONTAL_DOUBLE_ARROW =
+ static_cast<int32_t>(::android::os::PointerIconType::HORIZONTAL_DOUBLE_ARROW),
+ TYPE_VERTICAL_DOUBLE_ARROW =
+ static_cast<int32_t>(::android::os::PointerIconType::VERTICAL_DOUBLE_ARROW),
+ TYPE_TOP_RIGHT_DOUBLE_ARROW =
+ static_cast<int32_t>(::android::os::PointerIconType::TOP_RIGHT_DOUBLE_ARROW),
+ TYPE_TOP_LEFT_DOUBLE_ARROW =
+ static_cast<int32_t>(::android::os::PointerIconType::TOP_LEFT_DOUBLE_ARROW),
+ TYPE_ZOOM_IN = static_cast<int32_t>(::android::os::PointerIconType::ZOOM_IN),
+ TYPE_ZOOM_OUT = static_cast<int32_t>(::android::os::PointerIconType::ZOOM_OUT),
+ TYPE_GRAB = static_cast<int32_t>(::android::os::PointerIconType::GRAB),
+ TYPE_GRABBING = static_cast<int32_t>(::android::os::PointerIconType::GRABBING),
+ TYPE_HANDWRITING = static_cast<int32_t>(::android::os::PointerIconType::HANDWRITING),
- TYPE_SPOT_HOVER = 2000,
- TYPE_SPOT_TOUCH = 2001,
- TYPE_SPOT_ANCHOR = 2002,
+ TYPE_SPOT_HOVER = static_cast<int32_t>(::android::os::PointerIconType::SPOT_HOVER),
+ TYPE_SPOT_TOUCH = static_cast<int32_t>(::android::os::PointerIconType::SPOT_TOUCH),
+ TYPE_SPOT_ANCHOR = static_cast<int32_t>(::android::os::PointerIconType::SPOT_ANCHOR),
};
} // namespace android
diff --git a/include/input/InputConsumer.h b/include/input/InputConsumer.h
index 560e804..611478c 100644
--- a/include/input/InputConsumer.h
+++ b/include/input/InputConsumer.h
@@ -111,6 +111,11 @@
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 3b6e401..f715039 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 @@
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 @@
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 2edc138..728a8e1 100644
--- a/include/input/TfLiteMotionPredictor.h
+++ b/include/input/TfLiteMotionPredictor.h
@@ -105,6 +105,11 @@
// 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 ca9b08f..eec12e4 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -161,7 +161,7 @@
},
android: {
lto: {
- thin: true,
+ thin: true,
},
},
},
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index 42dd691..54457fc 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -44,6 +44,7 @@
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 @@
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 @@
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 @@
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 @@
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::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 89a4d27..9f03907 100644
--- a/libs/binder/include/binder/BpBinder.h
+++ b/libs/binder/include/binder/BpBinder.h
@@ -35,7 +35,8 @@
}
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 @@
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 @@
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 30dbddd..2a8a353 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -50,6 +50,7 @@
],
cflags: [
+ "-DBINDER_WITH_KERNEL_IPC",
"-Wall",
"-Wextra",
"-Werror",
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index e2ede3f..34a86f2 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 @@
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 52edae4..c665ad8 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 ca3d5e6..39cf1c4 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/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 0ee96e7..2cea14f 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -115,6 +115,7 @@
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 @@
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 @@
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 62fe9e5..8832f1a 100644
--- a/libs/binder/tests/binderRpcTestCommon.h
+++ b/libs/binder/tests/binderRpcTestCommon.h
@@ -74,6 +74,12 @@
}
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 885bb45..f480780 100644
--- a/libs/binder/tests/binderRpcUniversalTests.cpp
+++ b/libs/binder/tests/binderRpcUniversalTests.cpp
@@ -48,11 +48,13 @@
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 5b1e9ea..a57d07f 100644
--- a/libs/binder/tests/parcel_fuzzer/main.cpp
+++ b/libs/binder/tests/parcel_fuzzer/main.cpp
@@ -46,7 +46,18 @@
(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 0a584bf..83d0ca7 100644
--- a/libs/binder/tests/unit_fuzzers/BpBinderFuzzFunctions.h
+++ b/libs/binder/tests/unit_fuzzers/BpBinderFuzzFunctions.h
@@ -95,14 +95,16 @@
},
[](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 6a48d2b..eabfe60 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 03fd006..7a275c2 100644
--- a/libs/binder/trusty/ndk/rules.mk
+++ b/libs/binder/trusty/ndk/rules.mk
@@ -23,6 +23,7 @@
$(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 a8f2cbf..19f3aad 100644
--- a/libs/binderdebug/BinderDebug.cpp
+++ b/libs/binderdebug/BinderDebug.cpp
@@ -199,4 +199,31 @@
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 6ce8edf..018393c 100644
--- a/libs/binderdebug/include/binderdebug/BinderDebug.h
+++ b/libs/binderdebug/include/binderdebug/BinderDebug.h
@@ -44,4 +44,12 @@
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 b5c4010..e888b0a 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::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 @@
// 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(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, 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 @@
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 @@
}
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 0000000..428a1a2
--- /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 0000000..55ba3c3
--- /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 0000000..23a8d24
--- /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 0000000..1dfa512
--- /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 0000000..0c16665
--- /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 0000000..6f4bafb
--- /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 0000000..f2195c0
--- /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 0000000..2ad7f4e
--- /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 70cb36b..d1b2c5f 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -155,9 +155,9 @@
},
}
-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 @@
],
}
+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 @@
"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 15b2221..0000000
--- 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 1d2ea3e..eb945cc 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -2443,7 +2443,6 @@
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 0000000..8ed08c2
--- /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 6b0595e..eb231c1 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 920d949..9f198ca 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 2caa2b9..fc97dbf 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 51e0193..a2549e7 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -43,7 +43,6 @@
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 @@
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 f0def50..18d293f 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 faca980..0000000
--- 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 1368ac5..d8121be 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 ba76671..53f443a 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 9908edd..97a9035 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 30c0885..3b16724 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 2c85d15..b9d5ccf 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 0c6109d..5c23e08 100644
--- a/libs/gui/android/gui/WindowInfosUpdate.aidl
+++ b/libs/gui/android/gui/WindowInfosUpdate.aidl
@@ -19,4 +19,4 @@
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 a836f46..738c73a 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -74,7 +74,6 @@
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 dbb80e5..0000000
--- 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 2d1b51a..e4f1890 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -178,6 +178,8 @@
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 0000000..794f69e
--- /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 0000000..3d29529
--- /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 0f16f71..2faa330 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -21,7 +21,8 @@
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 ea7078d..946ff05 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 @@
}
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 @@
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 @@
}
const sp<SurfaceControl> getSurfaceControl() {
+ std::scoped_lock lock(mBlastBufferQueueAdapter->mMutex);
return mBlastBufferQueueAdapter->mSurfaceControl;
}
@@ -156,6 +166,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 @@
// 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 @@
// 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 @@
// ...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 @@
// ...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 @@
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 @@
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 @@
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 @@
// 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 @@
// 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 @@
// 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 df7739c..1ec6f91 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -119,8 +119,7 @@
}
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 @@
// 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 @@
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 0a2750a..bffb3f0 100644
--- a/libs/gui/tests/DisplayedContentSampling_test.cpp
+++ b/libs/gui/tests/DisplayedContentSampling_test.cpp
@@ -116,10 +116,10 @@
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 9791212..f441eaa 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// Does not receive events outside its crop
injectTap(26, 26);
- EXPECT_EQ(containerSurface->consumeEvent(/*timeout=*/100ms), nullptr);
+ containerSurface->assertNoEvent();
}
/**
@@ -1137,7 +1139,7 @@
// Does not receive events outside parent bounds
injectTap(31, 31);
- EXPECT_EQ(containerSurface->consumeEvent(/*timeout=*/100ms), nullptr);
+ containerSurface->assertNoEvent();
}
/**
@@ -1163,7 +1165,7 @@
// 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 @@
[&](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 @@
// 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 @@
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 @@
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 577d239..f4b059c 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -173,7 +173,7 @@
// 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 @@
// 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 @@
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/Android.bp b/libs/input/Android.bp
index 3278c23..fed590c 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -30,6 +30,7 @@
"android/os/InputEventInjectionResult.aidl",
"android/os/InputEventInjectionSync.aidl",
"android/os/InputConfig.aidl",
+ "android/os/PointerIconType.aidl",
],
}
diff --git a/libs/input/InputConsumer.cpp b/libs/input/InputConsumer.cpp
index e0d874e..be2110e 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,
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 @@
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 @@
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 @@
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 52acb51..76f2b4a 100644
--- a/libs/input/InputConsumerNoResampling.cpp
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -413,10 +413,9 @@
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 c4e3ff6..5b61d39 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -18,21 +18,29 @@
#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>
#include <android-base/strings.h>
#include <android/input.h>
+#include <com_android_input_flags.h>
#include <attestation/HmacKeyManager.h>
#include <ftl/enum.h>
#include <input/TfLiteMotionPredictor.h>
+namespace input_flags = com::android::input::flags;
+
namespace android {
namespace {
@@ -55,8 +63,73 @@
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,
@@ -103,6 +176,7 @@
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) {
@@ -137,6 +211,9 @@
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) {
@@ -184,6 +261,17 @@
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) {
@@ -197,7 +285,13 @@
// device starts to speed up, but avoids producing noisy predictions as it slows down.
break;
}
- // TODO(b/266747654): Stop predictions if confidence is < some threshold.
+ if (input_flags::enable_prediction_pruning_via_jerk_thresholding()) {
+ 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 d17476e..b843a4b 100644
--- a/libs/input/TfLiteMotionPredictor.cpp
+++ b/libs/input/TfLiteMotionPredictor.cpp
@@ -281,6 +281,8 @@
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 5d39155..6b97cbb 100644
--- a/libs/input/android/os/InputConfig.aidl
+++ b/libs/input/android/os/InputConfig.aidl
@@ -157,4 +157,12 @@
* 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/android/os/PointerIconType.aidl b/libs/input/android/os/PointerIconType.aidl
new file mode 100644
index 0000000..f244c62
--- /dev/null
+++ b/libs/input/android/os/PointerIconType.aidl
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Represents an icon that can be used as a mouse pointer.
+ * Please look at frameworks/base/core/java/android/view/PointerIcon.java for the detailed
+ * explanation of each constant.
+ * @hide
+ */
+@Backing(type="int")
+enum PointerIconType {
+ CUSTOM = -1,
+ TYPE_NULL = 0,
+ NOT_SPECIFIED = 1,
+ ARROW = 1000,
+ CONTEXT_MENU = 1001,
+ HAND = 1002,
+ HELP = 1003,
+ WAIT = 1004,
+ CELL = 1006,
+ CROSSHAIR = 1007,
+ TEXT = 1008,
+ VERTICAL_TEXT = 1009,
+ ALIAS = 1010,
+ COPY = 1011,
+ NO_DROP = 1012,
+ ALL_SCROLL = 1013,
+ HORIZONTAL_DOUBLE_ARROW = 1014,
+ VERTICAL_DOUBLE_ARROW = 1015,
+ TOP_RIGHT_DOUBLE_ARROW = 1016,
+ TOP_LEFT_DOUBLE_ARROW = 1017,
+ ZOOM_IN = 1018,
+ ZOOM_OUT = 1019,
+ GRAB = 1020,
+ GRABBING = 1021,
+ HANDWRITING = 1022,
+
+ SPOT_HOVER = 2000,
+ SPOT_TOUCH = 2001,
+ SPOT_ANCHOR = 2002,
+}
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index b48b0fb..e161c2a 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -128,3 +128,19 @@
description: "Enable fling scrolling to be stopped by putting a finger on the touchpad again"
bug: "281106755"
}
+
+flag {
+ name: "enable_prediction_pruning_via_jerk_thresholding"
+ namespace: "input"
+ description: "Enable prediction pruning based on jerk thresholds."
+ bug: "266747654"
+ 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 e67a65a..ee140b7 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -36,6 +36,7 @@
"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 3343114..b8f1caa 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 @@
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, 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 @@
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 a3df482..97740db 100644
--- a/libs/nativewindow/rust/Android.bp
+++ b/libs/nativewindow/rust/Android.bp
@@ -54,6 +54,10 @@
},
min_sdk_version: "VanillaIceCream",
vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
}
rust_library {
@@ -78,6 +82,10 @@
},
min_sdk_version: "VanillaIceCream",
vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
}
rust_test {
@@ -116,6 +124,10 @@
},
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 22ad834..dc3f51f 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 0defc7e..c003111 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -84,6 +84,7 @@
"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 @@
"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 233134d..1c60563 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 de05268..00a6213 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -102,6 +102,11 @@
VK,
};
+ enum class SkiaBackend {
+ GANESH,
+ GRAPHITE,
+ };
+
static std::unique_ptr<RenderEngine> create(const RenderEngineCreationArgs& args);
static bool canSupport(GraphicsApi);
@@ -257,6 +262,7 @@
RenderEngine::ContextPriority contextPriority;
RenderEngine::Threaded threaded;
RenderEngine::GraphicsApi graphicsApi;
+ RenderEngine::SkiaBackend skiaBackend;
struct Builder;
@@ -267,7 +273,8 @@
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 @@
supportsBackgroundBlur(_supportsBackgroundBlur),
contextPriority(_contextPriority),
threaded(_threaded),
- graphicsApi(_graphicsApi) {}
+ graphicsApi(_graphicsApi),
+ skiaBackend(_skiaBackend) {}
RenderEngineCreationArgs() = delete;
};
@@ -314,10 +322,14 @@
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 @@
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 aa18713..68798bf 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 5940d04..e6123c2 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 0000000..b5cb21b
--- /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 0000000..cf24a3b
--- /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 3715859..fd71332 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -83,23 +83,6 @@
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 371b812..0a2f9b2 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.h
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.h
@@ -29,7 +29,6 @@
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 2f09a38..49b9f1e 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 @@
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 512e62c..2824fcb 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 @@
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 0000000..3dd9ed2
--- /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 0000000..3bec3f7
--- /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 0000000..e19d66f
--- /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 0000000..685f899
--- /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 8222e62..a2457e5 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 @@
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 @@
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 3a07205..9f614bd 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -107,6 +107,7 @@
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,20 +120,12 @@
.setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
.setThreaded(renderengine::RenderEngine::Threaded::NO)
.setGraphicsApi(graphicsApi())
+ .setSkiaBackend(skiaBackend())
.build();
return renderengine::RenderEngine::create(reCreationArgs);
}
};
-class SkiaVkRenderEngineFactory : public RenderEngineFactory {
-public:
- std::string name() override { return "SkiaVkRenderEngineFactory"; }
-
- renderengine::RenderEngine::GraphicsApi graphicsApi() override {
- return renderengine::RenderEngine::GraphicsApi::VK;
- }
-};
-
class SkiaGLESRenderEngineFactory : public RenderEngineFactory {
public:
std::string name() override { return "SkiaGLRenderEngineFactory"; }
@@ -140,6 +133,36 @@
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 GraphiteVkRenderEngineFactory : public RenderEngineFactory {
+public:
+ std::string name() override { return "GraphiteVkRenderEngineFactory"; }
+
+ renderengine::RenderEngine::GraphicsApi graphicsApi() override {
+ return renderengine::RenderEngine::GraphicsApi::VK;
+ }
+
+ renderengine::RenderEngine::SkiaBackend skiaBackend() override {
+ return renderengine::RenderEngine::SkiaBackend::GRAPHITE;
+ }
};
class RenderEngineTest : public ::testing::TestWithParam<std::shared_ptr<RenderEngineFactory>> {
@@ -1242,7 +1265,12 @@
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 @@
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, 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 cc92bc3..7fa47b4 100644
--- a/libs/sensor/Android.bp
+++ b/libs/sensor/Android.bp
@@ -24,6 +24,7 @@
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 ef4d737..c511f4a 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 @@
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 d6ffeb7..f4c8ba2 100644
--- a/libs/ui/include/ui/LayerStack.h
+++ b/libs/ui/include/ui/LayerStack.h
@@ -55,6 +55,10 @@
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 1aa1077..10efea5 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -147,6 +147,7 @@
<< args.dump();
}
+ mMouseDevices.emplace(args.deviceId);
auto [displayId, pc] = ensureMouseControllerLocked(args.displayId);
NotifyMotionArgs newArgs(args);
newArgs.displayId = displayId;
@@ -178,6 +179,7 @@
}
NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMotionArgs& args) {
+ mMouseDevices.emplace(args.deviceId);
auto [displayId, pc] = ensureMouseControllerLocked(args.displayId);
NotifyMotionArgs newArgs(args);
@@ -405,8 +407,10 @@
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 @@
// 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 @@
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 2d12574..4385072 100644
--- a/services/inputflinger/benchmarks/Android.bp
+++ b/services/inputflinger/benchmarks/Android.bp
@@ -11,6 +11,7 @@
cc_benchmark {
name: "inputflinger_benchmarks",
srcs: [
+ ":inputdispatcher_common_test_sources",
"InputDispatcher_benchmarks.cpp",
],
defaults: [
@@ -31,6 +32,8 @@
],
static_libs: [
"libattestation",
+ "libgmock",
+ "libgtest",
"libinputdispatcher",
],
}
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 5ae3715..5f95590 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 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 @@
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 @@
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 2df86a0..76c492e 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -2532,11 +2532,19 @@
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 @@
<< ", 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 @@
// 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 @@
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();
@@ -7157,4 +7169,11 @@
return false;
}
+void InputDispatcher::setInputMethodConnectionIsActive(bool isActive) {
+ std::scoped_lock _l(mLock);
+ if (mTracer) {
+ mTracer->setInputMethodConnectionIsActive(isActive);
+ }
+}
+
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index edca63e..3579a67 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -158,6 +158,8 @@
bool isPointerInWindow(const sp<IBinder>& token, int32_t displayId, DeviceId deviceId,
int32_t pointerId) override;
+ void setInputMethodConnectionIsActive(bool isActive) override;
+
private:
enum class DropReason {
NOT_DROPPED,
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 1fec9b7..c2d2b7b 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -95,12 +95,14 @@
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;
+ }
}
}
@@ -345,24 +347,26 @@
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 35ad858..f9a2855 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 @@
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 058639a..60a75ee 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -92,7 +92,8 @@
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 f8aa625..0caa5e1 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -70,14 +70,14 @@
});
}
-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 @@
// 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 @@
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 3d534bc..559a3fd 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -43,11 +43,11 @@
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 037d7c8..1f86f66 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 @@
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 @@
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 0d1531f..4f0ad16 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -46,7 +46,8 @@
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/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 7c440e8..6b9262c 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -236,6 +236,11 @@
*/
virtual bool isPointerInWindow(const sp<IBinder>& token, int32_t displayId, DeviceId deviceId,
int32_t pointerId) = 0;
+
+ /*
+ * Notify the dispatcher that the state of the input method connection changed.
+ */
+ virtual void setInputMethodConnectionIsActive(bool isActive) = 0;
};
} // namespace android
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp
index 55ed5c6..4931a5f 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp
@@ -61,10 +61,10 @@
e.downTime, e.flags, e.repeatCount, type};
}
-void writeEventToBackend(const TracedEvent& event, const TracedEventArgs args,
+void writeEventToBackend(const TracedEvent& event, const TracedEventMetadata metadata,
InputTracingBackendInterface& backend) {
- std::visit(Visitor{[&](const TracedMotionEvent& e) { backend.traceMotionEvent(e, args); },
- [&](const TracedKeyEvent& e) { backend.traceKeyEvent(e, args); }},
+ std::visit(Visitor{[&](const TracedMotionEvent& e) { backend.traceMotionEvent(e, metadata); },
+ [&](const TracedKeyEvent& e) { backend.traceKeyEvent(e, metadata); }},
event);
}
@@ -86,8 +86,10 @@
// 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
@@ -126,22 +128,21 @@
const InputTargetInfo& targetInfo = getTargetInfo(target);
if (eventState->isEventProcessingComplete) {
// Disallow adding new targets after eventProcessingComplete() is called.
- if (eventState->targets.find(targetInfo.uid) == eventState->targets.end()) {
+ if (eventState->metadata.targets.count(targetInfo.uid) == 0) {
LOG(FATAL) << __func__ << ": Cannot add new target after eventProcessingComplete";
}
return;
}
if (isDerivedCookie(cookie)) {
// Disallow adding new targets from a derived cookie.
- if (eventState->targets.find(targetInfo.uid) == eventState->targets.end()) {
+ if (eventState->metadata.targets.count(targetInfo.uid) == 0) {
LOG(FATAL) << __func__ << ": Cannot add new target from a derived cookie";
}
return;
}
- eventState->targets.emplace(targetInfo.uid);
- eventState->isSecure |= targetInfo.isSecureWindow;
- // TODO(b/210460522): Set events as sensitive when the IME connection is active.
+ eventState->metadata.targets.emplace(targetInfo.uid);
+ eventState->metadata.isSecure |= targetInfo.isSecureWindow;
}
void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) {
@@ -177,9 +178,7 @@
// is dispatched, such as in the case of key fallback events. To account for these cases,
// derived events can be traced after the processing is complete for the original event.
const auto& event = eventState->events.back();
- const TracedEventArgs traceArgs{.isSecure = eventState->isSecure,
- .targets = eventState->targets};
- writeEventToBackend(event, std::move(traceArgs), *mBackend);
+ writeEventToBackend(event, eventState->metadata, *mBackend);
}
return std::make_unique<EventTrackerImpl>(std::move(eventState), /*isDerived=*/true);
}
@@ -206,7 +205,7 @@
<< ": Failed to find a previously traced event that matches the dispatched event";
}
- if (eventState->targets.count(dispatchEntry.targetUid) == 0) {
+ if (eventState->metadata.targets.count(dispatchEntry.targetUid) == 0) {
LOG(FATAL) << __func__ << ": Event is being dispatched to UID that it is not targeting";
}
@@ -226,9 +225,7 @@
/*hmac=*/{},
resolvedKeyRepeatCount};
if (eventState->isEventProcessingComplete) {
- const TracedEventArgs traceArgs{.isSecure = eventState->isSecure,
- .targets = eventState->targets};
- mBackend->traceWindowDispatch(std::move(windowDispatchArgs), std::move(traceArgs));
+ mBackend->traceWindowDispatch(std::move(windowDispatchArgs), eventState->metadata);
} else {
eventState->pendingDispatchArgs.emplace_back(std::move(windowDispatchArgs));
}
@@ -246,10 +243,11 @@
// --- InputTracer::EventState ---
void InputTracer::EventState::onEventProcessingComplete() {
+ metadata.isImeConnectionActive = tracer.mIsImeConnectionActive;
+
// Write all of the events known so far to the trace.
for (const auto& event : events) {
- const TracedEventArgs traceArgs{.isSecure = isSecure, .targets = targets};
- writeEventToBackend(event, traceArgs, *tracer.mBackend);
+ writeEventToBackend(event, metadata, *tracer.mBackend);
}
// Write all pending dispatch args to the trace.
for (const auto& windowDispatchArgs : pendingDispatchArgs) {
@@ -263,8 +261,7 @@
<< ": Failed to find a previously traced event that matches the dispatched "
"event";
}
- const TracedEventArgs traceArgs{.isSecure = isSecure, .targets = targets};
- tracer.mBackend->traceWindowDispatch(windowDispatchArgs, std::move(traceArgs));
+ tracer.mBackend->traceWindowDispatch(windowDispatchArgs, metadata);
}
pendingDispatchArgs.clear();
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h
index 717bc1f..ab175be 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.h
+++ b/services/inputflinger/dispatcher/trace/InputTracer.h
@@ -48,9 +48,13 @@
std::unique_ptr<EventTrackerInterface> traceDerivedEvent(const EventEntry&,
const EventTrackerInterface&) override;
void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface&) override;
+ void setInputMethodConnectionIsActive(bool isActive) override {
+ mIsImeConnectionActive = isActive;
+ }
private:
std::unique_ptr<InputTracingBackendInterface> mBackend;
+ bool mIsImeConnectionActive{false};
// The state of a tracked event, shared across all events derived from the original event.
struct EventState {
@@ -64,10 +68,8 @@
bool isEventProcessingComplete{false};
// A queue to hold dispatch args from being traced until event processing is complete.
std::vector<const WindowDispatchArgs> pendingDispatchArgs;
- // True if the event is targeting at least one secure window;
- bool isSecure{false};
- // The list of all possible UIDs that this event could be targeting.
- std::set<gui::Uid> targets;
+ // The metadata should not be modified after event processing is complete.
+ TracedEventMetadata metadata{};
};
// Get the event state associated with a tracking cookie.
diff --git a/services/inputflinger/dispatcher/trace/InputTracerInterface.h b/services/inputflinger/dispatcher/trace/InputTracerInterface.h
index 609d10c..af6eefb 100644
--- a/services/inputflinger/dispatcher/trace/InputTracerInterface.h
+++ b/services/inputflinger/dispatcher/trace/InputTracerInterface.h
@@ -103,6 +103,11 @@
* provided.
*/
virtual void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface&) = 0;
+
+ /**
+ * Notify that the state of the input method connection changed.
+ */
+ virtual void setInputMethodConnectionIsActive(bool isActive) = 0;
};
} // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
index 3ff7fab..2b45e3a 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
@@ -92,11 +92,13 @@
using TracedEvent = std::variant<TracedKeyEvent, TracedMotionEvent>;
/** Additional information about an input event being traced. */
-struct TracedEventArgs {
+struct TracedEventMetadata {
// True if the event is targeting at least one secure window.
bool isSecure;
// The list of possible UIDs that this event could be targeting.
std::set<gui::Uid> targets;
+ // True if the there was an active input method connection while this event was processed.
+ bool isImeConnectionActive;
};
/** Additional information about an input event being dispatched to a window. */
@@ -121,13 +123,13 @@
virtual ~InputTracingBackendInterface() = default;
/** Trace a KeyEvent. */
- virtual void traceKeyEvent(const TracedKeyEvent&, const TracedEventArgs&) = 0;
+ virtual void traceKeyEvent(const TracedKeyEvent&, const TracedEventMetadata&) = 0;
/** Trace a MotionEvent. */
- virtual void traceMotionEvent(const TracedMotionEvent&, const TracedEventArgs&) = 0;
+ virtual void traceMotionEvent(const TracedMotionEvent&, const TracedEventMetadata&) = 0;
/** Trace an event being sent to a window. */
- virtual void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventArgs&) = 0;
+ virtual void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventMetadata&) = 0;
};
} // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
index b76bec3..9c39743 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
@@ -98,10 +98,10 @@
}
TraceLevel PerfettoBackend::InputEventDataSource::resolveTraceLevel(
- const TracedEventArgs& args) const {
+ const TracedEventMetadata& metadata) const {
// Check for matches with the rules in the order that they are defined.
for (const auto& rule : mConfig.rules) {
- if (ruleMatches(rule, args)) {
+ if (ruleMatches(rule, metadata)) {
return rule.level;
}
}
@@ -110,17 +110,23 @@
}
bool PerfettoBackend::InputEventDataSource::ruleMatches(const TraceRule& rule,
- const TracedEventArgs& args) const {
+ const TracedEventMetadata& metadata) const {
// By default, a rule will match all events. Return early if the rule does not match.
// Match the event if it is directed to a secure window.
- if (rule.matchSecure.has_value() && *rule.matchSecure != args.isSecure) {
+ if (rule.matchSecure.has_value() && *rule.matchSecure != metadata.isSecure) {
+ return false;
+ }
+
+ // Match the event if it was processed while there was an active InputMethod connection.
+ if (rule.matchImeConnectionActive.has_value() &&
+ *rule.matchImeConnectionActive != metadata.isImeConnectionActive) {
return false;
}
// Match the event if all of its target packages are explicitly allowed in the "match all" list.
if (!rule.matchAllPackages.empty() &&
- !std::all_of(args.targets.begin(), args.targets.end(), [&](const auto& uid) {
+ !std::all_of(metadata.targets.begin(), metadata.targets.end(), [&](const auto& uid) {
return isPermanentlyAllowed(uid) ||
std::any_of(rule.matchAllPackages.begin(), rule.matchAllPackages.end(),
[&](const auto& pkg) { return uid == mUidMap->at(pkg); });
@@ -130,7 +136,7 @@
// Match the event if any of its target packages are allowed in the "match any" list.
if (!rule.matchAnyPackages.empty() &&
- !std::any_of(args.targets.begin(), args.targets.end(), [&](const auto& uid) {
+ !std::any_of(metadata.targets.begin(), metadata.targets.end(), [&](const auto& uid) {
return std::any_of(rule.matchAnyPackages.begin(), rule.matchAnyPackages.end(),
[&](const auto& pkg) { return uid == mUidMap->at(pkg); });
})) {
@@ -166,14 +172,14 @@
}
void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event,
- const TracedEventArgs& args) {
+ const TracedEventMetadata& metadata) {
InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
auto dataSource = ctx.GetDataSourceLocked();
dataSource->initializeUidMap(mGetPackageUid);
if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) {
return;
}
- const TraceLevel traceLevel = dataSource->resolveTraceLevel(args);
+ const TraceLevel traceLevel = dataSource->resolveTraceLevel(metadata);
if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) {
return;
}
@@ -186,14 +192,15 @@
});
}
-void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event, const TracedEventArgs& args) {
+void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event,
+ const TracedEventMetadata& metadata) {
InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
auto dataSource = ctx.GetDataSourceLocked();
dataSource->initializeUidMap(mGetPackageUid);
if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) {
return;
}
- const TraceLevel traceLevel = dataSource->resolveTraceLevel(args);
+ const TraceLevel traceLevel = dataSource->resolveTraceLevel(metadata);
if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) {
return;
}
@@ -207,14 +214,14 @@
}
void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs,
- const TracedEventArgs& args) {
+ const TracedEventMetadata& metadata) {
InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
auto dataSource = ctx.GetDataSourceLocked();
dataSource->initializeUidMap(mGetPackageUid);
if (!dataSource->getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH)) {
return;
}
- const TraceLevel traceLevel = dataSource->resolveTraceLevel(args);
+ const TraceLevel traceLevel = dataSource->resolveTraceLevel(metadata);
if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) {
return;
}
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
index af1c6b7..e945066 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
@@ -54,9 +54,9 @@
explicit PerfettoBackend(GetPackageUid);
~PerfettoBackend() override = default;
- void traceKeyEvent(const TracedKeyEvent&, const TracedEventArgs&) override;
- void traceMotionEvent(const TracedMotionEvent&, const TracedEventArgs&) override;
- void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventArgs&) override;
+ void traceKeyEvent(const TracedKeyEvent&, const TracedEventMetadata&) override;
+ void traceMotionEvent(const TracedMotionEvent&, const TracedEventMetadata&) override;
+ void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventMetadata&) override;
private:
// Implementation of the perfetto data source.
@@ -72,13 +72,13 @@
void initializeUidMap(GetPackageUid);
bool shouldIgnoreTracedInputEvent(const EventType&) const;
inline ftl::Flags<TraceFlag> getFlags() const { return mConfig.flags; }
- TraceLevel resolveTraceLevel(const TracedEventArgs&) const;
+ TraceLevel resolveTraceLevel(const TracedEventMetadata&) const;
private:
const int32_t mInstanceId;
TraceConfig mConfig;
- bool ruleMatches(const TraceRule&, const TracedEventArgs&) const;
+ bool ruleMatches(const TraceRule&, const TracedEventMetadata&) const;
std::optional<std::map<std::string, gui::Uid>> mUidMap;
};
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
index 1614789..77d09cb 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
@@ -54,25 +54,25 @@
template <typename Backend>
void ThreadedBackend<Backend>::traceMotionEvent(const TracedMotionEvent& event,
- const TracedEventArgs& traceArgs) {
+ const TracedEventMetadata& metadata) {
std::scoped_lock lock(mLock);
- mQueue.emplace_back(event, traceArgs);
+ mQueue.emplace_back(event, metadata);
mThreadWakeCondition.notify_all();
}
template <typename Backend>
void ThreadedBackend<Backend>::traceKeyEvent(const TracedKeyEvent& event,
- const TracedEventArgs& traceArgs) {
+ const TracedEventMetadata& metadata) {
std::scoped_lock lock(mLock);
- mQueue.emplace_back(event, traceArgs);
+ mQueue.emplace_back(event, metadata);
mThreadWakeCondition.notify_all();
}
template <typename Backend>
void ThreadedBackend<Backend>::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs,
- const TracedEventArgs& traceArgs) {
+ const TracedEventMetadata& metadata) {
std::scoped_lock lock(mLock);
- mQueue.emplace_back(dispatchArgs, traceArgs);
+ mQueue.emplace_back(dispatchArgs, metadata);
mThreadWakeCondition.notify_all();
}
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.h b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
index 3bfc72b..650a87e 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.h
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
@@ -38,9 +38,9 @@
ThreadedBackend(Backend&& innerBackend);
~ThreadedBackend() override;
- void traceKeyEvent(const TracedKeyEvent&, const TracedEventArgs&) override;
- void traceMotionEvent(const TracedMotionEvent&, const TracedEventArgs&) override;
- void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventArgs&) override;
+ void traceKeyEvent(const TracedKeyEvent&, const TracedEventMetadata&) override;
+ void traceMotionEvent(const TracedMotionEvent&, const TracedEventMetadata&) override;
+ void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventMetadata&) override;
private:
std::mutex mLock;
@@ -49,7 +49,7 @@
Backend mBackend;
using TraceEntry =
std::pair<std::variant<TracedKeyEvent, TracedMotionEvent, WindowDispatchArgs>,
- TracedEventArgs>;
+ TracedEventMetadata>;
std::vector<TraceEntry> mQueue GUARDED_BY(mLock);
// InputThread stops when its destructor is called. Initialize it last so that it is the
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 7d27d4a..8d91599 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 @@
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 @@
* 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 @@
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 @@
}
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 @@
// 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 4b39e40..8451675 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 @@
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 721cdfd..f558ba1 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -185,6 +185,7 @@
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 @@
}
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 09ae6dd..6ae9790 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -22,6 +22,15 @@
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 @@
"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 0000000..e231bcc
--- /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 e0a7324..d83924f 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"
-// --- FakeInputDispatcherPolicy ---
+#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>
+
+namespace android {
class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
public:
FakeInputDispatcherPolicy() = default;
virtual ~FakeInputDispatcherPolicy() = default;
+ 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:
- void notifyConfigurationChanged(nsecs_t) override {}
+ 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);
- void notifyNoFocusedWindowAnr(
- const std::shared_ptr<InputApplicationHandle>& applicationHandle) override {
- LOG(ERROR) << "There is no focused window for " << applicationHandle->getName();
- }
+ 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 {}
+ 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 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; }
+ void assertFilterInputEventWasCalledInternal(
+ const std::function<void(const InputEvent&)>& verify);
};
} // namespace android
diff --git a/services/inputflinger/tests/FakeInputTracingBackend.cpp b/services/inputflinger/tests/FakeInputTracingBackend.cpp
index 069b50d..b46055e 100644
--- a/services/inputflinger/tests/FakeInputTracingBackend.cpp
+++ b/services/inputflinger/tests/FakeInputTracingBackend.cpp
@@ -162,7 +162,7 @@
// --- FakeInputTracingBackend ---
void FakeInputTracingBackend::traceKeyEvent(const trace::TracedKeyEvent& event,
- const trace::TracedEventArgs&) {
+ const trace::TracedEventMetadata&) {
{
std::scoped_lock lock(mTrace->mLock);
mTrace->mTracedEvents.emplace(event.id, event);
@@ -171,7 +171,7 @@
}
void FakeInputTracingBackend::traceMotionEvent(const trace::TracedMotionEvent& event,
- const trace::TracedEventArgs&) {
+ const trace::TracedEventMetadata&) {
{
std::scoped_lock lock(mTrace->mLock);
mTrace->mTracedEvents.emplace(event.id, event);
@@ -180,7 +180,7 @@
}
void FakeInputTracingBackend::traceWindowDispatch(const trace::WindowDispatchArgs& args,
- const trace::TracedEventArgs&) {
+ const trace::TracedEventMetadata&) {
{
std::scoped_lock lock(mTrace->mLock);
mTrace->mTracedWindowDispatches.push_back(args);
diff --git a/services/inputflinger/tests/FakeInputTracingBackend.h b/services/inputflinger/tests/FakeInputTracingBackend.h
index ab05d6b..cd4b507 100644
--- a/services/inputflinger/tests/FakeInputTracingBackend.h
+++ b/services/inputflinger/tests/FakeInputTracingBackend.h
@@ -82,11 +82,12 @@
private:
std::shared_ptr<VerifyingTrace> mTrace;
- void traceKeyEvent(const trace::TracedKeyEvent& entry, const trace::TracedEventArgs&) override;
+ void traceKeyEvent(const trace::TracedKeyEvent& entry,
+ const trace::TracedEventMetadata&) override;
void traceMotionEvent(const trace::TracedMotionEvent& entry,
- const trace::TracedEventArgs&) override;
+ const trace::TracedEventMetadata&) override;
void traceWindowDispatch(const trace::WindowDispatchArgs& entry,
- const trace::TracedEventArgs&) override;
+ const trace::TracedEventMetadata&) override;
};
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/FakeWindowHandle.h b/services/inputflinger/tests/FakeWindowHandle.h
deleted file mode 100644
index 8ce61e7..0000000
--- 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 0000000..a6955ec
--- /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 0000000..c0c8975
--- /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 9e3a4f1..62a9235 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 @@
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_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 @@
// 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 @@
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);
- });
+ ScopedFlagOverride(std::function<bool()> read, std::function<void(bool)> write, bool value)
+ : mInitialValue(read()), mWriteValue(write) {
+ mWriteValue(value);
}
-
- 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() { 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;
- }
-
- void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
- const std::set<gui::Uid>& uids) override {
- ASSERT_TRUE(mNotifiedInteractions.emplace(deviceId, uids));
- }
-
- 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;
- }
-
- gui::Uid getPackageUid(std::string) override { return gui::Uid::INVALID; }
+ const bool mInitialValue;
+ std::function<void(bool)> mWriteValue;
};
+
+typedef bool (*readFlagValueFunction)();
+typedef void (*writeFlagValueFunction)(bool);
+
+/**
+ * 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))
+
} // 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 @@
}
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 @@
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 @@
* 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 @@
}
/**
+ * 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 @@
* 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
/**
* 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 @@
* 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 @@
}
/**
+ * 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 @@
* 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 @@
}
/**
+ * 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 @@
* 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
* 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
* 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 @@
}
/**
+ * 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 @@
* 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 @@
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 @@
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 @@
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 @@
/**
* 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 @@
}
/**
+ * 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 @@
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 @@
* 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 @@
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 @@
/*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 @@
/*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 1d46c9a..367bc70 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -9790,163 +9790,13 @@
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 @@
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 @@
// 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 3b2565e..7d1b23c 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -1772,4 +1772,107 @@
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 81c3353..48e1954 100644
--- a/services/inputflinger/tests/fuzzers/Android.bp
+++ b/services/inputflinger/tests/fuzzers/Android.bp
@@ -178,7 +178,12 @@
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 dc5a213..7335fb7 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 @@
} // 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 @@
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 @@
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 @@
[&]() -> void {
std::optional<NotifyMotionArgs> motion = streamProvider.nextMotion();
if (motion) {
- dispatcher.notifyMotion(*motion);
+ dispatcher->notifyMotion(*motion);
}
},
[&]() -> void {
@@ -169,7 +170,7 @@
}
}
- dispatcher.onWindowInfosChanged(
+ dispatcher->onWindowInfosChanged(
{windowInfos, {}, /*vsyncId=*/0, /*timestamp=*/0});
},
// Consume on all the windows
@@ -187,7 +188,7 @@
})();
}
- 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 e2d17ee..86bcf20 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 @@
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 dc3821c..5e3e3d8 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 @@
BlurRegions = 1u << 18,
HasProtectedContent = 1u << 19,
CachingHint = 1u << 20,
+ DimmingEnabled = 1u << 21,
};
// clang-format on
@@ -248,6 +249,10 @@
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 @@
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 @@
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 @@
&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 1f53588..ea9442d 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 @@
return false;
}
- if (hasUnsupportedDataspace()) {
+ if (hasKnownColorShift()) {
return false;
}
@@ -366,12 +365,21 @@
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 @@
// 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 0a5c43a..4bafed2 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -439,7 +439,7 @@
if (!layerDeniedFromCaching && layerIsInactive &&
(firstLayer || runHasFirstLayer || !layerHasBlur) &&
- !currentSet->hasUnsupportedDataspace()) {
+ !currentSet->hasKnownColorShift()) {
if (isPartOfRun) {
builder.increment();
} else {
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index d2eff75..54ee0ef 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -1339,6 +1339,55 @@
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 044917e..39fce2b 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
@@ -304,6 +304,16 @@
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;
@@ -1033,6 +1043,47 @@
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/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 62f8fb1..45ab7dd 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -560,10 +560,8 @@
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 0966fe0..7daeefe 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -1028,6 +1028,8 @@
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 @@
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 @@
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 @@
// 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 @@
// 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 @@
// 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 867f3af..028bd19 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -585,11 +585,13 @@
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 cd2120e..073bad3 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 @@
// 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 @@
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 @@
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 @@
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 d283d6f..c094aa1 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 @@
struct LayerFECompositionState;
}
-namespace gui {
-class LayerDebugInfo;
-}
-
namespace frametimeline {
class SurfaceFrame;
} // namespace frametimeline
@@ -703,8 +702,6 @@
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 @@
// 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/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index 620edca..c2251a8 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -27,6 +27,7 @@
#include "LayerFE.h"
#include "SurfaceFlinger.h"
+#include "common/FlagManager.h"
#include "ui/FenceResult.h"
#include "ui/LayerStack.h"
@@ -80,6 +81,16 @@
LayerFE::LayerFE(const std::string& name) : mName(name) {}
+LayerFE::~LayerFE() {
+ // Ensures that no promise is left unfulfilled before the LayerFE is destroyed.
+ // An unfulfilled promise could occur when a screenshot is attempted, but the
+ // render area is invalid and there is no memory for the capture result.
+ if (FlagManager::getInstance().ce_fence_promise() &&
+ mReleaseFencePromiseStatus == ReleaseFencePromiseStatus::INITIALIZED) {
+ setReleaseFence(Fence::NO_FENCE);
+ }
+}
+
const compositionengine::LayerFECompositionState* LayerFE::getCompositionState() const {
return mSnapshot.get();
}
diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h
index 019fa22..658f949 100644
--- a/services/surfaceflinger/LayerFE.h
+++ b/services/surfaceflinger/LayerFE.h
@@ -36,6 +36,7 @@
class LayerFE : public virtual RefBase, public virtual compositionengine::LayerFE {
public:
LayerFE(const std::string& name);
+ virtual ~LayerFE();
// compositionengine::LayerFE overrides
const compositionengine::LayerFECompositionState* getCompositionState() const override;
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 96eccf2..6b65449 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -235,7 +235,8 @@
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 @@
}
}
-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 @@
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 90e61a9..f772126 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -127,8 +127,8 @@
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 @@
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 7968096..c83d81f 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -250,18 +250,6 @@
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);
@@ -675,7 +663,13 @@
void Scheduler::recordLayerHistory(int32_t id, const LayerProps& layerProps, nsecs_t presentTime,
nsecs_t now, LayerHistory::LayerUpdateType updateType) {
- if (pacesetterSelectorPtr()->canSwitch()) {
+ const auto& selectorPtr = pacesetterSelectorPtr();
+ // Skip recording layer history on LayerUpdateType::SetFrameRate for MRR devices when the
+ // dVRR vote types are guarded (disabled) for MRR. This is to avoid activity when setting dVRR
+ // vote types.
+ if (selectorPtr->canSwitch() &&
+ (updateType != LayerHistory::LayerUpdateType::SetFrameRate ||
+ layerProps.setFrameRateVote.isVoteValidForMrr(selectorPtr->isVrrDevice()))) {
mLayerHistory.record(id, layerProps, presentTime, now, updateType);
}
}
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 092bc0d..0b47924 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -427,6 +427,7 @@
mRateMap[idealPeriod()] = {idealPeriod(), 0};
}
+ mTimelines.clear();
clearTimestamps();
}
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index a661292..8ce61d8 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -68,6 +68,13 @@
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 9b8f310..8038364 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -141,8 +141,7 @@
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 8787cdb..134d28e 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -71,6 +71,11 @@
*/
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 a5bb6c2..d6a3f62 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
@@ -71,6 +71,7 @@
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 @@
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 68c277d..8335568 100644
--- a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
+++ b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
@@ -113,6 +113,7 @@
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 cd7a9c1..a6a0ca6 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>
@@ -793,6 +792,8 @@
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 +808,14 @@
.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 +1841,6 @@
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 +2073,17 @@
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 +2252,7 @@
outTransactionsAreEmpty = !needsTraversal;
const bool shouldCommit = (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal;
if (shouldCommit) {
- commitTransactions();
+ commitTransactionsLegacy();
}
bool mustComposite = latchBuffers() || shouldCommit;
@@ -2375,8 +2376,14 @@
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 +2422,7 @@
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 +2613,14 @@
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 +3178,8 @@
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 +3238,7 @@
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 +3298,19 @@
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 +4151,7 @@
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 +4856,8 @@
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 +4880,107 @@
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;
+ }
+
+ 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;
+ }
+ }
}
- // 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;
+ // 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 +5201,13 @@
}(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});
@@ -5253,9 +5300,8 @@
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) {
@@ -6005,7 +6051,11 @@
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);
@@ -6038,7 +6088,8 @@
if (mLegacyFrontEndEnabled) {
applyTransactions(transactions, VsyncId{0});
} else {
- applyAndCommitDisplayTransactionStates(transactions);
+ Mutex::Autolock lock(mStateLock);
+ applyAndCommitDisplayTransactionStatesLocked(transactions);
}
{
@@ -6656,7 +6707,11 @@
}
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) {
@@ -6705,17 +6760,18 @@
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");
}
}
@@ -7819,17 +7875,19 @@
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;
}
@@ -7842,6 +7900,7 @@
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;
}
@@ -7858,7 +7917,7 @@
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;
}
@@ -7896,6 +7955,7 @@
const auto display = getDisplayDeviceLocked(displayId);
if (!display) {
+ ALOGD("Unable to find display device for captureDisplay");
invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
return;
}
@@ -7913,7 +7973,7 @@
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;
}
@@ -7960,6 +8020,7 @@
status_t validate = validateScreenshotPermissions(args);
if (validate != OK) {
+ ALOGD("Permission denied to captureLayers");
invokeScreenCaptureError(validate, captureListener);
return;
}
@@ -7971,7 +8032,7 @@
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;
}
@@ -7981,7 +8042,7 @@
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;
}
@@ -8000,6 +8061,7 @@
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;
}
@@ -8010,7 +8072,7 @@
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;
}
@@ -8019,13 +8081,14 @@
// 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) {
@@ -8084,7 +8147,7 @@
}
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;
}
@@ -8273,9 +8336,15 @@
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) {
@@ -8991,6 +9060,8 @@
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;
}
@@ -9191,7 +9262,8 @@
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) {
@@ -9252,11 +9324,12 @@
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;
}
@@ -9291,7 +9364,8 @@
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);
@@ -9333,7 +9407,7 @@
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,
@@ -9844,6 +9918,7 @@
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);
@@ -9889,22 +9964,6 @@
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 c106abd..44fa806 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 31cd2d7..89a8f92 100644
--- a/services/surfaceflinger/TransactionState.h
+++ b/services/surfaceflinger/TransactionState.h
@@ -86,7 +86,7 @@
}
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 6507aba..5a54f7d 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -139,6 +139,10 @@
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(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 964943c..0c1f9fb 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -77,6 +77,9 @@
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 0a5cde3..4a60987 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -19,14 +19,18 @@
metadata {
purpose: PURPOSE_BUGFIX
}
-}
+ } # 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 @@
}
} # 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 9b83713..3b6a51a 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 @@
/**
* 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 c9af432..5b056d0 100644
--- a/services/surfaceflinger/tests/LayerTransactionTest.h
+++ b/services/surfaceflinger/tests/LayerTransactionTest.h
@@ -106,6 +106,10 @@
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 18262f6..9a78550 100644
--- a/services/surfaceflinger/tests/ScreenCapture_test.cpp
+++ b/services/surfaceflinger/tests/ScreenCapture_test.cpp
@@ -1039,6 +1039,29 @@
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 3eabe1f..625d2e6 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -493,7 +493,7 @@
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 67e6249..e8e7667 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -281,6 +281,24 @@
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 94989aa..ae9a89c 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -1198,6 +1198,42 @@
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 82023b0..83e3245 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -492,9 +492,11 @@
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 @@
}
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 @@
}
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 1f2a1ed..7fb9247 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 @@
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 d701a97..3b09554 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
@@ -54,6 +54,7 @@
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 e3aa4ef..51373c1 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -230,7 +230,8 @@
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 @@
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 e2b0ed1..8dd1a34 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -55,7 +55,7 @@
(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 c311901..4f44d1b 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
@@ -40,6 +40,7 @@
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 63a2bd0..81c37b0 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 e11a809..42aec7d 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::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 @@
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;
- }
-
- void turnVibratorOff(State& state) {
- checkHalResult(halCall<void>(mController, [](auto hal) { return hal->off(); }), state);
+ if ((result.value() & query) != query) {
+ state.SkipWithMessage("missing capability");
+ return true;
+ }
+ return false;
}
template <class R>
- bool checkHalResult(const vibrator::HalResult<R>& result, State& state) {
+ 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 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 @@
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, 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();
+
+ // Test
+ if (shouldSkipWithError<void>(onFn, "on", state)) {
+ return;
}
+
+ // Cleanup
+ state.PauseTiming();
+ if (shouldSkipWithError(turnVibratorOff(), state)) {
+ return;
+ }
+ cb.waitForComplete();
+ state.ResumeTiming();
}
});
-BENCHMARK_WRAPPER(VibratorBench, off, {
- auto duration = 60s;
- auto callback = []() {};
+BENCHMARK_WRAPPER(SlowVibratorBench, off, {
+ auto duration = MAX_ON_DURATION_MS;
for (auto _ : state) {
+ // Setup
state.PauseTiming();
- auto ret =
- halCall<void>(mController, [&](auto hal) { return hal->on(duration, callback); });
- if (!checkHalResult(ret, 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();
- turnVibratorOff(state);
+
+ // Test
+ if (shouldSkipWithError<void>(offFn, "off", state)) {
+ return;
+ }
+
+ // Cleanup
+ state.PauseTiming();
+ cb.waitForComplete();
+ state.ResumeTiming();
}
});
BENCHMARK_WRAPPER(VibratorBench, setAmplitude, {
- if (!hasCapabilities(vibrator::Capabilities::AMPLITUDE_CONTROL, state)) {
- state.SkipWithMessage("missing capability");
+ 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); };
- for (auto _ : state) {
- state.PauseTiming();
- vibrator::HalController controller;
- controller.init();
- auto result =
- halCall<void>(controller, [&](auto hal) { return hal->on(duration, callback); });
- if (!checkHalResult(result, state)) {
- continue;
- }
- state.ResumeTiming();
- auto ret =
- halCall<void>(controller, [&](auto hal) { return hal->setAmplitude(amplitude); });
- state.PauseTiming();
- if (checkHalResult(ret, state)) {
- turnVibratorOff(state);
- }
- }
-});
-
-BENCHMARK_WRAPPER(VibratorBench, setAmplitudeCached, {
- if (!hasCapabilities(vibrator::Capabilities::AMPLITUDE_CONTROL, state)) {
- state.SkipWithMessage("missing capability");
+ auto onFn = [&](auto hal) { return hal->on(duration, [&]() {}); };
+ if (shouldSkipWithError<void>(onFn, "on", state)) {
return;
}
- auto duration = 60s;
- auto callback = []() {};
- auto amplitude = 1.0f;
-
- 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);
+ if (shouldSkipWithError<void>(setAmplitudeFn, "setAmplitude", state)) {
+ return;
+ }
}
});
BENCHMARK_WRAPPER(VibratorBench, setExternalControl, {
- if (!hasCapabilities(vibrator::Capabilities::EXTERNAL_CONTROL, state)) {
- state.SkipWithMessage("missing capability");
+ if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::EXTERNAL_CONTROL, state)) {
return;
}
+ auto enableExternalControlFn = [](auto hal) { return hal->setExternalControl(true); };
+
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);
+ // Test
+ if (shouldSkipWithError<void>(enableExternalControlFn, "setExternalControl true", state)) {
+ return;
}
+
+ // Cleanup
+ state.PauseTiming();
+ if (shouldSkipWithError(disableExternalControl(), state)) {
+ return;
+ }
+ state.ResumeTiming();
}
});
-BENCHMARK_WRAPPER(VibratorBench, setExternalControlCached, {
- if (!hasCapabilities(vibrator::Capabilities::EXTERNAL_CONTROL, state)) {
- state.SkipWithMessage("missing capability");
- return;
- }
-
- for (auto _ : state) {
- state.ResumeTiming();
- auto result =
- halCall<void>(mController, [](auto hal) { return hal->setExternalControl(true); });
- state.PauseTiming();
- if (checkHalResult(result, state)) {
- auto ret = halCall<void>(mController,
- [](auto hal) { return hal->setExternalControl(false); });
- checkHalResult(ret, state);
- }
- }
-});
-
-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);
-
- for (auto _ : state) {
- auto ret =
- halCall<void>(mController, [&](auto hal) { return hal->setAmplitude(amplitude); });
- checkHalResult(ret, state);
+ if (shouldSkipWithError<void>(enableExternalControlFn, "setExternalControl true", state)) {
+ return;
}
- auto offResult =
- halCall<void>(mController, [](auto hal) { return hal->setExternalControl(false); });
- checkHalResult(offResult, state);
+ for (auto _ : state) {
+ if (shouldSkipWithError<void>(setAmplitudeFn, "setExternalAmplitude", state)) {
+ return;
+ }
+ }
});
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 @@
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 @@
}
};
+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 @@
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);
- });
- state.PauseTiming();
- if (checkHalResult(ret, state)) {
- auto disableResult =
- halCall<void>(mController, [&](auto hal) { return hal->alwaysOnDisable(id); });
- checkHalResult(disableResult, state);
+ // Test
+ if (shouldSkipWithError<void>(enableFn, "alwaysOnEnable", state)) {
+ return;
}
+
+ // Cleanup
+ state.PauseTiming();
+ 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 @@
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 @@
auto effect = getEffect(state);
auto strength = getStrength(state);
- auto callback = []() {};
for (auto _ : state) {
- state.ResumeTiming();
- auto ret = halCall<std::chrono::milliseconds>(mController, [&](auto hal) {
- return hal->performEffect(effect, strength, callback);
- });
+ // Setup
state.PauseTiming();
- if (checkHalResult(ret, state)) {
- turnVibratorOff(state);
+ auto cb = mCallbacks.next();
+ auto performFn = [&](auto hal) {
+ return hal->performEffect(effect, strength, cb.completeFn());
+ };
+ state.ResumeTiming();
+
+ // Test
+ if (shouldSkipWithError<milliseconds>(performFn, "performEffect", state)) {
+ return;
}
+
+ // Cleanup
+ state.PauseTiming();
+ 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 @@
}
};
-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 @@
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) {
- state.ResumeTiming();
- auto ret = halCall<std::chrono::milliseconds>(mController, [&](auto hal) {
- return hal->performComposedEffect(effects, callback);
- });
+ // Setup
state.PauseTiming();
- if (checkHalResult(ret, state)) {
- turnVibratorOff(state);
+ auto cb = mCallbacks.next();
+ auto performFn = [&](auto hal) {
+ return hal->performComposedEffect(effects, cb.completeFn());
+ };
+ state.ResumeTiming();
+
+ // Test
+ if (shouldSkipWithError<milliseconds>(performFn, "performComposedEffect", state)) {
+ return;
}
+
+ // Cleanup
+ state.PauseTiming();
+ if (shouldSkipWithError(turnVibratorOff(), state)) {
+ return;
+ }
+ cb.waitForComplete();
+ state.ResumeTiming();
}
});