Merge "Avoid string allocations when checking for permissions" into main
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp
index 341fabb..de0aafa 100644
--- a/cmds/dumpstate/Android.bp
+++ b/cmds/dumpstate/Android.bp
@@ -37,9 +37,6 @@
name: "libdumpstateutil",
defaults: ["dumpstate_cflag_defaults"],
vendor_available: true,
- vndk: {
- enabled: true,
- },
srcs: [
"DumpstateInternal.cpp",
"DumpstateUtil.cpp",
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index a5c0c60..95a05cd 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -790,7 +790,8 @@
if (OK !=
IInterface::asBinder(cb)->linkToDeath(sp<ServiceManager>::fromExisting(this))) {
- ALOGE("%s Could not linkToDeath when adding client callback for %s", name.c_str());
+ ALOGE("%s Could not linkToDeath when adding client callback for %s",
+ ctx.toDebugString().c_str(), name.c_str());
return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Couldn't linkToDeath.");
}
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/performance_hint.h b/include/android/performance_hint.h
index 3c82d88..97e4dc0 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -114,6 +114,9 @@
* API, the client is expected to call {@link APerformanceHint_reportActualWorkDuration} each
* cycle to report the actual time taken to complete to the system.
*
+ * Note, methods of {@link APerformanceHintSession_*} are not thread safe so callers must
+ * ensure thread safety.
+ *
* All timings should be from `std::chrono::steady_clock` or `clock_gettime(CLOCK_MONOTONIC, ...)`
*/
typedef struct APerformanceHintSession APerformanceHintSession;
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/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/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/battery/MultiStateCounter.h b/libs/battery/MultiStateCounter.h
index 7da8d51..04b7186 100644
--- a/libs/battery/MultiStateCounter.h
+++ b/libs/battery/MultiStateCounter.h
@@ -62,6 +62,12 @@
void setState(state_t state, time_t timestamp);
+ /**
+ * Copies the current state and accumulated times-in-state from the source. Resets
+ * the accumulated value.
+ */
+ void copyStatesFrom(const MultiStateCounter<T>& source);
+
void setValue(state_t state, const T& value);
/**
@@ -193,6 +199,22 @@
}
template <class T>
+void MultiStateCounter<T>::copyStatesFrom(const MultiStateCounter<T>& source) {
+ if (stateCount != source.stateCount) {
+ ALOGE("State count mismatch: %u vs. %u\n", stateCount, source.stateCount);
+ return;
+ }
+
+ currentState = source.currentState;
+ for (int i = 0; i < stateCount; i++) {
+ states[i].timeInStateSinceUpdate = source.states[i].timeInStateSinceUpdate;
+ states[i].counter = emptyValue;
+ }
+ lastStateChangeTimestamp = source.lastStateChangeTimestamp;
+ lastUpdateTimestamp = source.lastUpdateTimestamp;
+}
+
+template <class T>
void MultiStateCounter<T>::setValue(state_t state, const T& value) {
states[state].counter = value;
}
diff --git a/libs/battery/MultiStateCounterTest.cpp b/libs/battery/MultiStateCounterTest.cpp
index cb11a54..a51a38a 100644
--- a/libs/battery/MultiStateCounterTest.cpp
+++ b/libs/battery/MultiStateCounterTest.cpp
@@ -72,6 +72,22 @@
EXPECT_DOUBLE_EQ(4.0, testCounter.getCount(2));
}
+TEST_F(MultiStateCounterTest, copyStatesFrom) {
+ DoubleMultiStateCounter sourceCounter(3, 0);
+
+ sourceCounter.updateValue(0, 0);
+ sourceCounter.setState(1, 0);
+ sourceCounter.setState(2, 1000);
+
+ DoubleMultiStateCounter testCounter(3, 0);
+ testCounter.copyStatesFrom(sourceCounter);
+ testCounter.updateValue(6.0, 3000);
+
+ EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
+ EXPECT_DOUBLE_EQ(2.0, testCounter.getCount(1));
+ EXPECT_DOUBLE_EQ(4.0, testCounter.getCount(2));
+}
+
TEST_F(MultiStateCounterTest, setEnabled) {
DoubleMultiStateCounter testCounter(3, 0);
testCounter.updateValue(0, 0);
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index eec12e4..57a48d7 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -229,6 +229,7 @@
cc_library_headers {
name: "trusty_mock_headers",
host_supported: true,
+ vendor_available: true,
export_include_dirs: [
"trusty/include",
@@ -243,12 +244,18 @@
cc_defaults {
name: "trusty_mock_defaults",
host_supported: true,
+ vendor_available: true,
header_libs: [
"libbinder_headers_base",
"liblog_stub",
"trusty_mock_headers",
],
+ export_header_lib_headers: [
+ "libbinder_headers_base",
+ "liblog_stub",
+ "trusty_mock_headers",
+ ],
shared_libs: [
"libutils_binder_sdk",
@@ -358,9 +365,6 @@
// for vndbinder
vendor_available: true,
- vndk: {
- enabled: true,
- },
recovery_available: true,
double_loadable: true,
// TODO(b/153609531): remove when no longer needed.
@@ -669,6 +673,7 @@
"//packages/modules/Virtualization:__subpackages__",
"//device/google/cuttlefish/shared/minidroid:__subpackages__",
"//system/software_defined_vehicle:__subpackages__",
+ "//visibility:any_system_partition",
],
}
diff --git a/libs/binder/ndk/Android.bp b/libs/binder/ndk/Android.bp
index 30dbddd..9a2d14a 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -50,8 +50,10 @@
],
cflags: [
+ "-DBINDER_WITH_KERNEL_IPC",
"-Wall",
"-Wextra",
+ "-Wextra-semi",
"-Werror",
],
@@ -145,6 +147,46 @@
afdo: true,
}
+cc_library {
+ name: "libbinder_ndk_on_trusty_mock",
+ defaults: [
+ "trusty_mock_defaults",
+ ],
+
+ export_include_dirs: [
+ "include_cpp",
+ "include_ndk",
+ "include_platform",
+ ],
+
+ srcs: [
+ "ibinder.cpp",
+ "libbinder.cpp",
+ "parcel.cpp",
+ "stability.cpp",
+ "status.cpp",
+ ],
+
+ shared_libs: [
+ "libbinder_on_trusty_mock",
+ ],
+
+ header_libs: [
+ "libbinder_trusty_ndk_headers",
+ ],
+ export_header_lib_headers: [
+ "libbinder_trusty_ndk_headers",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ ],
+
+ visibility: ["//frameworks/native/libs/binder:__subpackages__"],
+}
+
cc_library_headers {
name: "libbinder_headers_platform_shared",
export_include_dirs: ["include_cpp"],
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index e2ede3f..d929ec8 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"
@@ -44,7 +45,9 @@
static const void* kId = "ABBinder";
static void* kValue = static_cast<void*>(new bool{true});
-void clean(const void* /*id*/, void* /*obj*/, void* /*cookie*/){/* do nothing */};
+void clean(const void* /*id*/, void* /*obj*/, void* /*cookie*/) {
+ /* do nothing */
+}
static void attach(const sp<IBinder>& binder) {
auto alreadyAttached = binder->attachObject(kId, kValue, nullptr /*cookie*/, clean);
@@ -69,7 +72,7 @@
LOG_ALWAYS_FATAL_IF(id != kId, "%p %p %p", id, obj, cookie);
delete static_cast<Value*>(obj);
-};
+}
} // namespace ABpBinderTag
@@ -211,6 +214,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();
@@ -602,6 +611,7 @@
return recipient->unlinkToDeath(binder->getBinder(), cookie);
}
+#ifdef BINDER_WITH_KERNEL_IPC
uid_t AIBinder_getCallingUid() {
return ::android::IPCThreadState::self()->getCallingUid();
}
@@ -613,6 +623,7 @@
bool AIBinder_isHandlingTransaction() {
return ::android::IPCThreadState::self()->getServingStackPointer() != nullptr;
}
+#endif
void AIBinder_incStrong(AIBinder* binder) {
if (binder == nullptr) {
@@ -830,9 +841,11 @@
localBinder->setRequestingSid(requestingSid);
}
+#ifdef BINDER_WITH_KERNEL_IPC
const char* AIBinder_getCallingSid() {
return ::android::IPCThreadState::self()->getCallingSid();
}
+#endif
void AIBinder_setMinSchedulerPolicy(AIBinder* binder, int policy, int priority) {
binder->asABBinder()->setMinSchedulerPolicy(policy, priority);
diff --git a/libs/binder/rust/Android.bp b/libs/binder/rust/Android.bp
index 57a38dc..ef556d7 100644
--- a/libs/binder/rust/Android.bp
+++ b/libs/binder/rust/Android.bp
@@ -35,6 +35,21 @@
}
rust_library {
+ name: "libbinder_rs_on_trusty_mock",
+ crate_name: "binder",
+ srcs: ["src/lib.rs"],
+ cfgs: [
+ "trusty",
+ ],
+ rustlibs: [
+ "libbinder_ndk_sys_on_trusty_mock",
+ "libdowncast_rs",
+ "liblibc",
+ ],
+ vendor: true,
+}
+
+rust_library {
name: "libbinder_tokio_rs",
crate_name: "binder_tokio",
srcs: ["binder_tokio/lib.rs"],
@@ -89,6 +104,26 @@
visibility: [":__subpackages__"],
}
+rust_library {
+ name: "libbinder_ndk_sys_on_trusty_mock",
+ crate_name: "binder_ndk_sys",
+ srcs: [
+ "sys/lib.rs",
+ ":libbinder_ndk_bindgen_on_trusty_mock",
+ ],
+ cfgs: [
+ "trusty",
+ ],
+ shared_libs: [
+ "libbinder_ndk_on_trusty_mock",
+ ],
+ vendor: true,
+ // Lints are checked separately for libbinder_ndk_sys.
+ // The Trusty mock copy pulls in extra headers that
+ // don't pass the lints for the bindgen output.
+ lints: "none",
+}
+
rust_bindgen {
name: "libbinder_ndk_bindgen",
crate_name: "binder_ndk_bindgen",
@@ -125,6 +160,28 @@
min_sdk_version: "Tiramisu",
}
+rust_bindgen {
+ name: "libbinder_ndk_bindgen_on_trusty_mock",
+ crate_name: "binder_ndk_bindgen",
+ wrapper_src: "sys/BinderBindings.hpp",
+ source_stem: "bindings",
+ defaults: [
+ "trusty_mock_defaults",
+ ],
+
+ bindgen_flag_files: [
+ // Unfortunately the only way to specify the rust_non_exhaustive enum
+ // style for a type is to make it the default
+ // and then specify constified enums for the enums we don't want
+ // rustified
+ "libbinder_ndk_bindgen_flags.txt",
+ ],
+ shared_libs: [
+ "libbinder_ndk_on_trusty_mock",
+ "libc++",
+ ],
+}
+
rust_test {
name: "libbinder_rs-internal_test",
crate_name: "binder",
diff --git a/libs/binder/rust/rpcbinder/Android.bp b/libs/binder/rust/rpcbinder/Android.bp
index 535ce01..2e46345 100644
--- a/libs/binder/rust/rpcbinder/Android.bp
+++ b/libs/binder/rust/rpcbinder/Android.bp
@@ -18,6 +18,7 @@
"libbinder_ndk_sys",
"libbinder_rpc_unstable_bindgen_sys",
"libbinder_rs",
+ "libcfg_if",
"libdowncast_rs",
"libforeign_types",
"liblibc",
diff --git a/libs/binder/rust/rpcbinder/src/lib.rs b/libs/binder/rust/rpcbinder/src/lib.rs
index 163f000..7e5c9dd 100644
--- a/libs/binder/rust/rpcbinder/src/lib.rs
+++ b/libs/binder/rust/rpcbinder/src/lib.rs
@@ -16,10 +16,10 @@
//! API for RPC Binder services.
-#[cfg(not(target_os = "trusty"))]
mod server;
mod session;
+pub use server::RpcServer;
#[cfg(not(target_os = "trusty"))]
-pub use server::{RpcServer, RpcServerRef};
+pub use server::RpcServerRef;
pub use session::{FileDescriptorTransportMode, RpcSession, RpcSessionRef};
diff --git a/libs/binder/rust/rpcbinder/src/server.rs b/libs/binder/rust/rpcbinder/src/server.rs
index 6fda878..d6bdbd8 100644
--- a/libs/binder/rust/rpcbinder/src/server.rs
+++ b/libs/binder/rust/rpcbinder/src/server.rs
@@ -14,160 +14,12 @@
* limitations under the License.
*/
-use crate::session::FileDescriptorTransportMode;
-use binder::{unstable_api::AsNative, SpIBinder};
-use binder_rpc_unstable_bindgen::ARpcServer;
-use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
-use std::ffi::CString;
-use std::io::{Error, ErrorKind};
-use std::os::unix::io::{IntoRawFd, OwnedFd};
-
-foreign_type! {
- type CType = binder_rpc_unstable_bindgen::ARpcServer;
- fn drop = binder_rpc_unstable_bindgen::ARpcServer_free;
-
- /// A type that represents a foreign instance of RpcServer.
- #[derive(Debug)]
- pub struct RpcServer;
- /// A borrowed RpcServer.
- pub struct RpcServerRef;
-}
-
-/// SAFETY: The opaque handle can be cloned freely.
-unsafe impl Send for RpcServer {}
-/// SAFETY: The underlying C++ RpcServer class is thread-safe.
-unsafe impl Sync for RpcServer {}
-
-impl RpcServer {
- /// Creates a binder RPC server, serving the supplied binder service implementation on the given
- /// vsock port. Only connections from the given CID are accepted.
- ///
- // Set `cid` to libc::VMADDR_CID_ANY to accept connections from any client.
- // Set `cid` to libc::VMADDR_CID_LOCAL to only bind to the local vsock interface.
- pub fn new_vsock(mut service: SpIBinder, cid: u32, port: u32) -> Result<RpcServer, Error> {
- let service = service.as_native_mut();
-
- // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
- // Plus the binder objects are threadsafe.
- unsafe {
- Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newVsock(
- service, cid, port,
- ))
- }
- }
-
- /// Creates a binder RPC server, serving the supplied binder service implementation on the given
- /// socket file descriptor. The socket should be bound to an address before calling this
- /// function.
- pub fn new_bound_socket(
- mut service: SpIBinder,
- socket_fd: OwnedFd,
- ) -> Result<RpcServer, Error> {
- let service = service.as_native_mut();
-
- // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
- // Plus the binder objects are threadsafe.
- // The server takes ownership of the socket FD.
- unsafe {
- Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newBoundSocket(
- service,
- socket_fd.into_raw_fd(),
- ))
- }
- }
-
- /// Creates a binder RPC server that bootstraps sessions using an existing Unix domain socket
- /// pair, with a given root IBinder object. Callers should create a pair of SOCK_STREAM Unix
- /// domain sockets, pass one to the server and the other to the client. Multiple client session
- /// can be created from the client end of the pair.
- pub fn new_unix_domain_bootstrap(
- mut service: SpIBinder,
- bootstrap_fd: OwnedFd,
- ) -> Result<RpcServer, Error> {
- let service = service.as_native_mut();
-
- // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
- // Plus the binder objects are threadsafe.
- // The server takes ownership of the bootstrap FD.
- unsafe {
- Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newUnixDomainBootstrap(
- service,
- bootstrap_fd.into_raw_fd(),
- ))
- }
- }
-
- /// Creates a binder RPC server, serving the supplied binder service implementation on the given
- /// IP address and port.
- pub fn new_inet(mut service: SpIBinder, address: &str, port: u32) -> Result<RpcServer, Error> {
- let address = match CString::new(address) {
- Ok(s) => s,
- Err(e) => {
- log::error!("Cannot convert {} to CString. Error: {:?}", address, e);
- return Err(Error::from(ErrorKind::InvalidInput));
- }
- };
- let service = service.as_native_mut();
-
- // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
- // Plus the binder objects are threadsafe.
- unsafe {
- Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newInet(
- service,
- address.as_ptr(),
- port,
- ))
- }
- }
-
- unsafe fn checked_from_ptr(ptr: *mut ARpcServer) -> Result<RpcServer, Error> {
- if ptr.is_null() {
- return Err(Error::new(ErrorKind::Other, "Failed to start server"));
- }
- // SAFETY: Our caller must pass us a valid or null pointer, and we've checked that it's not
- // null.
- Ok(unsafe { RpcServer::from_ptr(ptr) })
- }
-}
-
-impl RpcServerRef {
- /// Sets the list of file descriptor transport modes supported by this server.
- pub fn set_supported_file_descriptor_transport_modes(
- &self,
- modes: &[FileDescriptorTransportMode],
- ) {
- // SAFETY: Does not keep the pointer after returning does, nor does it
- // read past its boundary. Only passes the 'self' pointer as an opaque handle.
- unsafe {
- binder_rpc_unstable_bindgen::ARpcServer_setSupportedFileDescriptorTransportModes(
- self.as_ptr(),
- modes.as_ptr(),
- modes.len(),
- )
- }
- }
-
- /// Starts a new background thread and calls join(). Returns immediately.
- pub fn start(&self) {
- // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
- unsafe { binder_rpc_unstable_bindgen::ARpcServer_start(self.as_ptr()) };
- }
-
- /// Joins the RpcServer thread. The call blocks until the server terminates.
- /// This must be called from exactly one thread.
- pub fn join(&self) {
- // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
- unsafe { binder_rpc_unstable_bindgen::ARpcServer_join(self.as_ptr()) };
- }
-
- /// Shuts down the running RpcServer. Can be called multiple times and from
- /// multiple threads. Called automatically during drop().
- pub fn shutdown(&self) -> Result<(), Error> {
- // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
- if unsafe { binder_rpc_unstable_bindgen::ARpcServer_shutdown(self.as_ptr()) } {
- Ok(())
- } else {
- Err(Error::from(ErrorKind::UnexpectedEof))
- }
+cfg_if::cfg_if! {
+ if #[cfg(target_os = "trusty")] {
+ mod trusty;
+ pub use trusty::*;
+ } else {
+ mod android;
+ pub use android::*;
}
}
diff --git a/libs/binder/rust/rpcbinder/src/server/android.rs b/libs/binder/rust/rpcbinder/src/server/android.rs
new file mode 100644
index 0000000..ad0365b
--- /dev/null
+++ b/libs/binder/rust/rpcbinder/src/server/android.rs
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use crate::session::FileDescriptorTransportMode;
+use binder::{unstable_api::AsNative, SpIBinder};
+use binder_rpc_unstable_bindgen::ARpcServer;
+use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
+use std::ffi::CString;
+use std::io::{Error, ErrorKind};
+use std::os::unix::io::{IntoRawFd, OwnedFd};
+
+foreign_type! {
+ type CType = binder_rpc_unstable_bindgen::ARpcServer;
+ fn drop = binder_rpc_unstable_bindgen::ARpcServer_free;
+
+ /// A type that represents a foreign instance of RpcServer.
+ #[derive(Debug)]
+ pub struct RpcServer;
+ /// A borrowed RpcServer.
+ pub struct RpcServerRef;
+}
+
+/// SAFETY: The opaque handle can be cloned freely.
+unsafe impl Send for RpcServer {}
+/// SAFETY: The underlying C++ RpcServer class is thread-safe.
+unsafe impl Sync for RpcServer {}
+
+impl RpcServer {
+ /// Creates a binder RPC server, serving the supplied binder service implementation on the given
+ /// vsock port. Only connections from the given CID are accepted.
+ ///
+ // Set `cid` to libc::VMADDR_CID_ANY to accept connections from any client.
+ // Set `cid` to libc::VMADDR_CID_LOCAL to only bind to the local vsock interface.
+ pub fn new_vsock(mut service: SpIBinder, cid: u32, port: u32) -> Result<RpcServer, Error> {
+ let service = service.as_native_mut();
+
+ // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
+ // Plus the binder objects are threadsafe.
+ unsafe {
+ Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newVsock(
+ service, cid, port,
+ ))
+ }
+ }
+
+ /// Creates a binder RPC server, serving the supplied binder service implementation on the given
+ /// socket file descriptor. The socket should be bound to an address before calling this
+ /// function.
+ pub fn new_bound_socket(
+ mut service: SpIBinder,
+ socket_fd: OwnedFd,
+ ) -> Result<RpcServer, Error> {
+ let service = service.as_native_mut();
+
+ // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
+ // Plus the binder objects are threadsafe.
+ // The server takes ownership of the socket FD.
+ unsafe {
+ Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newBoundSocket(
+ service,
+ socket_fd.into_raw_fd(),
+ ))
+ }
+ }
+
+ /// Creates a binder RPC server that bootstraps sessions using an existing Unix domain socket
+ /// pair, with a given root IBinder object. Callers should create a pair of SOCK_STREAM Unix
+ /// domain sockets, pass one to the server and the other to the client. Multiple client session
+ /// can be created from the client end of the pair.
+ pub fn new_unix_domain_bootstrap(
+ mut service: SpIBinder,
+ bootstrap_fd: OwnedFd,
+ ) -> Result<RpcServer, Error> {
+ let service = service.as_native_mut();
+
+ // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
+ // Plus the binder objects are threadsafe.
+ // The server takes ownership of the bootstrap FD.
+ unsafe {
+ Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newUnixDomainBootstrap(
+ service,
+ bootstrap_fd.into_raw_fd(),
+ ))
+ }
+ }
+
+ /// Creates a binder RPC server, serving the supplied binder service implementation on the given
+ /// IP address and port.
+ pub fn new_inet(mut service: SpIBinder, address: &str, port: u32) -> Result<RpcServer, Error> {
+ let address = match CString::new(address) {
+ Ok(s) => s,
+ Err(e) => {
+ log::error!("Cannot convert {} to CString. Error: {:?}", address, e);
+ return Err(Error::from(ErrorKind::InvalidInput));
+ }
+ };
+ let service = service.as_native_mut();
+
+ // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
+ // Plus the binder objects are threadsafe.
+ unsafe {
+ Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newInet(
+ service,
+ address.as_ptr(),
+ port,
+ ))
+ }
+ }
+
+ unsafe fn checked_from_ptr(ptr: *mut ARpcServer) -> Result<RpcServer, Error> {
+ if ptr.is_null() {
+ return Err(Error::new(ErrorKind::Other, "Failed to start server"));
+ }
+ // SAFETY: Our caller must pass us a valid or null pointer, and we've checked that it's not
+ // null.
+ Ok(unsafe { RpcServer::from_ptr(ptr) })
+ }
+}
+
+impl RpcServerRef {
+ /// Sets the list of file descriptor transport modes supported by this server.
+ pub fn set_supported_file_descriptor_transport_modes(
+ &self,
+ modes: &[FileDescriptorTransportMode],
+ ) {
+ // SAFETY: Does not keep the pointer after returning does, nor does it
+ // read past its boundary. Only passes the 'self' pointer as an opaque handle.
+ unsafe {
+ binder_rpc_unstable_bindgen::ARpcServer_setSupportedFileDescriptorTransportModes(
+ self.as_ptr(),
+ modes.as_ptr(),
+ modes.len(),
+ )
+ }
+ }
+
+ /// Starts a new background thread and calls join(). Returns immediately.
+ pub fn start(&self) {
+ // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
+ unsafe { binder_rpc_unstable_bindgen::ARpcServer_start(self.as_ptr()) };
+ }
+
+ /// Joins the RpcServer thread. The call blocks until the server terminates.
+ /// This must be called from exactly one thread.
+ pub fn join(&self) {
+ // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
+ unsafe { binder_rpc_unstable_bindgen::ARpcServer_join(self.as_ptr()) };
+ }
+
+ /// Shuts down the running RpcServer. Can be called multiple times and from
+ /// multiple threads. Called automatically during drop().
+ pub fn shutdown(&self) -> Result<(), Error> {
+ // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
+ if unsafe { binder_rpc_unstable_bindgen::ARpcServer_shutdown(self.as_ptr()) } {
+ Ok(())
+ } else {
+ Err(Error::from(ErrorKind::UnexpectedEof))
+ }
+ }
+}
diff --git a/libs/binder/rust/rpcbinder/src/server/trusty.rs b/libs/binder/rust/rpcbinder/src/server/trusty.rs
new file mode 100644
index 0000000..fe45dec
--- /dev/null
+++ b/libs/binder/rust/rpcbinder/src/server/trusty.rs
@@ -0,0 +1,161 @@
+/*
+ * 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.
+ */
+
+use binder::{unstable_api::AsNative, SpIBinder};
+use libc::size_t;
+use std::ffi::{c_char, c_void};
+use std::ptr;
+use tipc::{ConnectResult, Handle, MessageResult, PortCfg, TipcError, UnbufferedService, Uuid};
+
+pub trait PerSessionCallback: Fn(Uuid) -> Option<SpIBinder> + Send + Sync + 'static {}
+impl<T> PerSessionCallback for T where T: Fn(Uuid) -> Option<SpIBinder> + Send + Sync + 'static {}
+
+pub struct RpcServer {
+ inner: *mut binder_rpc_server_bindgen::ARpcServerTrusty,
+}
+
+/// SAFETY: The opaque handle points to a heap allocation
+/// that should be process-wide and not tied to the current thread.
+unsafe impl Send for RpcServer {}
+/// SAFETY: The underlying C++ RpcServer class is thread-safe.
+unsafe impl Sync for RpcServer {}
+
+impl Drop for RpcServer {
+ fn drop(&mut self) {
+ // SAFETY: `ARpcServerTrusty_delete` is the correct destructor to call
+ // on pointers returned by `ARpcServerTrusty_new`.
+ unsafe {
+ binder_rpc_server_bindgen::ARpcServerTrusty_delete(self.inner);
+ }
+ }
+}
+
+impl RpcServer {
+ /// Allocates a new RpcServer object.
+ pub fn new(service: SpIBinder) -> RpcServer {
+ Self::new_per_session(move |_uuid| Some(service.clone()))
+ }
+
+ /// Allocates a new per-session RpcServer object.
+ ///
+ /// Per-session objects take a closure that gets called once
+ /// for every new connection. The closure gets the UUID of
+ /// the peer and can accept or reject that connection.
+ pub fn new_per_session<F: PerSessionCallback>(f: F) -> RpcServer {
+ // SAFETY: Takes ownership of the returned handle, which has correct refcount.
+ let inner = unsafe {
+ binder_rpc_server_bindgen::ARpcServerTrusty_newPerSession(
+ Some(per_session_callback_wrapper::<F>),
+ Box::into_raw(Box::new(f)).cast(),
+ Some(per_session_callback_deleter::<F>),
+ )
+ };
+ RpcServer { inner }
+ }
+}
+
+unsafe extern "C" fn per_session_callback_wrapper<F: PerSessionCallback>(
+ uuid_ptr: *const c_void,
+ len: size_t,
+ cb_ptr: *mut c_char,
+) -> *mut binder_rpc_server_bindgen::AIBinder {
+ // SAFETY: This callback should only get called while the RpcServer is alive.
+ let cb = unsafe { &mut *cb_ptr.cast::<F>() };
+
+ if len != std::mem::size_of::<Uuid>() {
+ return ptr::null_mut();
+ }
+
+ // SAFETY: On the previous lines we check that we got exactly the right amount of bytes.
+ let uuid = unsafe {
+ let mut uuid = std::mem::MaybeUninit::<Uuid>::uninit();
+ uuid.as_mut_ptr().copy_from(uuid_ptr.cast(), 1);
+ uuid.assume_init()
+ };
+
+ cb(uuid).map_or_else(ptr::null_mut, |b| {
+ // Prevent AIBinder_decStrong from being called before AIBinder_toPlatformBinder.
+ // The per-session callback in C++ is supposed to call AIBinder_decStrong on the
+ // pointer we return here.
+ std::mem::ManuallyDrop::new(b).as_native_mut().cast()
+ })
+}
+
+unsafe extern "C" fn per_session_callback_deleter<F: PerSessionCallback>(cb: *mut c_char) {
+ // SAFETY: shared_ptr calls this to delete the pointer we gave it.
+ // It should only get called once the last shared reference goes away.
+ unsafe {
+ drop(Box::<F>::from_raw(cb.cast()));
+ }
+}
+
+pub struct RpcServerConnection {
+ ctx: *mut c_void,
+}
+
+impl Drop for RpcServerConnection {
+ fn drop(&mut self) {
+ // We do not need to close handle_fd since we do not own it.
+ unsafe {
+ binder_rpc_server_bindgen::ARpcServerTrusty_handleChannelCleanup(self.ctx);
+ }
+ }
+}
+
+impl UnbufferedService for RpcServer {
+ type Connection = RpcServerConnection;
+
+ fn on_connect(
+ &self,
+ _port: &PortCfg,
+ handle: &Handle,
+ peer: &Uuid,
+ ) -> tipc::Result<ConnectResult<Self::Connection>> {
+ let mut conn = RpcServerConnection { ctx: std::ptr::null_mut() };
+ let rc = unsafe {
+ binder_rpc_server_bindgen::ARpcServerTrusty_handleConnect(
+ self.inner,
+ handle.as_raw_fd(),
+ peer.as_ptr().cast(),
+ &mut conn.ctx,
+ )
+ };
+ if rc < 0 {
+ Err(TipcError::from_uapi(rc.into()))
+ } else {
+ Ok(ConnectResult::Accept(conn))
+ }
+ }
+
+ fn on_message(
+ &self,
+ conn: &Self::Connection,
+ _handle: &Handle,
+ buffer: &mut [u8],
+ ) -> tipc::Result<MessageResult> {
+ assert!(buffer.is_empty());
+ let rc = unsafe { binder_rpc_server_bindgen::ARpcServerTrusty_handleMessage(conn.ctx) };
+ if rc < 0 {
+ Err(TipcError::from_uapi(rc.into()))
+ } else {
+ Ok(MessageResult::MaintainConnection)
+ }
+ }
+
+ fn on_disconnect(&self, conn: &Self::Connection) {
+ unsafe { binder_rpc_server_bindgen::ARpcServerTrusty_handleDisconnect(conn.ctx) };
+ }
+}
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index 16049f2..0f9c58c 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -100,7 +100,9 @@
mod native;
mod parcel;
mod proxy;
-#[cfg(not(target_os = "trusty"))]
+#[cfg(not(trusty))]
+mod service;
+#[cfg(not(trusty))]
mod state;
use binder_ndk_sys as sys;
@@ -108,16 +110,15 @@
pub use crate::binder_async::{BinderAsyncPool, BoxFuture};
pub use binder::{BinderFeatures, FromIBinder, IBinder, Interface, Strong, Weak};
pub use error::{ExceptionCode, IntoBinderResult, Status, StatusCode};
-pub use native::{
- add_service, force_lazy_services_persist, is_handling_transaction, register_lazy_service,
- LazyServiceGuard,
-};
pub use parcel::{ParcelFileDescriptor, Parcelable, ParcelableHolder};
-pub use proxy::{
- get_declared_instances, get_interface, get_service, is_declared, wait_for_interface,
- wait_for_service, DeathRecipient, SpIBinder, WpIBinder,
+pub use proxy::{DeathRecipient, SpIBinder, WpIBinder};
+#[cfg(not(trusty))]
+pub use service::{
+ add_service, force_lazy_services_persist, get_declared_instances, get_interface, get_service,
+ is_declared, is_handling_transaction, register_lazy_service, wait_for_interface,
+ wait_for_service, LazyServiceGuard,
};
-#[cfg(not(target_os = "trusty"))]
+#[cfg(not(trusty))]
pub use state::{ProcessState, ThreadState};
/// Binder result containing a [`Status`] on error.
diff --git a/libs/binder/rust/src/native.rs b/libs/binder/rust/src/native.rs
index 8ae010e..c87cc94 100644
--- a/libs/binder/rust/src/native.rs
+++ b/libs/binder/rust/src/native.rs
@@ -23,12 +23,11 @@
use crate::sys;
use std::convert::TryFrom;
-use std::ffi::{c_void, CStr, CString};
+use std::ffi::{c_void, CStr};
use std::io::Write;
use std::mem::ManuallyDrop;
use std::ops::Deref;
use std::os::raw::c_char;
-use std::sync::Mutex;
/// Rust wrapper around Binder remotable objects.
///
@@ -328,7 +327,7 @@
/// contains a `T` pointer in its user data. fd should be a non-owned file
/// descriptor, and args must be an array of null-terminated string
/// pointers with length num_args.
- #[cfg(not(target_os = "trusty"))]
+ #[cfg(not(trusty))]
unsafe extern "C" fn on_dump(
binder: *mut sys::AIBinder,
fd: i32,
@@ -375,7 +374,7 @@
}
/// Called to handle the `dump` transaction.
- #[cfg(target_os = "trusty")]
+ #[cfg(trusty)]
unsafe extern "C" fn on_dump(
_binder: *mut sys::AIBinder,
_fd: i32,
@@ -462,110 +461,6 @@
}
}
-/// Register a new service with the default service manager.
-///
-/// Registers the given binder object with the given identifier. If successful,
-/// this service can then be retrieved using that identifier.
-///
-/// This function will panic if the identifier contains a 0 byte (NUL).
-pub fn add_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
- let instance = CString::new(identifier).unwrap();
- let status =
- // Safety: `AServiceManager_addService` expects valid `AIBinder` and C
- // string pointers. Caller retains ownership of both pointers.
- // `AServiceManager_addService` creates a new strong reference and copies
- // the string, so both pointers need only be valid until the call returns.
- unsafe { sys::AServiceManager_addService(binder.as_native_mut(), instance.as_ptr()) };
- status_result(status)
-}
-
-/// Register a dynamic service via the LazyServiceRegistrar.
-///
-/// Registers the given binder object with the given identifier. If successful,
-/// this service can then be retrieved using that identifier. The service process
-/// will be shut down once all registered services are no longer in use.
-///
-/// If any service in the process is registered as lazy, all should be, otherwise
-/// the process may be shut down while a service is in use.
-///
-/// This function will panic if the identifier contains a 0 byte (NUL).
-pub fn register_lazy_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
- let instance = CString::new(identifier).unwrap();
- // Safety: `AServiceManager_registerLazyService` expects valid `AIBinder` and C
- // string pointers. Caller retains ownership of both
- // pointers. `AServiceManager_registerLazyService` creates a new strong reference
- // and copies the string, so both pointers need only be valid until the
- // call returns.
- let status = unsafe {
- sys::AServiceManager_registerLazyService(binder.as_native_mut(), instance.as_ptr())
- };
- status_result(status)
-}
-
-/// Prevent a process which registers lazy services from being shut down even when none
-/// of the services is in use.
-///
-/// If persist is true then shut down will be blocked until this function is called again with
-/// persist false. If this is to be the initial state, call this function before calling
-/// register_lazy_service.
-///
-/// Consider using [`LazyServiceGuard`] rather than calling this directly.
-pub fn force_lazy_services_persist(persist: bool) {
- // Safety: No borrowing or transfer of ownership occurs here.
- unsafe { sys::AServiceManager_forceLazyServicesPersist(persist) }
-}
-
-/// An RAII object to ensure a process which registers lazy services is not killed. During the
-/// lifetime of any of these objects the service manager will not not kill the process even if none
-/// of its lazy services are in use.
-#[must_use]
-#[derive(Debug)]
-pub struct LazyServiceGuard {
- // Prevent construction outside this module.
- _private: (),
-}
-
-// Count of how many LazyServiceGuard objects are in existence.
-static GUARD_COUNT: Mutex<u64> = Mutex::new(0);
-
-impl LazyServiceGuard {
- /// Create a new LazyServiceGuard to prevent the service manager prematurely killing this
- /// process.
- pub fn new() -> Self {
- let mut count = GUARD_COUNT.lock().unwrap();
- *count += 1;
- if *count == 1 {
- // It's important that we make this call with the mutex held, to make sure
- // that multiple calls (e.g. if the count goes 1 -> 0 -> 1) are correctly
- // sequenced. (That also means we can't just use an AtomicU64.)
- force_lazy_services_persist(true);
- }
- Self { _private: () }
- }
-}
-
-impl Drop for LazyServiceGuard {
- fn drop(&mut self) {
- let mut count = GUARD_COUNT.lock().unwrap();
- *count -= 1;
- if *count == 0 {
- force_lazy_services_persist(false);
- }
- }
-}
-
-impl Clone for LazyServiceGuard {
- fn clone(&self) -> Self {
- Self::new()
- }
-}
-
-impl Default for LazyServiceGuard {
- fn default() -> Self {
- Self::new()
- }
-}
-
/// Tests often create a base BBinder instance; so allowing the unit
/// type to be remotable translates nicely to Binder::new(()).
impl Remotable for () {
@@ -590,10 +485,3 @@
}
impl Interface for () {}
-
-/// Determine whether the current thread is currently executing an incoming
-/// transaction.
-pub fn is_handling_transaction() -> bool {
- // Safety: This method is always safe to call.
- unsafe { sys::AIBinder_isHandlingTransaction() }
-}
diff --git a/libs/binder/rust/src/proxy.rs b/libs/binder/rust/src/proxy.rs
index 7434e9d..340014a 100644
--- a/libs/binder/rust/src/proxy.rs
+++ b/libs/binder/rust/src/proxy.rs
@@ -29,11 +29,10 @@
use std::cmp::Ordering;
use std::convert::TryInto;
-use std::ffi::{c_void, CStr, CString};
+use std::ffi::{c_void, CString};
use std::fmt;
use std::mem;
use std::os::fd::AsRawFd;
-use std::os::raw::c_char;
use std::ptr;
use std::sync::Arc;
@@ -129,14 +128,6 @@
}
}
-fn interface_cast<T: FromIBinder + ?Sized>(service: Option<SpIBinder>) -> Result<Strong<T>> {
- if let Some(service) = service {
- FromIBinder::try_from(service)
- } else {
- Err(StatusCode::NAME_NOT_FOUND)
- }
-}
-
pub mod unstable_api {
use super::{sys, SpIBinder};
@@ -739,93 +730,6 @@
}
}
-/// Retrieve an existing service, blocking for a few seconds if it doesn't yet
-/// exist.
-pub fn get_service(name: &str) -> Option<SpIBinder> {
- let name = CString::new(name).ok()?;
- // Safety: `AServiceManager_getService` returns either a null pointer or a
- // valid pointer to an owned `AIBinder`. Either of these values is safe to
- // pass to `SpIBinder::from_raw`.
- unsafe { SpIBinder::from_raw(sys::AServiceManager_getService(name.as_ptr())) }
-}
-
-/// Retrieve an existing service, or start it if it is configured as a dynamic
-/// service and isn't yet started.
-pub fn wait_for_service(name: &str) -> Option<SpIBinder> {
- let name = CString::new(name).ok()?;
- // Safety: `AServiceManager_waitforService` returns either a null pointer or
- // a valid pointer to an owned `AIBinder`. Either of these values is safe to
- // pass to `SpIBinder::from_raw`.
- unsafe { SpIBinder::from_raw(sys::AServiceManager_waitForService(name.as_ptr())) }
-}
-
-/// Retrieve an existing service for a particular interface, blocking for a few
-/// seconds if it doesn't yet exist.
-pub fn get_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> {
- interface_cast(get_service(name))
-}
-
-/// Retrieve an existing service for a particular interface, or start it if it
-/// is configured as a dynamic service and isn't yet started.
-pub fn wait_for_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> {
- interface_cast(wait_for_service(name))
-}
-
-/// Check if a service is declared (e.g. in a VINTF manifest)
-pub fn is_declared(interface: &str) -> Result<bool> {
- let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?;
-
- // Safety: `interface` is a valid null-terminated C-style string and is only
- // borrowed for the lifetime of the call. The `interface` local outlives
- // this call as it lives for the function scope.
- unsafe { Ok(sys::AServiceManager_isDeclared(interface.as_ptr())) }
-}
-
-/// Retrieve all declared instances for a particular interface
-///
-/// For instance, if 'android.foo.IFoo/foo' is declared, and 'android.foo.IFoo'
-/// is passed here, then ["foo"] would be returned.
-pub fn get_declared_instances(interface: &str) -> Result<Vec<String>> {
- unsafe extern "C" fn callback(instance: *const c_char, opaque: *mut c_void) {
- // Safety: opaque was a mutable pointer created below from a Vec of
- // CString, and outlives this callback. The null handling here is just
- // to avoid the possibility of unwinding across C code if this crate is
- // ever compiled with panic=unwind.
- if let Some(instances) = unsafe { opaque.cast::<Vec<CString>>().as_mut() } {
- // Safety: instance is a valid null-terminated C string with a
- // lifetime at least as long as this function, and we immediately
- // copy it into an owned CString.
- unsafe {
- instances.push(CStr::from_ptr(instance).to_owned());
- }
- } else {
- eprintln!("Opaque pointer was null in get_declared_instances callback!");
- }
- }
-
- let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?;
- let mut instances: Vec<CString> = vec![];
- // Safety: `interface` and `instances` are borrowed for the length of this
- // call and both outlive the call. `interface` is guaranteed to be a valid
- // null-terminated C-style string.
- unsafe {
- sys::AServiceManager_forEachDeclaredInstance(
- interface.as_ptr(),
- &mut instances as *mut _ as *mut c_void,
- Some(callback),
- );
- }
-
- instances
- .into_iter()
- .map(CString::into_string)
- .collect::<std::result::Result<Vec<String>, _>>()
- .map_err(|e| {
- eprintln!("An interface instance name was not a valid UTF-8 string: {}", e);
- StatusCode::BAD_VALUE
- })
-}
-
/// Safety: `SpIBinder` guarantees that `binder` always contains a valid pointer
/// to an `AIBinder`, so we can trivially extract this pointer here.
unsafe impl AsNative<sys::AIBinder> for SpIBinder {
diff --git a/libs/binder/rust/src/service.rs b/libs/binder/rust/src/service.rs
new file mode 100644
index 0000000..3ca3b54
--- /dev/null
+++ b/libs/binder/rust/src/service.rs
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use crate::binder::{AsNative, FromIBinder, Strong};
+use crate::error::{status_result, Result, StatusCode};
+use crate::proxy::SpIBinder;
+use crate::sys;
+
+use std::ffi::{c_void, CStr, CString};
+use std::os::raw::c_char;
+use std::sync::Mutex;
+
+/// Register a new service with the default service manager.
+///
+/// Registers the given binder object with the given identifier. If successful,
+/// this service can then be retrieved using that identifier.
+///
+/// This function will panic if the identifier contains a 0 byte (NUL).
+pub fn add_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
+ let instance = CString::new(identifier).unwrap();
+ let status =
+ // Safety: `AServiceManager_addService` expects valid `AIBinder` and C
+ // string pointers. Caller retains ownership of both pointers.
+ // `AServiceManager_addService` creates a new strong reference and copies
+ // the string, so both pointers need only be valid until the call returns.
+ unsafe { sys::AServiceManager_addService(binder.as_native_mut(), instance.as_ptr()) };
+ status_result(status)
+}
+
+/// Register a dynamic service via the LazyServiceRegistrar.
+///
+/// Registers the given binder object with the given identifier. If successful,
+/// this service can then be retrieved using that identifier. The service process
+/// will be shut down once all registered services are no longer in use.
+///
+/// If any service in the process is registered as lazy, all should be, otherwise
+/// the process may be shut down while a service is in use.
+///
+/// This function will panic if the identifier contains a 0 byte (NUL).
+pub fn register_lazy_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
+ let instance = CString::new(identifier).unwrap();
+ // Safety: `AServiceManager_registerLazyService` expects valid `AIBinder` and C
+ // string pointers. Caller retains ownership of both
+ // pointers. `AServiceManager_registerLazyService` creates a new strong reference
+ // and copies the string, so both pointers need only be valid until the
+ // call returns.
+ let status = unsafe {
+ sys::AServiceManager_registerLazyService(binder.as_native_mut(), instance.as_ptr())
+ };
+ status_result(status)
+}
+
+/// Prevent a process which registers lazy services from being shut down even when none
+/// of the services is in use.
+///
+/// If persist is true then shut down will be blocked until this function is called again with
+/// persist false. If this is to be the initial state, call this function before calling
+/// register_lazy_service.
+///
+/// Consider using [`LazyServiceGuard`] rather than calling this directly.
+pub fn force_lazy_services_persist(persist: bool) {
+ // Safety: No borrowing or transfer of ownership occurs here.
+ unsafe { sys::AServiceManager_forceLazyServicesPersist(persist) }
+}
+
+/// An RAII object to ensure a process which registers lazy services is not killed. During the
+/// lifetime of any of these objects the service manager will not kill the process even if none
+/// of its lazy services are in use.
+#[must_use]
+#[derive(Debug)]
+pub struct LazyServiceGuard {
+ // Prevent construction outside this module.
+ _private: (),
+}
+
+// Count of how many LazyServiceGuard objects are in existence.
+static GUARD_COUNT: Mutex<u64> = Mutex::new(0);
+
+impl LazyServiceGuard {
+ /// Create a new LazyServiceGuard to prevent the service manager prematurely killing this
+ /// process.
+ pub fn new() -> Self {
+ let mut count = GUARD_COUNT.lock().unwrap();
+ *count += 1;
+ if *count == 1 {
+ // It's important that we make this call with the mutex held, to make sure
+ // that multiple calls (e.g. if the count goes 1 -> 0 -> 1) are correctly
+ // sequenced. (That also means we can't just use an AtomicU64.)
+ force_lazy_services_persist(true);
+ }
+ Self { _private: () }
+ }
+}
+
+impl Drop for LazyServiceGuard {
+ fn drop(&mut self) {
+ let mut count = GUARD_COUNT.lock().unwrap();
+ *count -= 1;
+ if *count == 0 {
+ force_lazy_services_persist(false);
+ }
+ }
+}
+
+impl Clone for LazyServiceGuard {
+ fn clone(&self) -> Self {
+ Self::new()
+ }
+}
+
+impl Default for LazyServiceGuard {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+/// Determine whether the current thread is currently executing an incoming
+/// transaction.
+pub fn is_handling_transaction() -> bool {
+ // Safety: This method is always safe to call.
+ unsafe { sys::AIBinder_isHandlingTransaction() }
+}
+
+fn interface_cast<T: FromIBinder + ?Sized>(service: Option<SpIBinder>) -> Result<Strong<T>> {
+ if let Some(service) = service {
+ FromIBinder::try_from(service)
+ } else {
+ Err(StatusCode::NAME_NOT_FOUND)
+ }
+}
+
+/// Retrieve an existing service, blocking for a few seconds if it doesn't yet
+/// exist.
+pub fn get_service(name: &str) -> Option<SpIBinder> {
+ let name = CString::new(name).ok()?;
+ // Safety: `AServiceManager_getService` returns either a null pointer or a
+ // valid pointer to an owned `AIBinder`. Either of these values is safe to
+ // pass to `SpIBinder::from_raw`.
+ unsafe { SpIBinder::from_raw(sys::AServiceManager_getService(name.as_ptr())) }
+}
+
+/// Retrieve an existing service, or start it if it is configured as a dynamic
+/// service and isn't yet started.
+pub fn wait_for_service(name: &str) -> Option<SpIBinder> {
+ let name = CString::new(name).ok()?;
+ // Safety: `AServiceManager_waitforService` returns either a null pointer or
+ // a valid pointer to an owned `AIBinder`. Either of these values is safe to
+ // pass to `SpIBinder::from_raw`.
+ unsafe { SpIBinder::from_raw(sys::AServiceManager_waitForService(name.as_ptr())) }
+}
+
+/// Retrieve an existing service for a particular interface, blocking for a few
+/// seconds if it doesn't yet exist.
+pub fn get_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> {
+ interface_cast(get_service(name))
+}
+
+/// Retrieve an existing service for a particular interface, or start it if it
+/// is configured as a dynamic service and isn't yet started.
+pub fn wait_for_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> {
+ interface_cast(wait_for_service(name))
+}
+
+/// Check if a service is declared (e.g. in a VINTF manifest)
+pub fn is_declared(interface: &str) -> Result<bool> {
+ let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?;
+
+ // Safety: `interface` is a valid null-terminated C-style string and is only
+ // borrowed for the lifetime of the call. The `interface` local outlives
+ // this call as it lives for the function scope.
+ unsafe { Ok(sys::AServiceManager_isDeclared(interface.as_ptr())) }
+}
+
+/// Retrieve all declared instances for a particular interface
+///
+/// For instance, if 'android.foo.IFoo/foo' is declared, and 'android.foo.IFoo'
+/// is passed here, then ["foo"] would be returned.
+pub fn get_declared_instances(interface: &str) -> Result<Vec<String>> {
+ unsafe extern "C" fn callback(instance: *const c_char, opaque: *mut c_void) {
+ // Safety: opaque was a mutable pointer created below from a Vec of
+ // CString, and outlives this callback. The null handling here is just
+ // to avoid the possibility of unwinding across C code if this crate is
+ // ever compiled with panic=unwind.
+ if let Some(instances) = unsafe { opaque.cast::<Vec<CString>>().as_mut() } {
+ // Safety: instance is a valid null-terminated C string with a
+ // lifetime at least as long as this function, and we immediately
+ // copy it into an owned CString.
+ unsafe {
+ instances.push(CStr::from_ptr(instance).to_owned());
+ }
+ } else {
+ eprintln!("Opaque pointer was null in get_declared_instances callback!");
+ }
+ }
+
+ let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?;
+ let mut instances: Vec<CString> = vec![];
+ // Safety: `interface` and `instances` are borrowed for the length of this
+ // call and both outlive the call. `interface` is guaranteed to be a valid
+ // null-terminated C-style string.
+ unsafe {
+ sys::AServiceManager_forEachDeclaredInstance(
+ interface.as_ptr(),
+ &mut instances as *mut _ as *mut c_void,
+ Some(callback),
+ );
+ }
+
+ instances
+ .into_iter()
+ .map(CString::into_string)
+ .collect::<std::result::Result<Vec<String>, _>>()
+ .map_err(|e| {
+ eprintln!("An interface instance name was not a valid UTF-8 string: {}", e);
+ StatusCode::BAD_VALUE
+ })
+}
diff --git a/libs/binder/rust/sys/lib.rs b/libs/binder/rust/sys/lib.rs
index c5c847b..5352473 100644
--- a/libs/binder/rust/sys/lib.rs
+++ b/libs/binder/rust/sys/lib.rs
@@ -25,7 +25,9 @@
}
// Trusty puts the full path to the auto-generated file in BINDGEN_INC_FILE
-// and builds it with warnings-as-errors, so we need to use #[allow(bad_style)]
+// and builds it with warnings-as-errors, so we need to use #[allow(bad_style)].
+// We need to use cfg(target_os) instead of cfg(trusty) here because of
+// the difference between the two build systems, which we cannot mock.
#[cfg(target_os = "trusty")]
#[allow(bad_style)]
mod bindings {
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index 2f0987f..35002eb 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -435,6 +435,8 @@
// Add the Trusty mock library as a fake dependency so it gets built
required: [
"libbinder_on_trusty_mock",
+ "libbinder_ndk_on_trusty_mock",
+ "libbinder_rs_on_trusty_mock",
"binderRpcTestService_on_trusty_mock",
"binderRpcTest_on_trusty_mock",
],
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/trusty/RpcServerTrusty.cpp b/libs/binder/trusty/RpcServerTrusty.cpp
index 1f857a0..17919c2 100644
--- a/libs/binder/trusty/RpcServerTrusty.cpp
+++ b/libs/binder/trusty/RpcServerTrusty.cpp
@@ -60,7 +60,7 @@
RpcServerTrusty::RpcServerTrusty(std::unique_ptr<RpcTransportCtx> ctx, std::string&& portName,
std::shared_ptr<const PortAcl>&& portAcl, size_t msgMaxSize)
- : mRpcServer(sp<RpcServer>::make(std::move(ctx))),
+ : mRpcServer(makeRpcServer(std::move(ctx))),
mPortName(std::move(portName)),
mPortAcl(std::move(portAcl)) {
mTipcPort.name = mPortName.c_str();
@@ -68,10 +68,6 @@
mTipcPort.msg_queue_len = 6; // Three each way
mTipcPort.priv = this;
- // TODO(b/266741352): follow-up to prevent needing this in the future
- // Trusty needs to be set to the latest stable version that is in prebuilts there.
- LOG_ALWAYS_FATAL_IF(!mRpcServer->setProtocolVersion(0));
-
if (mPortAcl) {
// Initialize the array of pointers to uuids.
// The pointers in mUuidPtrs should stay valid across moves of
@@ -101,8 +97,13 @@
int RpcServerTrusty::handleConnect(const tipc_port* port, handle_t chan, const uuid* peer,
void** ctx_p) {
auto* server = reinterpret_cast<RpcServerTrusty*>(const_cast<void*>(port->priv));
- server->mRpcServer->mShutdownTrigger = FdTrigger::make();
- server->mRpcServer->mConnectingThreads[rpc_this_thread::get_id()] = RpcMaybeThread();
+ return handleConnectInternal(server->mRpcServer.get(), chan, peer, ctx_p);
+}
+
+int RpcServerTrusty::handleConnectInternal(RpcServer* rpcServer, handle_t chan, const uuid* peer,
+ void** ctx_p) {
+ rpcServer->mShutdownTrigger = FdTrigger::make();
+ rpcServer->mConnectingThreads[rpc_this_thread::get_id()] = RpcMaybeThread();
int rc = NO_ERROR;
auto joinFn = [&](sp<RpcSession>&& session, RpcSession::PreJoinSetupResult&& result) {
@@ -138,13 +139,17 @@
std::array<uint8_t, RpcServer::kRpcAddressSize> addr;
constexpr size_t addrLen = sizeof(*peer);
memcpy(addr.data(), peer, addrLen);
- RpcServer::establishConnection(sp(server->mRpcServer), std::move(transportFd), addr, addrLen,
- joinFn);
+ RpcServer::establishConnection(sp<RpcServer>::fromExisting(rpcServer), std::move(transportFd),
+ addr, addrLen, joinFn);
return rc;
}
int RpcServerTrusty::handleMessage(const tipc_port* /*port*/, handle_t /*chan*/, void* ctx) {
+ return handleMessageInternal(ctx);
+}
+
+int RpcServerTrusty::handleMessageInternal(void* ctx) {
auto* channelContext = reinterpret_cast<ChannelContext*>(ctx);
LOG_ALWAYS_FATAL_IF(channelContext == nullptr,
"bad state: message received on uninitialized channel");
@@ -162,6 +167,10 @@
}
void RpcServerTrusty::handleDisconnect(const tipc_port* /*port*/, handle_t /*chan*/, void* ctx) {
+ return handleDisconnectInternal(ctx);
+}
+
+void RpcServerTrusty::handleDisconnectInternal(void* ctx) {
auto* channelContext = reinterpret_cast<ChannelContext*>(ctx);
if (channelContext == nullptr) {
// Connections marked "incoming" (outgoing from the server's side)
diff --git a/libs/binder/trusty/build-config-usertests b/libs/binder/trusty/build-config-usertests
index d0a1fbc..72e5ff9 100644
--- a/libs/binder/trusty/build-config-usertests
+++ b/libs/binder/trusty/build-config-usertests
@@ -16,4 +16,5 @@
[
porttest("com.android.trusty.binderRpcTest"),
+ porttest("com.android.trusty.rust.binder_rpc_test.test"),
]
diff --git a/libs/binder/trusty/include/binder/ARpcServerTrusty.h b/libs/binder/trusty/include/binder/ARpcServerTrusty.h
new file mode 100644
index 0000000..c82268b
--- /dev/null
+++ b/libs/binder/trusty/include/binder/ARpcServerTrusty.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <lib/tipc/tipc_srv.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct AIBinder;
+struct ARpcServerTrusty;
+
+struct ARpcServerTrusty* ARpcServerTrusty_newPerSession(struct AIBinder* (*)(const void*, size_t,
+ char*),
+ char*, void (*)(char*));
+void ARpcServerTrusty_delete(struct ARpcServerTrusty*);
+int ARpcServerTrusty_handleConnect(struct ARpcServerTrusty*, handle_t, const struct uuid*, void**);
+int ARpcServerTrusty_handleMessage(void*);
+void ARpcServerTrusty_handleDisconnect(void*);
+void ARpcServerTrusty_handleChannelCleanup(void*);
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/libs/binder/trusty/include/binder/RpcServerTrusty.h b/libs/binder/trusty/include/binder/RpcServerTrusty.h
index f35d6c2..fe44ea5 100644
--- a/libs/binder/trusty/include/binder/RpcServerTrusty.h
+++ b/libs/binder/trusty/include/binder/RpcServerTrusty.h
@@ -16,6 +16,7 @@
#pragma once
+#include <binder/ARpcServerTrusty.h>
#include <binder/IBinder.h>
#include <binder/RpcServer.h>
#include <binder/RpcSession.h>
@@ -88,6 +89,28 @@
explicit RpcServerTrusty(std::unique_ptr<RpcTransportCtx> ctx, std::string&& portName,
std::shared_ptr<const PortAcl>&& portAcl, size_t msgMaxSize);
+ // Internal helper that creates the RpcServer.
+ // This is used both from here and Rust.
+ static sp<RpcServer> makeRpcServer(std::unique_ptr<RpcTransportCtx> ctx) {
+ auto rpcServer = sp<RpcServer>::make(std::move(ctx));
+
+ // TODO(b/266741352): follow-up to prevent needing this in the future
+ // Trusty needs to be set to the latest stable version that is in prebuilts there.
+ LOG_ALWAYS_FATAL_IF(!rpcServer->setProtocolVersion(0));
+
+ return rpcServer;
+ }
+
+ friend struct ::ARpcServerTrusty;
+ friend ::ARpcServerTrusty* ::ARpcServerTrusty_newPerSession(::AIBinder* (*)(const void*, size_t,
+ char*),
+ char*, void (*)(char*));
+ friend void ::ARpcServerTrusty_delete(::ARpcServerTrusty*);
+ friend int ::ARpcServerTrusty_handleConnect(::ARpcServerTrusty*, handle_t, const uuid*, void**);
+ friend int ::ARpcServerTrusty_handleMessage(void*);
+ friend void ::ARpcServerTrusty_handleDisconnect(void*);
+ friend void ::ARpcServerTrusty_handleChannelCleanup(void*);
+
// The Rpc-specific context maintained for every open TIPC channel.
struct ChannelContext {
sp<RpcSession> session;
@@ -99,6 +122,11 @@
static void handleDisconnect(const tipc_port* port, handle_t chan, void* ctx);
static void handleChannelCleanup(void* ctx);
+ static int handleConnectInternal(RpcServer* rpcServer, handle_t chan, const uuid* peer,
+ void** ctx_p);
+ static int handleMessageInternal(void* ctx);
+ static void handleDisconnectInternal(void* ctx);
+
static constexpr tipc_srv_ops kTipcOps = {
.on_connect = &handleConnect,
.on_message = &handleMessage,
diff --git a/libs/binder/trusty/ndk/Android.bp b/libs/binder/trusty/ndk/Android.bp
new file mode 100644
index 0000000..af9874a
--- /dev/null
+++ b/libs/binder/trusty/ndk/Android.bp
@@ -0,0 +1,31 @@
+/*
+ * 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 {
+ // 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"],
+}
+
+cc_library_headers {
+ name: "libbinder_trusty_ndk_headers",
+ export_include_dirs: ["include"],
+ host_supported: true,
+ vendor_available: true,
+}
diff --git a/libs/binder/trusty/ndk/include/sys/cdefs.h b/libs/binder/trusty/ndk/include/sys/cdefs.h
index eabfe60..7528f2b 100644
--- a/libs/binder/trusty/ndk/include/sys/cdefs.h
+++ b/libs/binder/trusty/ndk/include/sys/cdefs.h
@@ -15,11 +15,16 @@
*/
#pragma once
+#if __has_include(<lk/compiler.h>)
#include <lk/compiler.h>
/* Alias the bionic macros to the ones from lk/compiler.h */
#define __BEGIN_DECLS __BEGIN_CDECLS
#define __END_DECLS __END_CDECLS
+#else // __has_include(<lk/compiler.h>)
+#include_next <sys/cdefs.h>
+#endif
+
#define __INTRODUCED_IN(x) /* nothing on Trusty */
#define __INTRODUCED_IN_LLNDK(x) /* nothing on Trusty */
diff --git a/libs/binder/trusty/rust/binder_ndk_sys/rules.mk b/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
index 672d9b7..2aaa061 100644
--- a/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
+++ b/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
@@ -29,6 +29,10 @@
$(LIBBINDER_DIR)/trusty/ndk \
trusty/user/base/lib/trusty-sys \
+MODULE_RUSTFLAGS += \
+ --cfg 'android_vendor' \
+ --cfg 'trusty' \
+
MODULE_BINDGEN_SRC_HEADER := $(LIBBINDER_DIR)/rust/sys/BinderBindings.hpp
# Add the flags from the flag file
diff --git a/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/ARpcServerTrusty.cpp b/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/ARpcServerTrusty.cpp
new file mode 100644
index 0000000..451383a
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/ARpcServerTrusty.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+#include <android/binder_libbinder.h>
+#include <binder/RpcServer.h>
+#include <binder/RpcServerTrusty.h>
+#include <binder/RpcSession.h>
+#include <binder/RpcTransportTipcTrusty.h>
+
+using android::RpcServer;
+using android::RpcServerTrusty;
+using android::RpcSession;
+using android::RpcTransportCtxFactoryTipcTrusty;
+using android::sp;
+using android::wp;
+
+struct ARpcServerTrusty {
+ sp<RpcServer> mRpcServer;
+
+ ARpcServerTrusty() = delete;
+ ARpcServerTrusty(sp<RpcServer> rpcServer) : mRpcServer(std::move(rpcServer)) {}
+};
+
+ARpcServerTrusty* ARpcServerTrusty_newPerSession(AIBinder* (*cb)(const void*, size_t, char*),
+ char* cbArg, void (*cbArgDeleter)(char*)) {
+ std::shared_ptr<char> cbArgSp(cbArg, cbArgDeleter);
+
+ auto rpcTransportCtxFactory = RpcTransportCtxFactoryTipcTrusty::make();
+ if (rpcTransportCtxFactory == nullptr) {
+ return nullptr;
+ }
+
+ auto ctx = rpcTransportCtxFactory->newServerCtx();
+ if (ctx == nullptr) {
+ return nullptr;
+ }
+
+ auto rpcServer = RpcServerTrusty::makeRpcServer(std::move(ctx));
+ if (rpcServer == nullptr) {
+ return nullptr;
+ }
+
+ rpcServer->setPerSessionRootObject(
+ [cb, cbArgSp](wp<RpcSession> /*session*/, const void* addrPtr, size_t len) {
+ auto* aib = (*cb)(addrPtr, len, cbArgSp.get());
+ auto b = AIBinder_toPlatformBinder(aib);
+
+ // We have a new sp<IBinder> backed by the same binder, so we can
+ // finally release the AIBinder* from the callback
+ AIBinder_decStrong(aib);
+
+ return b;
+ });
+
+ return new (std::nothrow) ARpcServerTrusty(std::move(rpcServer));
+}
+
+void ARpcServerTrusty_delete(ARpcServerTrusty* rstr) {
+ delete rstr;
+}
+
+int ARpcServerTrusty_handleConnect(ARpcServerTrusty* rstr, handle_t chan, const uuid* peer,
+ void** ctx_p) {
+ return RpcServerTrusty::handleConnectInternal(rstr->mRpcServer.get(), chan, peer, ctx_p);
+}
+
+int ARpcServerTrusty_handleMessage(void* ctx) {
+ return RpcServerTrusty::handleMessageInternal(ctx);
+}
+
+void ARpcServerTrusty_handleDisconnect(void* ctx) {
+ RpcServerTrusty::handleDisconnectInternal(ctx);
+}
+
+void ARpcServerTrusty_handleChannelCleanup(void* ctx) {
+ RpcServerTrusty::handleChannelCleanup(ctx);
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/rules.mk b/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/rules.mk
new file mode 100644
index 0000000..6def634
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/rules.mk
@@ -0,0 +1,29 @@
+# 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := \
+ $(LOCAL_DIR)/ARpcServerTrusty.cpp \
+
+MODULE_LIBRARY_DEPS += \
+ $(LIBBINDER_DIR)/trusty \
+ $(LIBBINDER_DIR)/trusty/ndk \
+ trusty/user/base/lib/libstdc++-trusty \
+
+include make/library.mk
diff --git a/libs/binder/trusty/rust/binder_rpc_server_bindgen/lib.rs b/libs/binder/trusty/rust/binder_rpc_server_bindgen/lib.rs
new file mode 100644
index 0000000..2e8b3ec
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_server_bindgen/lib.rs
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+//! Generated Rust bindings to binder_rpc_server
+
+#[allow(bad_style)]
+mod sys {
+ include!(env!("BINDGEN_INC_FILE"));
+}
+
+pub use sys::*;
diff --git a/libs/binder/trusty/rust/binder_rpc_server_bindgen/rules.mk b/libs/binder/trusty/rust/binder_rpc_server_bindgen/rules.mk
new file mode 100644
index 0000000..4ee333f
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_server_bindgen/rules.mk
@@ -0,0 +1,37 @@
+# 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LOCAL_DIR)/lib.rs
+
+MODULE_CRATE_NAME := binder_rpc_server_bindgen
+
+MODULE_LIBRARY_DEPS += \
+ $(LOCAL_DIR)/cpp \
+ trusty/user/base/lib/libstdc++-trusty \
+ trusty/user/base/lib/trusty-sys \
+
+MODULE_BINDGEN_SRC_HEADER := \
+ $(LIBBINDER_DIR)/trusty/include/binder/ARpcServerTrusty.h
+
+MODULE_BINDGEN_FLAGS += \
+ --allowlist-type="ARpcServerTrusty" \
+ --allowlist-function="ARpcServerTrusty_.*" \
+
+include make/library.mk
diff --git a/libs/binder/trusty/rust/binder_rpc_test/aidl/rules.mk b/libs/binder/trusty/rust/binder_rpc_test/aidl/rules.mk
new file mode 100644
index 0000000..1b0dca0
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/aidl/rules.mk
@@ -0,0 +1,34 @@
+# 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_TESTS_DIR := $(LOCAL_DIR)/../../../../tests
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_AIDL_LANGUAGE := rust
+
+MODULE_CRATE_NAME := binder_rpc_test_aidl
+
+MODULE_AIDLS := \
+ $(LIBBINDER_TESTS_DIR)/BinderRpcTestClientInfo.aidl \
+ $(LIBBINDER_TESTS_DIR)/BinderRpcTestServerConfig.aidl \
+ $(LIBBINDER_TESTS_DIR)/BinderRpcTestServerInfo.aidl \
+ $(LIBBINDER_TESTS_DIR)/IBinderRpcCallback.aidl \
+ $(LIBBINDER_TESTS_DIR)/IBinderRpcSession.aidl \
+ $(LIBBINDER_TESTS_DIR)/IBinderRpcTest.aidl \
+ $(LIBBINDER_TESTS_DIR)/ParcelableCertificateData.aidl \
+
+include make/aidl.mk
diff --git a/libs/binder/trusty/rust/binder_rpc_test/main.rs b/libs/binder/trusty/rust/binder_rpc_test/main.rs
new file mode 100644
index 0000000..3c1e784
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/main.rs
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#![cfg(test)]
+
+use binder::{IBinder, Strong};
+use binder_rpc_test_aidl::aidl::IBinderRpcTest::IBinderRpcTest;
+use rpcbinder::RpcSession;
+use trusty_std::ffi::{CString, FallibleCString};
+
+test::init!();
+
+const SERVICE_PORT: &str = "com.android.trusty.binderRpcTestService.V1";
+const RUST_SERVICE_PORT: &str = "com.android.trusty.rust.binderRpcTestService.V1";
+
+fn get_service(port: &str) -> Strong<dyn IBinderRpcTest> {
+ let port = CString::try_new(port).expect("Failed to allocate port name");
+ RpcSession::new().setup_trusty_client(port.as_c_str()).expect("Failed to create session")
+}
+
+#[test]
+fn ping() {
+ let srv = get_service(SERVICE_PORT);
+ assert_eq!(srv.as_binder().ping_binder(), Ok(()));
+}
+
+#[test]
+fn ping_rust() {
+ let srv = get_service(RUST_SERVICE_PORT);
+ assert_eq!(srv.as_binder().ping_binder(), Ok(()));
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_test/manifest.json b/libs/binder/trusty/rust/binder_rpc_test/manifest.json
new file mode 100644
index 0000000..c2ecaa4
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/manifest.json
@@ -0,0 +1,9 @@
+{
+ "uuid": "91eed949-8a9e-4569-9c83-5935fb624025",
+ "app_name": "rust_binder_rpc_test",
+ "min_heap": 16384,
+ "min_stack": 16384,
+ "mgmt_flags": {
+ "non_critical_app": true
+ }
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_test/rules.mk b/libs/binder/trusty/rust/binder_rpc_test/rules.mk
new file mode 100644
index 0000000..192a159
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/rules.mk
@@ -0,0 +1,35 @@
+# 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LOCAL_DIR)/main.rs
+
+MODULE_CRATE_NAME := binder_rpc_test
+
+MODULE_LIBRARY_DEPS += \
+ $(LIBBINDER_DIR)/trusty/rust \
+ $(LIBBINDER_DIR)/trusty/rust/rpcbinder \
+ $(LOCAL_DIR)/aidl \
+ trusty/user/base/lib/trusty-std \
+
+MODULE_RUST_TESTS := true
+
+MANIFEST := $(LOCAL_DIR)/manifest.json
+
+include make/library.mk
diff --git a/libs/binder/trusty/rust/binder_rpc_test/service/main.rs b/libs/binder/trusty/rust/binder_rpc_test/service/main.rs
new file mode 100644
index 0000000..b9a86bf
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/service/main.rs
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+
+use binder::{BinderFeatures, Interface, ParcelFileDescriptor, SpIBinder, Status, Strong};
+use binder_rpc_test_aidl::aidl::IBinderRpcCallback::IBinderRpcCallback;
+use binder_rpc_test_aidl::aidl::IBinderRpcSession::IBinderRpcSession;
+use binder_rpc_test_aidl::aidl::IBinderRpcTest::{BnBinderRpcTest, IBinderRpcTest};
+use rpcbinder::RpcServer;
+use std::rc::Rc;
+use tipc::{service_dispatcher, wrap_service, Manager, PortCfg};
+
+const RUST_SERVICE_PORT: &str = "com.android.trusty.rust.binderRpcTestService.V1";
+
+#[derive(Debug, Default)]
+struct TestService;
+
+impl Interface for TestService {}
+
+impl IBinderRpcTest for TestService {
+ fn sendString(&self, _: &str) -> Result<(), Status> {
+ todo!()
+ }
+ fn doubleString(&self, _: &str) -> Result<String, Status> {
+ todo!()
+ }
+ fn getClientPort(&self) -> Result<i32, Status> {
+ todo!()
+ }
+ fn countBinders(&self) -> Result<Vec<i32>, Status> {
+ todo!()
+ }
+ fn getNullBinder(&self) -> Result<SpIBinder, Status> {
+ todo!()
+ }
+ fn pingMe(&self, _: &SpIBinder) -> Result<i32, Status> {
+ todo!()
+ }
+ fn repeatBinder(&self, _: Option<&SpIBinder>) -> Result<Option<SpIBinder>, Status> {
+ todo!()
+ }
+ fn holdBinder(&self, _: Option<&SpIBinder>) -> Result<(), Status> {
+ todo!()
+ }
+ fn getHeldBinder(&self) -> Result<Option<SpIBinder>, Status> {
+ todo!()
+ }
+ fn nestMe(&self, _: &Strong<(dyn IBinderRpcTest + 'static)>, _: i32) -> Result<(), Status> {
+ todo!()
+ }
+ fn alwaysGiveMeTheSameBinder(&self) -> Result<SpIBinder, Status> {
+ todo!()
+ }
+ fn openSession(&self, _: &str) -> Result<Strong<(dyn IBinderRpcSession + 'static)>, Status> {
+ todo!()
+ }
+ fn getNumOpenSessions(&self) -> Result<i32, Status> {
+ todo!()
+ }
+ fn lock(&self) -> Result<(), Status> {
+ todo!()
+ }
+ fn unlockInMsAsync(&self, _: i32) -> Result<(), Status> {
+ todo!()
+ }
+ fn lockUnlock(&self) -> Result<(), Status> {
+ todo!()
+ }
+ fn sleepMs(&self, _: i32) -> Result<(), Status> {
+ todo!()
+ }
+ fn sleepMsAsync(&self, _: i32) -> Result<(), Status> {
+ todo!()
+ }
+ fn doCallback(
+ &self,
+ _: &Strong<(dyn IBinderRpcCallback + 'static)>,
+ _: bool,
+ _: bool,
+ _: &str,
+ ) -> Result<(), Status> {
+ todo!()
+ }
+ fn doCallbackAsync(
+ &self,
+ _: &Strong<(dyn IBinderRpcCallback + 'static)>,
+ _: bool,
+ _: bool,
+ _: &str,
+ ) -> Result<(), Status> {
+ todo!()
+ }
+ fn die(&self, _: bool) -> Result<(), Status> {
+ todo!()
+ }
+ fn scheduleShutdown(&self) -> Result<(), Status> {
+ todo!()
+ }
+ fn useKernelBinderCallingId(&self) -> Result<(), Status> {
+ todo!()
+ }
+ fn echoAsFile(&self, _: &str) -> Result<ParcelFileDescriptor, Status> {
+ todo!()
+ }
+ fn concatFiles(&self, _: &[ParcelFileDescriptor]) -> Result<ParcelFileDescriptor, Status> {
+ todo!()
+ }
+ fn blockingSendFdOneway(&self, _: &ParcelFileDescriptor) -> Result<(), Status> {
+ todo!()
+ }
+ fn blockingRecvFd(&self) -> Result<ParcelFileDescriptor, Status> {
+ todo!()
+ }
+ fn blockingSendIntOneway(&self, _: i32) -> Result<(), Status> {
+ todo!()
+ }
+ fn blockingRecvInt(&self) -> Result<i32, Status> {
+ todo!()
+ }
+}
+
+wrap_service!(TestRpcServer(RpcServer: UnbufferedService));
+
+service_dispatcher! {
+ enum TestDispatcher {
+ TestRpcServer,
+ }
+}
+
+fn main() {
+ let mut dispatcher = TestDispatcher::<1>::new().expect("Could not create test dispatcher");
+
+ let service = BnBinderRpcTest::new_binder(TestService::default(), BinderFeatures::default());
+ let rpc_server =
+ TestRpcServer::new(RpcServer::new_per_session(move |_uuid| Some(service.as_binder())));
+
+ let cfg = PortCfg::new(RUST_SERVICE_PORT)
+ .expect("Could not create port config")
+ .allow_ta_connect()
+ .allow_ns_connect();
+ dispatcher.add_service(Rc::new(rpc_server), cfg).expect("Could not add service to dispatcher");
+
+ Manager::<_, _, 1, 4>::new_with_dispatcher(dispatcher, [])
+ .expect("Could not create service manager")
+ .run_event_loop()
+ .expect("Test event loop failed");
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_test/service/manifest.json b/libs/binder/trusty/rust/binder_rpc_test/service/manifest.json
new file mode 100644
index 0000000..121ba11
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/service/manifest.json
@@ -0,0 +1,10 @@
+{
+ "uuid": "4741fc65-8b65-4893-ba55-b182c003c8b7",
+ "app_name": "rust_binder_rpc_test_service",
+ "min_heap": 16384,
+ "min_stack": 16384,
+ "mgmt_flags": {
+ "non_critical_app": true,
+ "restart_on_exit": true
+ }
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_test/service/rules.mk b/libs/binder/trusty/rust/binder_rpc_test/service/rules.mk
new file mode 100644
index 0000000..1ddc382
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/service/rules.mk
@@ -0,0 +1,33 @@
+# 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LOCAL_DIR)/main.rs
+
+MODULE_CRATE_NAME := binder_rpc_test_service
+
+MODULE_LIBRARY_DEPS += \
+ $(LIBBINDER_DIR)/trusty/rust \
+ $(LIBBINDER_DIR)/trusty/rust/rpcbinder \
+ $(LOCAL_DIR)/../aidl \
+ trusty/user/base/lib/tipc/rust \
+
+MANIFEST := $(LOCAL_DIR)/manifest.json
+
+include make/trusted_app.mk
diff --git a/libs/binder/trusty/rust/rpcbinder/rules.mk b/libs/binder/trusty/rust/rpcbinder/rules.mk
index 76f3b94..97f5c03 100644
--- a/libs/binder/trusty/rust/rpcbinder/rules.mk
+++ b/libs/binder/trusty/rust/rpcbinder/rules.mk
@@ -28,6 +28,8 @@
$(LIBBINDER_DIR)/trusty/rust \
$(LIBBINDER_DIR)/trusty/rust/binder_ndk_sys \
$(LIBBINDER_DIR)/trusty/rust/binder_rpc_unstable_bindgen \
+ $(LIBBINDER_DIR)/trusty/rust/binder_rpc_server_bindgen \
+ external/rust/crates/cfg-if \
external/rust/crates/foreign-types \
trusty/user/base/lib/tipc/rust \
trusty/user/base/lib/trusty-sys \
diff --git a/libs/binder/trusty/rust/rules.mk b/libs/binder/trusty/rust/rules.mk
index d343f14..c5e671a 100644
--- a/libs/binder/trusty/rust/rules.mk
+++ b/libs/binder/trusty/rust/rules.mk
@@ -32,6 +32,7 @@
MODULE_RUSTFLAGS += \
--cfg 'android_vendor' \
+ --cfg 'trusty' \
# Trusty does not have `ProcessState`, so there are a few
# doc links in `IBinder` that are still broken.
diff --git a/libs/binder/trusty/usertests-inc.mk b/libs/binder/trusty/usertests-inc.mk
index 1300121..833d209 100644
--- a/libs/binder/trusty/usertests-inc.mk
+++ b/libs/binder/trusty/usertests-inc.mk
@@ -16,4 +16,8 @@
TRUSTY_USER_TESTS += \
frameworks/native/libs/binder/trusty/binderRpcTest \
frameworks/native/libs/binder/trusty/binderRpcTest/service \
+ frameworks/native/libs/binder/trusty/rust/binder_rpc_test/service \
+
+TRUSTY_RUST_USER_TESTS += \
+ frameworks/native/libs/binder/trusty/rust/binder_rpc_test \
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/bufferqueueconverter/Android.bp b/libs/bufferqueueconverter/Android.bp
index 3c8c41f..196161b 100644
--- a/libs/bufferqueueconverter/Android.bp
+++ b/libs/bufferqueueconverter/Android.bp
@@ -17,9 +17,6 @@
cc_library {
name: "libbufferqueueconverter",
vendor_available: true,
- vndk: {
- enabled: true,
- },
double_loadable: true,
srcs: [
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/gralloc/types/Android.bp b/libs/gralloc/types/Android.bp
index f300da5..8dabc2c 100644
--- a/libs/gralloc/types/Android.bp
+++ b/libs/gralloc/types/Android.bp
@@ -38,10 +38,7 @@
},
vendor_available: true,
- vndk: {
- enabled: true,
- support_system_process: true,
- },
+ double_loadable: true,
apex_available: [
"//apex_available:platform",
"com.android.media.swcodec",
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index b70e80e..6c45746 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 {
@@ -282,10 +305,6 @@
cc_library_shared {
name: "libgui",
vendor_available: true,
- vndk: {
- enabled: true,
- private: true,
- },
double_loadable: true,
defaults: [
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/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/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/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..fead018
--- /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(());
+
+ 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 93bf4fa..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);
}
diff --git a/libs/input/InputConsumer.cpp b/libs/input/InputConsumer.cpp
index 41ecfe3..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.
@@ -769,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/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index 77292d4..5b61d39 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -18,6 +18,7 @@
#include <input/MotionPredictor.h>
+#include <algorithm>
#include <array>
#include <cinttypes>
#include <cmath>
@@ -62,6 +63,11 @@
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 ---
@@ -255,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) {
@@ -269,13 +286,12 @@
break;
}
if (input_flags::enable_prediction_pruning_via_jerk_thresholding()) {
- // TODO(b/266747654): Stop predictions if confidence is < some threshold
- // Arbitrarily high pruning index, will correct once jerk thresholding is implemented.
- const size_t upperBoundPredictionIndex = std::numeric_limits<size_t>::max();
- if (i > upperBoundPredictionIndex) {
+ if (i >= static_cast<size_t>(maxNumPredictions)) {
break;
}
}
+ // TODO(b/266747654): Stop predictions if confidence is < some
+ // threshold. Currently predictions are pruned via jerk thresholding.
const TfLiteMotionPredictorSample::Point predictedPoint =
convertPrediction(axisFrom, axisTo, predictedR[i], predictedPhi[i]);
diff --git a/libs/input/TfLiteMotionPredictor.cpp b/libs/input/TfLiteMotionPredictor.cpp
index 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/input_flags.aconfig b/libs/input/input_flags.aconfig
index e041c51..5c4b889 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -135,5 +135,25 @@
description: "Enable prediction pruning based on jerk thresholds."
bug: "266747654"
is_fixed_read_only: true
+}
+flag {
+ name: "device_associations"
+ namespace: "input"
+ description: "Binds InputDevice name and InputDevice description against display unique id."
+ bug: "324075859"
+}
+
+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"
+}
+
+flag {
+ name: "hide_pointer_indicators_for_secure_windows"
+ namespace: "input"
+ description: "Hide touch and pointer indicators if a secure window is present on display"
+ bug: "325252005"
}
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 f74874c..b8f1caa 100644
--- a/libs/input/tests/MotionPredictor_test.cpp
+++ b/libs/input/tests/MotionPredictor_test.cpp
@@ -14,9 +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>
@@ -197,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) {
@@ -250,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/skia/debug/SkiaCapture.cpp b/libs/renderengine/skia/debug/SkiaCapture.cpp
index 48dc77e..e778884 100644
--- a/libs/renderengine/skia/debug/SkiaCapture.cpp
+++ b/libs/renderengine/skia/debug/SkiaCapture.cpp
@@ -30,7 +30,7 @@
#include "SkCanvas.h"
#include "SkRect.h"
#include "SkTypeface.h"
-#include "src/utils/SkMultiPictureDocument.h"
+#include "include/docs/SkMultiPictureDocument.h"
#include <sys/stat.h>
namespace android {
@@ -196,7 +196,7 @@
// procs doesn't need to outlive this Make call
// The last argument is a callback for the endPage behavior.
// See SkSharingProc.h for more explanation of this callback.
- mMultiPic = SkMakeMultiPictureDocument(
+ mMultiPic = SkMultiPictureDocument::Make(
mOpenMultiPicStream.get(), &procs,
[sharingCtx = mSerialContext.get()](const SkPicture* pic) {
SkSharingSerialContext::collectNonTextureImagesFromPicture(pic, sharingCtx);
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 4bd0852..9f614bd 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -1265,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() {
@@ -1687,11 +1692,6 @@
}
TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_outputDataspace) {
- // TODO: b/331445583 - Fix in Graphite and re-enable.
- if (GetParam()->skiaBackend() == renderengine::RenderEngine::SkiaBackend::GRAPHITE) {
- GTEST_SKIP();
- }
-
const auto& renderEngineFactory = GetParam();
// skip for non color management
if (!renderEngineFactory->apiSupported()) {
@@ -1842,11 +1842,6 @@
}
TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_opaqueBufferSource) {
- // TODO: b/331447131 - Fix in Graphite and re-enable.
- if (GetParam()->skiaBackend() == renderengine::RenderEngine::SkiaBackend::GRAPHITE) {
- GTEST_SKIP();
- }
-
const auto& renderEngineFactory = GetParam();
// skip for non color management
if (!renderEngineFactory->apiSupported()) {
@@ -1997,11 +1992,6 @@
}
TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_bufferSource) {
- // TODO: b/331446495 - Fix in Graphite and re-enable.
- if (GetParam()->skiaBackend() == renderengine::RenderEngine::SkiaBackend::GRAPHITE) {
- GTEST_SKIP();
- }
-
const auto& renderEngineFactory = GetParam();
// skip for non color management
if (!renderEngineFactory->apiSupported()) {
@@ -2061,11 +2051,6 @@
}
TEST_P(RenderEngineTest, drawLayers_fillBuffer_premultipliesAlpha) {
- // TODO: b/331446496 - Fix in Graphite and re-enable.
- if (GetParam()->skiaBackend() == renderengine::RenderEngine::SkiaBackend::GRAPHITE) {
- GTEST_SKIP();
- }
-
if (!GetParam()->apiSupported()) {
GTEST_SKIP();
}
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index 9cb298a..12230f9 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -105,9 +105,6 @@
cc_library_shared {
name: "libui",
vendor_available: true,
- vndk: {
- enabled: true,
- },
double_loadable: true,
cflags: [
@@ -280,13 +277,3 @@
"tests",
"tools",
]
-
-filegroup {
- name: "libui_host_common",
- srcs: [
- "Rect.cpp",
- "Region.cpp",
- "PixelFormat.cpp",
- "Transform.cpp",
- ],
-}
diff --git a/libs/ui/DebugUtils.cpp b/libs/ui/DebugUtils.cpp
index 8675f14..bee58e5 100644
--- a/libs/ui/DebugUtils.cpp
+++ b/libs/ui/DebugUtils.cpp
@@ -22,14 +22,12 @@
#include <android-base/stringprintf.h>
#include <string>
-using android::base::StringAppendF;
using android::base::StringPrintf;
using android::ui::ColorMode;
using android::ui::RenderIntent;
-std::string decodeStandard(android_dataspace dataspace) {
- const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
- switch (dataspaceSelect) {
+std::string decodeStandardOnly(uint32_t dataspaceStandard) {
+ switch (dataspaceStandard) {
case HAL_DATASPACE_STANDARD_BT709:
return std::string("BT709");
@@ -62,63 +60,44 @@
case HAL_DATASPACE_STANDARD_ADOBE_RGB:
return std::string("AdobeRGB");
-
- case 0:
- switch (dataspace & 0xffff) {
- case HAL_DATASPACE_JFIF:
- return std::string("(deprecated) JFIF (BT601_625)");
-
- case HAL_DATASPACE_BT601_625:
- return std::string("(deprecated) BT601_625");
-
- case HAL_DATASPACE_BT601_525:
- return std::string("(deprecated) BT601_525");
-
- case HAL_DATASPACE_SRGB_LINEAR:
- case HAL_DATASPACE_SRGB:
- return std::string("(deprecated) sRGB");
-
- case HAL_DATASPACE_BT709:
- return std::string("(deprecated) BT709");
-
- case HAL_DATASPACE_ARBITRARY:
- return std::string("ARBITRARY");
-
- case HAL_DATASPACE_UNKNOWN:
- // Fallthrough
- default:
- return StringPrintf("Unknown deprecated dataspace code %d", dataspace);
- }
}
- return StringPrintf("Unknown dataspace code %d", dataspaceSelect);
+ return StringPrintf("Unknown dataspace code %d", dataspaceStandard);
}
-std::string decodeTransfer(android_dataspace dataspace) {
- const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
- if (dataspaceSelect == 0) {
+std::string decodeStandard(android_dataspace dataspace) {
+ const uint32_t dataspaceStandard = (dataspace & HAL_DATASPACE_STANDARD_MASK);
+ if (dataspaceStandard == 0) {
switch (dataspace & 0xffff) {
case HAL_DATASPACE_JFIF:
+ return std::string("(deprecated) JFIF (BT601_625)");
+
case HAL_DATASPACE_BT601_625:
+ return std::string("(deprecated) BT601_625");
+
case HAL_DATASPACE_BT601_525:
- case HAL_DATASPACE_BT709:
- return std::string("SMPTE_170M");
+ return std::string("(deprecated) BT601_525");
case HAL_DATASPACE_SRGB_LINEAR:
- case HAL_DATASPACE_ARBITRARY:
- return std::string("Linear");
-
case HAL_DATASPACE_SRGB:
- return std::string("sRGB");
+ return std::string("(deprecated) sRGB");
+
+ case HAL_DATASPACE_BT709:
+ return std::string("(deprecated) BT709");
+
+ case HAL_DATASPACE_ARBITRARY:
+ return std::string("ARBITRARY");
case HAL_DATASPACE_UNKNOWN:
// Fallthrough
default:
- return std::string("");
+ return StringPrintf("Unknown deprecated dataspace code %d", dataspace);
}
}
+ return decodeStandardOnly(dataspaceStandard);
+}
- const uint32_t dataspaceTransfer = (dataspace & HAL_DATASPACE_TRANSFER_MASK);
+std::string decodeTransferOnly(uint32_t dataspaceTransfer) {
switch (dataspaceTransfer) {
case HAL_DATASPACE_TRANSFER_UNSPECIFIED:
return std::string("Unspecified");
@@ -151,6 +130,52 @@
return StringPrintf("Unknown dataspace transfer %d", dataspaceTransfer);
}
+std::string decodeTransfer(android_dataspace dataspace) {
+ const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
+ if (dataspaceSelect == 0) {
+ switch (dataspace & 0xffff) {
+ case HAL_DATASPACE_JFIF:
+ case HAL_DATASPACE_BT601_625:
+ case HAL_DATASPACE_BT601_525:
+ case HAL_DATASPACE_BT709:
+ return std::string("SMPTE_170M");
+
+ case HAL_DATASPACE_SRGB_LINEAR:
+ case HAL_DATASPACE_ARBITRARY:
+ return std::string("Linear");
+
+ case HAL_DATASPACE_SRGB:
+ return std::string("sRGB");
+
+ case HAL_DATASPACE_UNKNOWN:
+ // Fallthrough
+ default:
+ return std::string("");
+ }
+ }
+
+ const uint32_t dataspaceTransfer = (dataspace & HAL_DATASPACE_TRANSFER_MASK);
+ return decodeTransferOnly(dataspaceTransfer);
+}
+
+std::string decodeRangeOnly(uint32_t dataspaceRange) {
+ switch (dataspaceRange) {
+ case HAL_DATASPACE_RANGE_UNSPECIFIED:
+ return std::string("Range Unspecified");
+
+ case HAL_DATASPACE_RANGE_FULL:
+ return std::string("Full range");
+
+ case HAL_DATASPACE_RANGE_LIMITED:
+ return std::string("Limited range");
+
+ case HAL_DATASPACE_RANGE_EXTENDED:
+ return std::string("Extended range");
+ }
+
+ return StringPrintf("Unknown dataspace range %d", dataspaceRange);
+}
+
std::string decodeRange(android_dataspace dataspace) {
const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
if (dataspaceSelect == 0) {
@@ -174,21 +199,7 @@
}
const uint32_t dataspaceRange = (dataspace & HAL_DATASPACE_RANGE_MASK);
- switch (dataspaceRange) {
- case HAL_DATASPACE_RANGE_UNSPECIFIED:
- return std::string("Range Unspecified");
-
- case HAL_DATASPACE_RANGE_FULL:
- return std::string("Full range");
-
- case HAL_DATASPACE_RANGE_LIMITED:
- return std::string("Limited range");
-
- case HAL_DATASPACE_RANGE_EXTENDED:
- return std::string("Extended range");
- }
-
- return StringPrintf("Unknown dataspace range %d", dataspaceRange);
+ return decodeRangeOnly(dataspaceRange);
}
std::string dataspaceDetails(android_dataspace dataspace) {
diff --git a/libs/ui/include/ui/DebugUtils.h b/libs/ui/include/ui/DebugUtils.h
index 18cd487..7c4ac42 100644
--- a/libs/ui/include/ui/DebugUtils.h
+++ b/libs/ui/include/ui/DebugUtils.h
@@ -27,8 +27,11 @@
}
std::string decodeStandard(android_dataspace dataspace);
+std::string decodeStandardOnly(uint32_t dataspaceStandard);
std::string decodeTransfer(android_dataspace dataspace);
+std::string decodeTransferOnly(uint32_t dataspaceTransfer);
std::string decodeRange(android_dataspace dataspace);
+std::string decodeRangeOnly(uint32_t dataspaceRange);
std::string dataspaceDetails(android_dataspace dataspace);
std::string decodeColorMode(android::ui::ColorMode colormode);
std::string decodeColorTransform(android_color_transform colorTransform);
diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp
index 6ccd9e7..417c1f3 100644
--- a/services/inputflinger/InputCommonConverter.cpp
+++ b/services/inputflinger/InputCommonConverter.cpp
@@ -20,6 +20,9 @@
namespace android {
+const static ui::Transform kIdentityTransform;
+const static std::array<uint8_t, 32> kInvalidHmac{};
+
static common::Source getSource(uint32_t source) {
static_assert(static_cast<common::Source>(AINPUT_SOURCE_UNKNOWN) == common::Source::UNKNOWN,
"SOURCE_UNKNOWN mismatch");
@@ -337,4 +340,31 @@
return event;
}
+MotionEvent toMotionEvent(const NotifyMotionArgs& args, const ui::Transform* transform,
+ const ui::Transform* rawTransform, const std::array<uint8_t, 32>* hmac) {
+ if (transform == nullptr) transform = &kIdentityTransform;
+ if (rawTransform == nullptr) rawTransform = &kIdentityTransform;
+ if (hmac == nullptr) hmac = &kInvalidHmac;
+
+ MotionEvent event;
+ event.initialize(args.id, args.deviceId, args.source, args.displayId, *hmac, args.action,
+ args.actionButton, args.flags, args.edgeFlags, args.metaState,
+ args.buttonState, args.classification, *transform, args.xPrecision,
+ args.yPrecision, args.xCursorPosition, args.yCursorPosition, *rawTransform,
+ args.downTime, args.eventTime, args.getPointerCount(),
+ args.pointerProperties.data(), args.pointerCoords.data());
+ return event;
+}
+
+KeyEvent toKeyEvent(const NotifyKeyArgs& args, int32_t repeatCount,
+ const std::array<uint8_t, 32>* hmac) {
+ if (hmac == nullptr) hmac = &kInvalidHmac;
+
+ KeyEvent event;
+ event.initialize(args.id, args.deviceId, args.source, args.displayId, *hmac, args.action,
+ args.flags, args.keyCode, args.scanCode, args.metaState, repeatCount,
+ args.downTime, args.eventTime);
+ return event;
+}
+
} // namespace android
diff --git a/services/inputflinger/InputCommonConverter.h b/services/inputflinger/InputCommonConverter.h
index 4d3b768..0d4cbb0 100644
--- a/services/inputflinger/InputCommonConverter.h
+++ b/services/inputflinger/InputCommonConverter.h
@@ -16,16 +16,25 @@
#pragma once
+#include "InputListener.h"
+
#include <aidl/android/hardware/input/common/Axis.h>
#include <aidl/android/hardware/input/common/MotionEvent.h>
-#include "InputListener.h"
+#include <input/Input.h>
namespace android {
-/**
- * Convert from framework's NotifyMotionArgs to hidl's common::MotionEvent
- */
+/** Convert from framework's NotifyMotionArgs to hidl's common::MotionEvent. */
::aidl::android::hardware::input::common::MotionEvent notifyMotionArgsToHalMotionEvent(
const NotifyMotionArgs& args);
+/** Convert from NotifyMotionArgs to MotionEvent. */
+MotionEvent toMotionEvent(const NotifyMotionArgs&, const ui::Transform* transform = nullptr,
+ const ui::Transform* rawTransform = nullptr,
+ const std::array<uint8_t, 32>* hmac = nullptr);
+
+/** Convert from NotifyKeyArgs to KeyEvent. */
+KeyEvent toKeyEvent(const NotifyKeyArgs&, int32_t repeatCount = 0,
+ const std::array<uint8_t, 32>* hmac = nullptr);
+
} // namespace android
diff --git a/services/inputflinger/InputFilterCallbacks.cpp b/services/inputflinger/InputFilterCallbacks.cpp
index 6c31442..a9bdbec 100644
--- a/services/inputflinger/InputFilterCallbacks.cpp
+++ b/services/inputflinger/InputFilterCallbacks.cpp
@@ -19,9 +19,10 @@
#include "InputFilterCallbacks.h"
#include <aidl/com/android/server/inputflinger/BnInputThread.h>
#include <android/binder_auto_utils.h>
+#include <utils/Looper.h>
#include <utils/StrongPointer.h>
-#include <utils/Thread.h>
#include <functional>
+#include "InputThread.h"
namespace android {
@@ -38,36 +39,37 @@
using namespace aidl::com::android::server::inputflinger;
-class InputFilterThreadImpl : public Thread {
-public:
- explicit InputFilterThreadImpl(std::function<void()> loop)
- : Thread(/*canCallJava=*/true), mThreadLoop(loop) {}
-
- ~InputFilterThreadImpl() {}
-
-private:
- std::function<void()> mThreadLoop;
-
- bool threadLoop() override {
- mThreadLoop();
- return true;
- }
-};
-
class InputFilterThread : public BnInputThread {
public:
InputFilterThread(std::shared_ptr<IInputThreadCallback> callback) : mCallback(callback) {
- mThread = sp<InputFilterThreadImpl>::make([this]() { loopOnce(); });
- mThread->run("InputFilterThread", ANDROID_PRIORITY_URGENT_DISPLAY);
+ mLooper = sp<Looper>::make(/*allowNonCallbacks=*/false);
+ mThread = std::make_unique<InputThread>(
+ "InputFilter", [this]() { loopOnce(); }, [this]() { mLooper->wake(); });
}
ndk::ScopedAStatus finish() override {
- mThread->requestExit();
+ if (mThread && mThread->isCallingThread()) {
+ ALOGE("InputFilterThread cannot be stopped on itself!");
+ return ndk::ScopedAStatus::fromStatus(INVALID_OPERATION);
+ }
+ mThread.reset();
+ return ndk::ScopedAStatus::ok();
+ }
+
+ ndk::ScopedAStatus sleepUntil(nsecs_t when) override {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ mLooper->pollOnce(toMillisecondTimeoutDelay(now, when));
+ return ndk::ScopedAStatus::ok();
+ }
+
+ ndk::ScopedAStatus wake() override {
+ mLooper->wake();
return ndk::ScopedAStatus::ok();
}
private:
- sp<Thread> mThread;
+ sp<Looper> mLooper;
+ std::unique_ptr<InputThread> mThread;
std::shared_ptr<IInputThreadCallback> mCallback;
void loopOnce() { LOG_ALWAYS_FATAL_IF(!mCallback->loopOnce().isOk()); }
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/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 293ad66..fe4d0cd 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -1,10 +1,10 @@
{
"presubmit": [
{
- "name": "CtsWindowManagerDeviceWindow",
+ "name": "CtsWindowManagerDeviceInput",
"options": [
{
- "include-filter": "android.server.wm.window.WindowInputTests"
+ "include-filter": "android.server.wm.input.WindowInputTests"
}
]
},
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
index 2f6b8fc..cc0592e 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
@@ -21,6 +21,13 @@
* infrastructure.
*
* <p>
+ * Earlier, we used rust thread park()/unpark() to put the thread to sleep and wake up from sleep.
+ * But that caused some breakages after migrating the rust system crates to 2021 edition. Since,
+ * the threads are created in C++, it was more reliable to rely on C++ side of the implementation
+ * to implement the sleep and wake functions.
+ * </p>
+ *
+ * <p>
* NOTE: Tried using rust provided threading infrastructure but that uses std::thread which doesn't
* have JNI support and can't call into Java policy that we use currently. libutils provided
* Thread.h also recommends against using std::thread and using the provided infrastructure that
@@ -33,6 +40,16 @@
/** Finish input thread (if not running, this call does nothing) */
void finish();
+ /** Wakes up the thread (if sleeping) */
+ void wake();
+
+ /**
+ * Puts the thread to sleep until a future time provided.
+ *
+ * NOTE: The thread can be awaken before the provided time using {@link wake()} function.
+ */
+ void sleepUntil(long whenNanos);
+
/** Callbacks from C++ to call into inputflinger rust components */
interface IInputThreadCallback {
/**
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 4d70a33..d1930f1 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -2455,7 +2455,7 @@
if (newTouchedWindowHandle == nullptr) {
ALOGD("No new touched window at (%.1f, %.1f) in display %" PRId32, x, y, displayId);
// Try to assign the pointer to the first foreground window we find, if there is one.
- newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle();
+ newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle(entry.deviceId);
}
// Verify targeted injection.
@@ -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
@@ -2615,7 +2623,7 @@
const auto [x, y] = resolveTouchedPosition(entry);
const bool isStylus = isPointerFromStylus(entry, /*pointerIndex=*/0);
sp<WindowInfoHandle> oldTouchedWindowHandle =
- tempTouchState.getFirstForegroundWindowHandle();
+ tempTouchState.getFirstForegroundWindowHandle(entry.deviceId);
LOG_ALWAYS_FATAL_IF(oldTouchedWindowHandle == nullptr);
sp<WindowInfoHandle> newTouchedWindowHandle =
findTouchedWindowAtLocked(displayId, x, y, isStylus);
@@ -2733,7 +2741,7 @@
// has a different UID, then we will not reveal coordinate information to this window.
if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
sp<WindowInfoHandle> foregroundWindowHandle =
- tempTouchState.getFirstForegroundWindowHandle();
+ tempTouchState.getFirstForegroundWindowHandle(entry.deviceId);
if (foregroundWindowHandle) {
const auto foregroundWindowUid = foregroundWindowHandle->getInfo()->ownerUid;
for (InputTarget& target : targets) {
@@ -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(),
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 1fec9b7..02bc368 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;
+ }
}
}
@@ -336,33 +338,34 @@
// it's unlikely that those two streams would be consistent with each other. Therefore,
// cancel the previous gesture if the display id changes.
if (motionEntry.displayId != lastMemento.displayId) {
- LOG(INFO) << "Canceling stream: last displayId was "
- << inputEventSourceToString(lastMemento.displayId) << " and new event is "
- << motionEntry;
+ LOG(INFO) << "Canceling stream: last displayId was " << lastMemento.displayId
+ << " and new event is " << motionEntry;
return true;
}
return false;
}
- if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties)) {
- // A stylus is already active.
- if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) &&
- actionMasked == AMOTION_EVENT_ACTION_DOWN) {
- // If this new event is from a different device, then cancel the old
- // stylus and allow the new stylus to take over, but only if it's going down.
- // Otherwise, they will start to race each other.
- return true;
+ if (!input_flags::enable_multi_device_same_window_stream()) {
+ if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties)) {
+ // A stylus is already active.
+ if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) &&
+ actionMasked == AMOTION_EVENT_ACTION_DOWN) {
+ // If this new event is from a different device, then cancel the old
+ // stylus and allow the new stylus to take over, but only if it's going down.
+ // Otherwise, they will start to race each other.
+ return true;
+ }
+
+ // Keep the current stylus gesture.
+ return false;
}
- // Keep the current stylus gesture.
- return false;
- }
-
- // Cancel the current gesture if this is a start of a new gesture from a new device.
- if (actionMasked == AMOTION_EVENT_ACTION_DOWN ||
- actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) {
- return true;
+ // Cancel the current gesture if this is a start of a new gesture from a new device.
+ if (actionMasked == AMOTION_EVENT_ACTION_DOWN ||
+ actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) {
+ return true;
+ }
}
// By default, don't cancel any events.
return false;
diff --git a/services/inputflinger/dispatcher/InputTarget.cpp b/services/inputflinger/dispatcher/InputTarget.cpp
index 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..15eef20 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,
@@ -178,9 +180,11 @@
clearWindowsWithoutPointers();
}
-sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle() const {
- for (size_t i = 0; i < windows.size(); i++) {
- const TouchedWindow& window = windows[i];
+sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle(DeviceId deviceId) const {
+ for (const auto& window : windows) {
+ if (!window.hasTouchingPointers(deviceId)) {
+ continue;
+ }
if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) {
return window.windowHandle;
}
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 3d534bc..e63edc3 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);
@@ -64,7 +64,7 @@
// set to false.
void cancelPointersForNonPilferingWindows();
- sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle() const;
+ sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle(DeviceId deviceId) const;
bool isSlippery() const;
sp<android::gui::WindowInfoHandle> getWallpaperWindow() const;
const TouchedWindow& getTouchedWindow(
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/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp
index 1d4d11c..4931a5f 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp
@@ -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
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
index 9c39743..91ebe9b 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
@@ -149,6 +149,8 @@
// --- PerfettoBackend ---
+bool PerfettoBackend::sUseInProcessBackendForTest{false};
+
std::once_flag PerfettoBackend::sDataSourceRegistrationFlag{};
std::atomic<int32_t> PerfettoBackend::sNextInstanceId{1};
@@ -159,7 +161,8 @@
// we never unregister the InputEventDataSource.
std::call_once(sDataSourceRegistrationFlag, []() {
perfetto::TracingInitArgs args;
- args.backends = perfetto::kSystemBackend;
+ args.backends = sUseInProcessBackendForTest ? perfetto::kInProcessBackend
+ : perfetto::kSystemBackend;
perfetto::Tracing::Initialize(args);
// Register our custom data source for input event tracing.
@@ -175,6 +178,9 @@
const TracedEventMetadata& metadata) {
InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
auto dataSource = ctx.GetDataSourceLocked();
+ if (!dataSource.valid()) {
+ return;
+ }
dataSource->initializeUidMap(mGetPackageUid);
if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) {
return;
@@ -196,6 +202,9 @@
const TracedEventMetadata& metadata) {
InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
auto dataSource = ctx.GetDataSourceLocked();
+ if (!dataSource.valid()) {
+ return;
+ }
dataSource->initializeUidMap(mGetPackageUid);
if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) {
return;
@@ -217,6 +226,9 @@
const TracedEventMetadata& metadata) {
InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
auto dataSource = ctx.GetDataSourceLocked();
+ if (!dataSource.valid()) {
+ return;
+ }
dataSource->initializeUidMap(mGetPackageUid);
if (!dataSource->getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH)) {
return;
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
index e945066..fdfe495 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
@@ -51,6 +51,8 @@
public:
using GetPackageUid = std::function<gui::Uid(std::string)>;
+ static bool sUseInProcessBackendForTest;
+
explicit PerfettoBackend(GetPackageUid);
~PerfettoBackend() override = default;
@@ -61,6 +63,7 @@
private:
// Implementation of the perfetto data source.
// Each instance of the InputEventDataSource represents a different tracing session.
+ // Its lifecycle is controlled by perfetto.
class InputEventDataSource : public perfetto::DataSource<InputEventDataSource> {
public:
explicit InputEventDataSource();
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
index 77d09cb..3c3c15a 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
@@ -57,6 +57,7 @@
const TracedEventMetadata& metadata) {
std::scoped_lock lock(mLock);
mQueue.emplace_back(event, metadata);
+ setIdleStatus(false);
mThreadWakeCondition.notify_all();
}
@@ -65,6 +66,7 @@
const TracedEventMetadata& metadata) {
std::scoped_lock lock(mLock);
mQueue.emplace_back(event, metadata);
+ setIdleStatus(false);
mThreadWakeCondition.notify_all();
}
@@ -73,6 +75,7 @@
const TracedEventMetadata& metadata) {
std::scoped_lock lock(mLock);
mQueue.emplace_back(dispatchArgs, metadata);
+ setIdleStatus(false);
mThreadWakeCondition.notify_all();
}
@@ -84,10 +87,15 @@
std::unique_lock lock(mLock);
base::ScopedLockAssertion assumeLocked(mLock);
+ if (mQueue.empty()) {
+ setIdleStatus(true);
+ }
+
// Wait until we need to process more events or exit.
mThreadWakeCondition.wait(lock,
[&]() REQUIRES(mLock) { return mThreadExit || !mQueue.empty(); });
if (mThreadExit) {
+ setIdleStatus(true);
return;
}
@@ -109,6 +117,36 @@
entries.clear();
}
+template <typename Backend>
+std::function<void()> ThreadedBackend<Backend>::getIdleWaiterForTesting() {
+ std::scoped_lock lock(mLock);
+ if (!mIdleWaiter) {
+ mIdleWaiter = std::make_shared<IdleWaiter>();
+ }
+
+ // Return a lambda that holds a strong reference to the idle waiter, whose lifetime can extend
+ // beyond this threaded backend object.
+ return [idleWaiter = mIdleWaiter]() {
+ std::unique_lock idleLock(idleWaiter->idleLock);
+ base::ScopedLockAssertion assumeLocked(idleWaiter->idleLock);
+ idleWaiter->threadIdleCondition.wait(idleLock, [&]() REQUIRES(idleWaiter->idleLock) {
+ return idleWaiter->isIdle;
+ });
+ };
+}
+
+template <typename Backend>
+void ThreadedBackend<Backend>::setIdleStatus(bool isIdle) {
+ if (!mIdleWaiter) {
+ return;
+ }
+ std::scoped_lock idleLock(mIdleWaiter->idleLock);
+ mIdleWaiter->isIdle = isIdle;
+ if (isIdle) {
+ mIdleWaiter->threadIdleCondition.notify_all();
+ }
+}
+
// Explicit template instantiation for the PerfettoBackend.
template class ThreadedBackend<PerfettoBackend>;
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.h b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
index 650a87e..52a84c4 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.h
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
@@ -42,6 +42,9 @@
void traceMotionEvent(const TracedMotionEvent&, const TracedEventMetadata&) override;
void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventMetadata&) override;
+ /** Returns a function that, when called, will block until the tracing thread is idle. */
+ std::function<void()> getIdleWaiterForTesting();
+
private:
std::mutex mLock;
bool mThreadExit GUARDED_BY(mLock){false};
@@ -52,12 +55,21 @@
TracedEventMetadata>;
std::vector<TraceEntry> mQueue GUARDED_BY(mLock);
+ struct IdleWaiter {
+ std::mutex idleLock;
+ std::condition_variable threadIdleCondition;
+ bool isIdle GUARDED_BY(idleLock){false};
+ };
+ // The lazy-initialized object used to wait for the tracing thread to idle.
+ std::shared_ptr<IdleWaiter> mIdleWaiter GUARDED_BY(mLock);
+
// InputThread stops when its destructor is called. Initialize it last so that it is the
// first thing to be destructed. This will guarantee the thread will not access other
// members that have already been destructed.
InputThread mTracerThread;
void threadLoop();
+ void setIdleStatus(bool isIdle) REQUIRES(mLock);
};
} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 79c8a4b..77e672c 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -111,9 +111,13 @@
// Used to determine which DisplayViewport should be tied to which InputDevice.
std::unordered_map<std::string, uint8_t> portAssociations;
- // The associations between input device physical port locations and display unique ids.
+ // The associations between input device ports and display unique ids.
// Used to determine which DisplayViewport should be tied to which InputDevice.
- std::unordered_map<std::string, std::string> uniqueIdAssociations;
+ std::unordered_map<std::string, std::string> uniqueIdAssociationsByPort;
+
+ // The associations between input device descriptor and display unique ids.
+ // Used to determine which DisplayViewport should be tied to which InputDevice.
+ std::unordered_map<std::string, std::string> uniqueIdAssociationsByDescriptor;
// The associations between input device ports device types.
// This is used to determine which device type and source should be tied to which InputDevice.
diff --git a/services/inputflinger/include/NotifyArgsBuilders.h b/services/inputflinger/include/NotifyArgsBuilders.h
index 8ffbc11..1bd5595 100644
--- a/services/inputflinger/include/NotifyArgsBuilders.h
+++ b/services/inputflinger/include/NotifyArgsBuilders.h
@@ -30,8 +30,11 @@
class MotionArgsBuilder {
public:
- MotionArgsBuilder(int32_t action, int32_t source) {
+ MotionArgsBuilder(int32_t action, int32_t source) : mEventId(InputEvent::nextId()) {
mAction = action;
+ if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
+ addFlag(AMOTION_EVENT_FLAG_CANCELED);
+ }
mSource = source;
mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
mDownTime = mEventTime;
@@ -97,7 +100,7 @@
return *this;
}
- NotifyMotionArgs build() {
+ NotifyMotionArgs build() const {
std::vector<PointerProperties> pointerProperties;
std::vector<PointerCoords> pointerCoords;
for (const PointerBuilder& pointer : mPointers) {
@@ -106,19 +109,17 @@
}
// Set mouse cursor position for the most common cases to avoid boilerplate.
+ float resolvedCursorX = mRawXCursorPosition;
+ float resolvedCursorY = mRawYCursorPosition;
if (mSource == AINPUT_SOURCE_MOUSE &&
!MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) &&
BitSet64::hasBit(pointerCoords[0].bits, AMOTION_EVENT_AXIS_X) &&
BitSet64::hasBit(pointerCoords[0].bits, AMOTION_EVENT_AXIS_Y)) {
- mRawXCursorPosition = pointerCoords[0].getX();
- mRawYCursorPosition = pointerCoords[0].getY();
+ resolvedCursorX = pointerCoords[0].getX();
+ resolvedCursorY = pointerCoords[0].getY();
}
- if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
- addFlag(AMOTION_EVENT_FLAG_CANCELED);
- }
-
- return {InputEvent::nextId(),
+ return {mEventId,
mEventTime,
/*readTime=*/mEventTime,
mDeviceId,
@@ -137,13 +138,14 @@
pointerCoords.data(),
/*xPrecision=*/0,
/*yPrecision=*/0,
- mRawXCursorPosition,
- mRawYCursorPosition,
+ resolvedCursorX,
+ resolvedCursorY,
mDownTime,
/*videoFrames=*/{}};
}
private:
+ const int32_t mEventId;
int32_t mAction;
int32_t mDeviceId{DEFAULT_DEVICE_ID};
uint32_t mSource;
@@ -163,7 +165,7 @@
class KeyArgsBuilder {
public:
- KeyArgsBuilder(int32_t action, int32_t source) {
+ KeyArgsBuilder(int32_t action, int32_t source) : mEventId(InputEvent::nextId()) {
mAction = action;
mSource = source;
mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
@@ -206,7 +208,7 @@
}
NotifyKeyArgs build() const {
- return {InputEvent::nextId(),
+ return {mEventId,
mEventTime,
/*readTime=*/mEventTime,
mDeviceId,
@@ -222,6 +224,7 @@
}
private:
+ const int32_t mEventId;
int32_t mAction;
int32_t mDeviceId = DEFAULT_DEVICE_ID;
uint32_t mSource;
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 782c84a..933c502 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -77,11 +77,11 @@
// If a device is associated with a specific display but there is no
// associated DisplayViewport, don't enable the device.
- if (enable && (mAssociatedDisplayPort || mAssociatedDisplayUniqueId) &&
+ if (enable && (mAssociatedDisplayPort || mAssociatedDisplayUniqueIdByPort) &&
!mAssociatedViewport) {
const std::string desc = mAssociatedDisplayPort
? "port " + std::to_string(*mAssociatedDisplayPort)
- : "uniqueId " + *mAssociatedDisplayUniqueId;
+ : "uniqueId " + *mAssociatedDisplayUniqueIdByPort;
ALOGW("Cannot enable input device %s because it is associated "
"with %s, but the corresponding viewport is not found",
getName().c_str(), desc.c_str());
@@ -124,9 +124,15 @@
} else {
dump += "<none>\n";
}
- dump += StringPrintf(INDENT2 "AssociatedDisplayUniqueId: ");
- if (mAssociatedDisplayUniqueId) {
- dump += StringPrintf("%s\n", mAssociatedDisplayUniqueId->c_str());
+ dump += StringPrintf(INDENT2 "AssociatedDisplayUniqueIdByPort: ");
+ if (mAssociatedDisplayUniqueIdByPort) {
+ dump += StringPrintf("%s\n", mAssociatedDisplayUniqueIdByPort->c_str());
+ } else {
+ dump += "<none>\n";
+ }
+ dump += StringPrintf(INDENT2 "AssociatedDisplayUniqueIdByDescriptor: ");
+ if (mAssociatedDisplayUniqueIdByDescriptor) {
+ dump += StringPrintf("%s\n", mAssociatedDisplayUniqueIdByDescriptor->c_str());
} else {
dump += "<none>\n";
}
@@ -269,8 +275,28 @@
// In most situations, no port or name will be specified.
mAssociatedDisplayPort = std::nullopt;
- mAssociatedDisplayUniqueId = std::nullopt;
+ mAssociatedDisplayUniqueIdByPort = std::nullopt;
mAssociatedViewport = std::nullopt;
+ // Find the display port that corresponds to the current input device descriptor
+ const std::string& inputDeviceDescriptor = mIdentifier.descriptor;
+ if (!inputDeviceDescriptor.empty()) {
+ const std::unordered_map<std::string, uint8_t>& ports =
+ readerConfig.portAssociations;
+ const auto& displayPort = ports.find(inputDeviceDescriptor);
+ if (displayPort != ports.end()) {
+ mAssociatedDisplayPort = std::make_optional(displayPort->second);
+ } else {
+ const std::unordered_map<std::string, std::string>&
+ displayUniqueIdsByDescriptor =
+ readerConfig.uniqueIdAssociationsByDescriptor;
+ const auto& displayUniqueIdByDescriptor =
+ displayUniqueIdsByDescriptor.find(inputDeviceDescriptor);
+ if (displayUniqueIdByDescriptor != displayUniqueIdsByDescriptor.end()) {
+ mAssociatedDisplayUniqueIdByDescriptor =
+ displayUniqueIdByDescriptor->second;
+ }
+ }
+ }
// Find the display port that corresponds to the current input port.
const std::string& inputPort = mIdentifier.location;
if (!inputPort.empty()) {
@@ -280,11 +306,11 @@
if (displayPort != ports.end()) {
mAssociatedDisplayPort = std::make_optional(displayPort->second);
} else {
- const std::unordered_map<std::string, std::string>& displayUniqueIds =
- readerConfig.uniqueIdAssociations;
- const auto& displayUniqueId = displayUniqueIds.find(inputPort);
- if (displayUniqueId != displayUniqueIds.end()) {
- mAssociatedDisplayUniqueId = displayUniqueId->second;
+ const std::unordered_map<std::string, std::string>& displayUniqueIdsByPort =
+ readerConfig.uniqueIdAssociationsByPort;
+ const auto& displayUniqueIdByPort = displayUniqueIdsByPort.find(inputPort);
+ if (displayUniqueIdByPort != displayUniqueIdsByPort.end()) {
+ mAssociatedDisplayUniqueIdByPort = displayUniqueIdByPort->second;
}
}
}
@@ -299,13 +325,21 @@
"but the corresponding viewport is not found.",
getName().c_str(), *mAssociatedDisplayPort);
}
- } else if (mAssociatedDisplayUniqueId != std::nullopt) {
- mAssociatedViewport =
- readerConfig.getDisplayViewportByUniqueId(*mAssociatedDisplayUniqueId);
+ } else if (mAssociatedDisplayUniqueIdByDescriptor != std::nullopt) {
+ mAssociatedViewport = readerConfig.getDisplayViewportByUniqueId(
+ *mAssociatedDisplayUniqueIdByDescriptor);
if (!mAssociatedViewport) {
ALOGW("Input device %s should be associated with display %s but the "
"corresponding viewport cannot be found",
- getName().c_str(), mAssociatedDisplayUniqueId->c_str());
+ getName().c_str(), mAssociatedDisplayUniqueIdByDescriptor->c_str());
+ }
+ } else if (mAssociatedDisplayUniqueIdByPort != std::nullopt) {
+ mAssociatedViewport = readerConfig.getDisplayViewportByUniqueId(
+ *mAssociatedDisplayUniqueIdByPort);
+ if (!mAssociatedViewport) {
+ ALOGW("Input device %s should be associated with display %s but the "
+ "corresponding viewport cannot be found",
+ getName().c_str(), mAssociatedDisplayUniqueIdByPort->c_str());
}
}
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 0719b0c..feb4071 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -63,8 +63,11 @@
inline std::optional<uint8_t> getAssociatedDisplayPort() const {
return mAssociatedDisplayPort;
}
- inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
- return mAssociatedDisplayUniqueId;
+ inline std::optional<std::string> getAssociatedDisplayUniqueIdByPort() const {
+ return mAssociatedDisplayUniqueIdByPort;
+ }
+ inline std::optional<std::string> getAssociatedDisplayUniqueIdByDescriptor() const {
+ return mAssociatedDisplayUniqueIdByDescriptor;
}
inline std::optional<std::string> getDeviceTypeAssociation() const {
return mAssociatedDeviceType;
@@ -194,7 +197,8 @@
bool mIsWaking;
bool mIsExternal;
std::optional<uint8_t> mAssociatedDisplayPort;
- std::optional<std::string> mAssociatedDisplayUniqueId;
+ std::optional<std::string> mAssociatedDisplayUniqueIdByPort;
+ std::optional<std::string> mAssociatedDisplayUniqueIdByDescriptor;
std::optional<std::string> mAssociatedDeviceType;
std::optional<DisplayViewport> mAssociatedViewport;
bool mHasMic;
@@ -449,8 +453,11 @@
inline std::optional<uint8_t> getAssociatedDisplayPort() const {
return mDevice.getAssociatedDisplayPort();
}
- inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
- return mDevice.getAssociatedDisplayUniqueId();
+ inline std::optional<std::string> getAssociatedDisplayUniqueIdByPort() const {
+ return mDevice.getAssociatedDisplayUniqueIdByPort();
+ }
+ inline std::optional<std::string> getAssociatedDisplayUniqueIdByDescriptor() const {
+ return mDevice.getAssociatedDisplayUniqueIdByDescriptor();
}
inline std::optional<std::string> getDeviceTypeAssociation() const {
return mDevice.getDeviceTypeAssociation();
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 7d27d4a..b0ba8d8 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,14 +533,20 @@
* 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();
}
- const std::optional<std::string> associatedDisplayUniqueId =
- getDeviceContext().getAssociatedDisplayUniqueId();
- if (associatedDisplayUniqueId) {
+ const std::optional<std::string> associatedDisplayUniqueIdByDescriptor =
+ getDeviceContext().getAssociatedDisplayUniqueIdByDescriptor();
+ if (associatedDisplayUniqueIdByDescriptor) {
+ return getDeviceContext().getAssociatedViewport();
+ }
+
+ const std::optional<std::string> associatedDisplayUniqueIdByPort =
+ getDeviceContext().getAssociatedDisplayUniqueIdByPort();
+ if (associatedDisplayUniqueIdByPort) {
return getDeviceContext().getAssociatedViewport();
}
@@ -939,8 +947,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 +1894,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 +1921,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/rust/input_filter.rs b/services/inputflinger/rust/input_filter.rs
index a544fa3..6df339e 100644
--- a/services/inputflinger/rust/input_filter.rs
+++ b/services/inputflinger/rust/input_filter.rs
@@ -396,14 +396,16 @@
IInputThread::{BnInputThread, IInputThread, IInputThreadCallback::IInputThreadCallback},
KeyEvent::KeyEvent,
};
- use std::sync::{Arc, RwLock, RwLockWriteGuard};
+ use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
+ use std::sync::{atomic::AtomicBool, atomic::Ordering, Arc, RwLock, RwLockWriteGuard};
+ use std::time::Duration;
#[derive(Default)]
struct TestCallbacksInner {
last_modifier_state: u32,
last_locked_modifier_state: u32,
last_event: Option<KeyEvent>,
- test_thread: Option<TestThread>,
+ test_thread: Option<FakeCppThread>,
}
#[derive(Default, Clone)]
@@ -438,13 +440,9 @@
self.0.read().unwrap().last_locked_modifier_state
}
- pub fn is_thread_created(&self) -> bool {
- self.0.read().unwrap().test_thread.is_some()
- }
-
- pub fn is_thread_finished(&self) -> bool {
+ pub fn is_thread_running(&self) -> bool {
if let Some(test_thread) = &self.0.read().unwrap().test_thread {
- return test_thread.is_finish_called();
+ return test_thread.is_running();
}
false
}
@@ -468,41 +466,101 @@
fn createInputFilterThread(
&self,
- _callback: &Strong<dyn IInputThreadCallback>,
+ callback: &Strong<dyn IInputThreadCallback>,
) -> std::result::Result<Strong<dyn IInputThread>, binder::Status> {
- let test_thread = TestThread::new();
+ let test_thread = FakeCppThread::new(callback.clone());
+ test_thread.start_looper();
self.inner().test_thread = Some(test_thread.clone());
Result::Ok(BnInputThread::new_binder(test_thread, BinderFeatures::default()))
}
}
#[derive(Default)]
- struct TestThreadInner {
- is_finish_called: bool,
+ struct FakeCppThreadInner {
+ join_handle: Option<std::thread::JoinHandle<()>>,
}
- #[derive(Default, Clone)]
- struct TestThread(Arc<RwLock<TestThreadInner>>);
+ #[derive(Clone)]
+ struct FakeCppThread {
+ callback: Arc<RwLock<Strong<dyn IInputThreadCallback>>>,
+ inner: Arc<RwLock<FakeCppThreadInner>>,
+ exit_flag: Arc<AtomicBool>,
+ }
- impl Interface for TestThread {}
+ impl Interface for FakeCppThread {}
- impl TestThread {
- pub fn new() -> Self {
- Default::default()
+ impl FakeCppThread {
+ pub fn new(callback: Strong<dyn IInputThreadCallback>) -> Self {
+ let thread = Self {
+ callback: Arc::new(RwLock::new(callback)),
+ inner: Arc::new(RwLock::new(FakeCppThreadInner { join_handle: None })),
+ exit_flag: Arc::new(AtomicBool::new(true)),
+ };
+ thread.create_looper();
+ thread
}
- fn inner(&self) -> RwLockWriteGuard<'_, TestThreadInner> {
- self.0.write().unwrap()
+ fn inner(&self) -> RwLockWriteGuard<'_, FakeCppThreadInner> {
+ self.inner.write().unwrap()
}
- pub fn is_finish_called(&self) -> bool {
- self.0.read().unwrap().is_finish_called
+ fn create_looper(&self) {
+ let clone = self.clone();
+ let join_handle = std::thread::Builder::new()
+ .name("fake_cpp_thread".to_string())
+ .spawn(move || loop {
+ if !clone.exit_flag.load(Ordering::Relaxed) {
+ clone.loop_once();
+ }
+ })
+ .unwrap();
+ self.inner().join_handle = Some(join_handle);
+ // Sleep until the looper thread starts
+ std::thread::sleep(Duration::from_millis(10));
+ }
+
+ pub fn start_looper(&self) {
+ self.exit_flag.store(false, Ordering::Relaxed);
+ }
+
+ pub fn stop_looper(&self) {
+ self.exit_flag.store(true, Ordering::Relaxed);
+ if let Some(join_handle) = &self.inner.read().unwrap().join_handle {
+ join_handle.thread().unpark();
+ }
+ }
+
+ pub fn is_running(&self) -> bool {
+ !self.exit_flag.load(Ordering::Relaxed)
+ }
+
+ fn loop_once(&self) {
+ let _ = self.callback.read().unwrap().loopOnce();
}
}
- impl IInputThread for TestThread {
+ impl IInputThread for FakeCppThread {
fn finish(&self) -> binder::Result<()> {
- self.inner().is_finish_called = true;
+ self.stop_looper();
+ Result::Ok(())
+ }
+
+ fn wake(&self) -> binder::Result<()> {
+ if let Some(join_handle) = &self.inner.read().unwrap().join_handle {
+ join_handle.thread().unpark();
+ }
+ Result::Ok(())
+ }
+
+ fn sleepUntil(&self, wake_up_time: i64) -> binder::Result<()> {
+ let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+ if wake_up_time == i64::MAX {
+ std::thread::park();
+ } else {
+ let duration_now = Duration::from_nanos(now as u64);
+ let duration_wake_up = Duration::from_nanos(wake_up_time as u64);
+ std::thread::park_timeout(duration_wake_up - duration_now);
+ }
Result::Ok(())
}
}
diff --git a/services/inputflinger/rust/input_filter_thread.rs b/services/inputflinger/rust/input_filter_thread.rs
index 2d503ae..96e5681 100644
--- a/services/inputflinger/rust/input_filter_thread.rs
+++ b/services/inputflinger/rust/input_filter_thread.rs
@@ -33,8 +33,6 @@
use log::{debug, error};
use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
use std::sync::{Arc, RwLock, RwLockWriteGuard};
-use std::time::Duration;
-use std::{thread, thread::Thread};
/// Interface to receive callback from Input filter thread
pub trait ThreadCallback {
@@ -54,15 +52,18 @@
thread_creator: InputFilterThreadCreator,
thread_callback_handler: ThreadCallbackHandler,
inner: Arc<RwLock<InputFilterThreadInner>>,
+ looper: Arc<RwLock<Looper>>,
}
struct InputFilterThreadInner {
- cpp_thread: Option<Strong<dyn IInputThread>>,
- looper: Option<Thread>,
next_timeout: i64,
is_finishing: bool,
}
+struct Looper {
+ cpp_thread: Option<Strong<dyn IInputThread>>,
+}
+
impl InputFilterThread {
/// Create a new InputFilterThread instance.
/// NOTE: This will create a new thread. Clone the existing instance to reuse the same thread.
@@ -71,11 +72,10 @@
thread_creator,
thread_callback_handler: ThreadCallbackHandler::new(),
inner: Arc::new(RwLock::new(InputFilterThreadInner {
- cpp_thread: None,
- looper: None,
next_timeout: i64::MAX,
is_finishing: false,
})),
+ looper: Arc::new(RwLock::new(Looper { cpp_thread: None })),
}
}
@@ -83,12 +83,17 @@
/// time on the input filter thread.
/// {@see ThreadCallback.notify_timeout_expired(...)}
pub fn request_timeout_at_time(&self, when_nanos: i64) {
- let filter_thread = &mut self.filter_thread();
- if when_nanos < filter_thread.next_timeout {
- filter_thread.next_timeout = when_nanos;
- if let Some(looper) = &filter_thread.looper {
- looper.unpark();
+ let mut need_wake = false;
+ {
+ // acquire filter lock
+ let filter_thread = &mut self.filter_thread();
+ if when_nanos < filter_thread.next_timeout {
+ filter_thread.next_timeout = when_nanos;
+ need_wake = true;
}
+ } // release filter lock
+ if need_wake {
+ self.wake();
}
}
@@ -120,29 +125,36 @@
fn start(&self) {
debug!("InputFilterThread: start thread");
- let filter_thread = &mut self.filter_thread();
- if filter_thread.cpp_thread.is_none() {
- filter_thread.cpp_thread = Some(self.thread_creator.create(
- &BnInputThreadCallback::new_binder(self.clone(), BinderFeatures::default()),
- ));
- filter_thread.looper = None;
- filter_thread.is_finishing = false;
- }
+ {
+ // acquire looper lock
+ let looper = &mut self.looper();
+ if looper.cpp_thread.is_none() {
+ looper.cpp_thread = Some(self.thread_creator.create(
+ &BnInputThreadCallback::new_binder(self.clone(), BinderFeatures::default()),
+ ));
+ }
+ } // release looper lock
+ self.set_finishing(false);
}
fn stop(&self) {
debug!("InputFilterThread: stop thread");
+ self.set_finishing(true);
+ self.wake();
+ {
+ // acquire looper lock
+ let looper = &mut self.looper();
+ if let Some(cpp_thread) = &looper.cpp_thread {
+ let _ = cpp_thread.finish();
+ }
+ // Clear all references
+ looper.cpp_thread = None;
+ } // release looper lock
+ }
+
+ fn set_finishing(&self, is_finishing: bool) {
let filter_thread = &mut self.filter_thread();
- filter_thread.is_finishing = true;
- if let Some(looper) = &filter_thread.looper {
- looper.unpark();
- }
- if let Some(cpp_thread) = &filter_thread.cpp_thread {
- let _ = cpp_thread.finish();
- }
- // Clear all references
- filter_thread.cpp_thread = None;
- filter_thread.looper = None;
+ filter_thread.is_finishing = is_finishing;
}
fn loop_once(&self, now: i64) {
@@ -163,25 +175,34 @@
wake_up_time = filter_thread.next_timeout;
}
}
- if filter_thread.looper.is_none() {
- filter_thread.looper = Some(std::thread::current());
- }
} // release thread lock
if timeout_expired {
self.thread_callback_handler.notify_timeout_expired(now);
}
- if wake_up_time == i64::MAX {
- thread::park();
- } else {
- let duration_now = Duration::from_nanos(now as u64);
- let duration_wake_up = Duration::from_nanos(wake_up_time as u64);
- thread::park_timeout(duration_wake_up - duration_now);
- }
+ self.sleep_until(wake_up_time);
}
fn filter_thread(&self) -> RwLockWriteGuard<'_, InputFilterThreadInner> {
self.inner.write().unwrap()
}
+
+ fn sleep_until(&self, when_nanos: i64) {
+ let looper = self.looper.read().unwrap();
+ if let Some(cpp_thread) = &looper.cpp_thread {
+ let _ = cpp_thread.sleepUntil(when_nanos);
+ }
+ }
+
+ fn wake(&self) {
+ let looper = self.looper.read().unwrap();
+ if let Some(cpp_thread) = &looper.cpp_thread {
+ let _ = cpp_thread.wake();
+ }
+ }
+
+ fn looper(&self) -> RwLockWriteGuard<'_, Looper> {
+ self.looper.write().unwrap()
+ }
}
impl Interface for InputFilterThread {}
@@ -252,165 +273,64 @@
#[cfg(test)]
mod tests {
- use crate::input_filter::test_callbacks::TestCallbacks;
- use crate::input_filter_thread::{
- test_thread::TestThread, test_thread_callback::TestThreadCallback,
- };
+ use crate::input_filter::{test_callbacks::TestCallbacks, InputFilterThreadCreator};
+ use crate::input_filter_thread::{test_thread_callback::TestThreadCallback, InputFilterThread};
+ use binder::Strong;
+ use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
+ use std::sync::{Arc, RwLock};
+ use std::time::Duration;
#[test]
fn test_register_callback_creates_cpp_thread() {
let test_callbacks = TestCallbacks::new();
- let test_thread = TestThread::new(test_callbacks.clone());
+ let test_thread = get_thread(test_callbacks.clone());
let test_thread_callback = TestThreadCallback::new();
- test_thread.register_thread_callback(test_thread_callback);
- assert!(test_callbacks.is_thread_created());
+ test_thread.register_thread_callback(Box::new(test_thread_callback));
+ assert!(test_callbacks.is_thread_running());
}
#[test]
fn test_unregister_callback_finishes_cpp_thread() {
let test_callbacks = TestCallbacks::new();
- let test_thread = TestThread::new(test_callbacks.clone());
+ let test_thread = get_thread(test_callbacks.clone());
let test_thread_callback = TestThreadCallback::new();
- test_thread.register_thread_callback(test_thread_callback.clone());
- test_thread.unregister_thread_callback(test_thread_callback);
- assert!(test_callbacks.is_thread_finished());
+ test_thread.register_thread_callback(Box::new(test_thread_callback.clone()));
+ test_thread.unregister_thread_callback(Box::new(test_thread_callback));
+ assert!(!test_callbacks.is_thread_running());
}
#[test]
fn test_notify_timeout_called_after_timeout_expired() {
let test_callbacks = TestCallbacks::new();
- let test_thread = TestThread::new(test_callbacks.clone());
+ let test_thread = get_thread(test_callbacks.clone());
let test_thread_callback = TestThreadCallback::new();
- test_thread.register_thread_callback(test_thread_callback.clone());
- test_thread.start_looper();
+ test_thread.register_thread_callback(Box::new(test_thread_callback.clone()));
- test_thread.request_timeout_at_time(500);
- test_thread.dispatch_next();
+ let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_milliseconds();
+ test_thread.request_timeout_at_time((now + 10) * 1000000);
- test_thread.move_time_forward(500);
-
- test_thread.stop_looper();
+ std::thread::sleep(Duration::from_millis(20));
assert!(test_thread_callback.is_notify_timeout_called());
}
#[test]
fn test_notify_timeout_not_called_before_timeout_expired() {
let test_callbacks = TestCallbacks::new();
- let test_thread = TestThread::new(test_callbacks.clone());
+ let test_thread = get_thread(test_callbacks.clone());
let test_thread_callback = TestThreadCallback::new();
- test_thread.register_thread_callback(test_thread_callback.clone());
- test_thread.start_looper();
+ test_thread.register_thread_callback(Box::new(test_thread_callback.clone()));
- test_thread.request_timeout_at_time(500);
- test_thread.dispatch_next();
+ let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_milliseconds();
+ test_thread.request_timeout_at_time((now + 100) * 1000000);
- test_thread.move_time_forward(100);
-
- test_thread.stop_looper();
+ std::thread::sleep(Duration::from_millis(10));
assert!(!test_thread_callback.is_notify_timeout_called());
}
-}
-#[cfg(test)]
-pub mod test_thread {
-
- use crate::input_filter::{test_callbacks::TestCallbacks, InputFilterThreadCreator};
- use crate::input_filter_thread::{test_thread_callback::TestThreadCallback, InputFilterThread};
- use binder::Strong;
- use std::sync::{
- atomic::AtomicBool, atomic::AtomicI64, atomic::Ordering, Arc, RwLock, RwLockWriteGuard,
- };
- use std::time::Duration;
-
- #[derive(Clone)]
- pub struct TestThread {
- input_thread: InputFilterThread,
- inner: Arc<RwLock<TestThreadInner>>,
- exit_flag: Arc<AtomicBool>,
- now: Arc<AtomicI64>,
- }
-
- struct TestThreadInner {
- join_handle: Option<std::thread::JoinHandle<()>>,
- }
-
- impl TestThread {
- pub fn new(callbacks: TestCallbacks) -> TestThread {
- Self {
- input_thread: InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(
- RwLock::new(Strong::new(Box::new(callbacks))),
- ))),
- inner: Arc::new(RwLock::new(TestThreadInner { join_handle: None })),
- exit_flag: Arc::new(AtomicBool::new(false)),
- now: Arc::new(AtomicI64::new(0)),
- }
- }
-
- fn inner(&self) -> RwLockWriteGuard<'_, TestThreadInner> {
- self.inner.write().unwrap()
- }
-
- pub fn get_input_thread(&self) -> InputFilterThread {
- self.input_thread.clone()
- }
-
- pub fn register_thread_callback(&self, thread_callback: TestThreadCallback) {
- self.input_thread.register_thread_callback(Box::new(thread_callback));
- }
-
- pub fn unregister_thread_callback(&self, thread_callback: TestThreadCallback) {
- self.input_thread.unregister_thread_callback(Box::new(thread_callback));
- }
-
- pub fn start_looper(&self) {
- self.exit_flag.store(false, Ordering::Relaxed);
- let clone = self.clone();
- let join_handle = std::thread::Builder::new()
- .name("test_thread".to_string())
- .spawn(move || {
- while !clone.exit_flag.load(Ordering::Relaxed) {
- clone.loop_once();
- }
- })
- .unwrap();
- self.inner().join_handle = Some(join_handle);
- // Sleep until the looper thread starts
- std::thread::sleep(Duration::from_millis(10));
- }
-
- pub fn stop_looper(&self) {
- self.exit_flag.store(true, Ordering::Relaxed);
- {
- let mut inner = self.inner();
- if let Some(join_handle) = &inner.join_handle {
- join_handle.thread().unpark();
- }
- inner.join_handle.take().map(std::thread::JoinHandle::join);
- inner.join_handle = None;
- }
- self.exit_flag.store(false, Ordering::Relaxed);
- }
-
- pub fn move_time_forward(&self, value: i64) {
- let _ = self.now.fetch_add(value, Ordering::Relaxed);
- self.dispatch_next();
- }
-
- pub fn dispatch_next(&self) {
- if let Some(join_handle) = &self.inner().join_handle {
- join_handle.thread().unpark();
- }
- // Sleep until the looper thread runs a loop
- std::thread::sleep(Duration::from_millis(10));
- }
-
- fn loop_once(&self) {
- self.input_thread.loop_once(self.now.load(Ordering::Relaxed));
- }
-
- pub fn request_timeout_at_time(&self, when_nanos: i64) {
- self.input_thread.request_timeout_at_time(when_nanos);
- }
+ fn get_thread(callbacks: TestCallbacks) -> InputFilterThread {
+ InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(RwLock::new(Strong::new(
+ Box::new(callbacks),
+ )))))
}
}
diff --git a/services/inputflinger/rust/slow_keys_filter.rs b/services/inputflinger/rust/slow_keys_filter.rs
index 09fbf40..0f18a2f 100644
--- a/services/inputflinger/rust/slow_keys_filter.rs
+++ b/services/inputflinger/rust/slow_keys_filter.rs
@@ -207,13 +207,19 @@
#[cfg(test)]
mod tests {
- use crate::input_filter::{test_callbacks::TestCallbacks, test_filter::TestFilter, Filter};
- use crate::input_filter_thread::test_thread::TestThread;
+ use crate::input_filter::{
+ test_callbacks::TestCallbacks, test_filter::TestFilter, Filter, InputFilterThreadCreator,
+ };
+ use crate::input_filter_thread::InputFilterThread;
use crate::slow_keys_filter::{SlowKeysFilter, POLICY_FLAG_DISABLE_KEY_REPEAT};
use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+ use binder::Strong;
use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
};
+ use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
+ use std::sync::{Arc, RwLock};
+ use std::time::Duration;
static BASE_KEY_EVENT: KeyEvent = KeyEvent {
id: 1,
@@ -231,18 +237,19 @@
metaState: 0,
};
+ static SLOW_KEYS_THRESHOLD_NS: i64 = 100 * 1000000; // 100 ms
+
#[test]
fn test_is_notify_key_for_internal_keyboard_not_blocked() {
let test_callbacks = TestCallbacks::new();
- let test_thread = TestThread::new(test_callbacks.clone());
+ let test_thread = get_thread(test_callbacks.clone());
let next = TestFilter::new();
let mut filter = setup_filter_with_internal_device(
Box::new(next.clone()),
test_thread.clone(),
- 1, /* device_id */
- 100, /* threshold */
+ 1, /* device_id */
+ SLOW_KEYS_THRESHOLD_NS,
);
- test_thread.start_looper();
let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
filter.notify_key(&event);
@@ -252,15 +259,14 @@
#[test]
fn test_is_notify_key_for_external_stylus_not_blocked() {
let test_callbacks = TestCallbacks::new();
- let test_thread = TestThread::new(test_callbacks.clone());
+ let test_thread = get_thread(test_callbacks.clone());
let next = TestFilter::new();
let mut filter = setup_filter_with_external_device(
Box::new(next.clone()),
test_thread.clone(),
- 1, /* device_id */
- 100, /* threshold */
+ 1, /* device_id */
+ SLOW_KEYS_THRESHOLD_NS,
);
- test_thread.start_looper();
let event =
KeyEvent { action: KeyEventAction::DOWN, source: Source::STYLUS, ..BASE_KEY_EVENT };
@@ -271,89 +277,115 @@
#[test]
fn test_notify_key_for_external_keyboard_when_key_pressed_for_threshold_time() {
let test_callbacks = TestCallbacks::new();
- let test_thread = TestThread::new(test_callbacks.clone());
+ let test_thread = get_thread(test_callbacks.clone());
let next = TestFilter::new();
let mut filter = setup_filter_with_external_device(
Box::new(next.clone()),
test_thread.clone(),
- 1, /* device_id */
- 100, /* threshold */
+ 1, /* device_id */
+ SLOW_KEYS_THRESHOLD_NS,
);
- test_thread.start_looper();
-
- filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
+ let down_time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+ filter.notify_key(&KeyEvent {
+ action: KeyEventAction::DOWN,
+ downTime: down_time,
+ eventTime: down_time,
+ ..BASE_KEY_EVENT
+ });
assert!(next.last_event().is_none());
- test_thread.dispatch_next();
- test_thread.move_time_forward(100);
-
- test_thread.stop_looper();
+ std::thread::sleep(Duration::from_nanos(2 * SLOW_KEYS_THRESHOLD_NS as u64));
assert_eq!(
next.last_event().unwrap(),
KeyEvent {
action: KeyEventAction::DOWN,
- downTime: 100,
- eventTime: 100,
+ downTime: down_time + SLOW_KEYS_THRESHOLD_NS,
+ eventTime: down_time + SLOW_KEYS_THRESHOLD_NS,
policyFlags: POLICY_FLAG_DISABLE_KEY_REPEAT,
..BASE_KEY_EVENT
}
);
+
+ let up_time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+ filter.notify_key(&KeyEvent {
+ action: KeyEventAction::UP,
+ downTime: down_time,
+ eventTime: up_time,
+ ..BASE_KEY_EVENT
+ });
+
+ assert_eq!(
+ next.last_event().unwrap(),
+ KeyEvent {
+ action: KeyEventAction::UP,
+ downTime: down_time + SLOW_KEYS_THRESHOLD_NS,
+ eventTime: up_time,
+ ..BASE_KEY_EVENT
+ }
+ );
}
#[test]
fn test_notify_key_for_external_keyboard_when_key_not_pressed_for_threshold_time() {
let test_callbacks = TestCallbacks::new();
- let test_thread = TestThread::new(test_callbacks.clone());
+ let test_thread = get_thread(test_callbacks.clone());
let next = TestFilter::new();
let mut filter = setup_filter_with_external_device(
Box::new(next.clone()),
test_thread.clone(),
- 1, /* device_id */
- 100, /* threshold */
+ 1, /* device_id */
+ SLOW_KEYS_THRESHOLD_NS,
);
- test_thread.start_looper();
+ let mut now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+ filter.notify_key(&KeyEvent {
+ action: KeyEventAction::DOWN,
+ downTime: now,
+ eventTime: now,
+ ..BASE_KEY_EVENT
+ });
- filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
- test_thread.dispatch_next();
+ std::thread::sleep(Duration::from_nanos(SLOW_KEYS_THRESHOLD_NS as u64 / 2));
- test_thread.move_time_forward(10);
+ now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+ filter.notify_key(&KeyEvent {
+ action: KeyEventAction::UP,
+ downTime: now,
+ eventTime: now,
+ ..BASE_KEY_EVENT
+ });
- filter.notify_key(&KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT });
- test_thread.dispatch_next();
-
- test_thread.stop_looper();
assert!(next.last_event().is_none());
}
#[test]
fn test_notify_key_for_external_keyboard_when_device_removed_before_threshold_time() {
let test_callbacks = TestCallbacks::new();
- let test_thread = TestThread::new(test_callbacks.clone());
+ let test_thread = get_thread(test_callbacks.clone());
let next = TestFilter::new();
let mut filter = setup_filter_with_external_device(
Box::new(next.clone()),
test_thread.clone(),
- 1, /* device_id */
- 100, /* threshold */
+ 1, /* device_id */
+ SLOW_KEYS_THRESHOLD_NS,
);
- test_thread.start_looper();
- filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
- assert!(next.last_event().is_none());
- test_thread.dispatch_next();
+ let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+ filter.notify_key(&KeyEvent {
+ action: KeyEventAction::DOWN,
+ downTime: now,
+ eventTime: now,
+ ..BASE_KEY_EVENT
+ });
filter.notify_devices_changed(&[]);
- test_thread.dispatch_next();
+ std::thread::sleep(Duration::from_nanos(2 * SLOW_KEYS_THRESHOLD_NS as u64));
- test_thread.move_time_forward(100);
-
- test_thread.stop_looper();
assert!(next.last_event().is_none());
}
fn setup_filter_with_external_device(
next: Box<dyn Filter + Send + Sync>,
- test_thread: TestThread,
+ test_thread: InputFilterThread,
device_id: i32,
threshold: i64,
) -> SlowKeysFilter {
@@ -367,7 +399,7 @@
fn setup_filter_with_internal_device(
next: Box<dyn Filter + Send + Sync>,
- test_thread: TestThread,
+ test_thread: InputFilterThread,
device_id: i32,
threshold: i64,
) -> SlowKeysFilter {
@@ -381,12 +413,18 @@
fn setup_filter_with_devices(
next: Box<dyn Filter + Send + Sync>,
- test_thread: TestThread,
+ test_thread: InputFilterThread,
devices: &[DeviceInfo],
threshold: i64,
) -> SlowKeysFilter {
- let mut filter = SlowKeysFilter::new(next, threshold, test_thread.get_input_thread());
+ let mut filter = SlowKeysFilter::new(next, threshold, test_thread);
filter.notify_devices_changed(devices);
filter
}
+
+ fn get_thread(callbacks: TestCallbacks) -> InputFilterThread {
+ InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(RwLock::new(Strong::new(
+ Box::new(callbacks),
+ )))))
+ }
}
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 6ae9790..9b5db23 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -67,6 +67,8 @@
"InputProcessorConverter_test.cpp",
"InputDispatcher_test.cpp",
"InputReader_test.cpp",
+ "InputTraceSession.cpp",
+ "InputTracingTest.cpp",
"InstrumentedInputReader.cpp",
"LatencyTracker_test.cpp",
"MultiTouchMotionAccumulator_test.cpp",
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
index e231bcc..1360cd0 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
@@ -466,8 +466,15 @@
mFilteredEvent = nullptr;
}
-gui::Uid FakeInputDispatcherPolicy::getPackageUid(std::string) {
- return gui::Uid::INVALID;
+gui::Uid FakeInputDispatcherPolicy::getPackageUid(std::string pkg) {
+ std::scoped_lock lock(mLock);
+ auto it = mPackageUidMap.find(pkg);
+ return it != mPackageUidMap.end() ? it->second : gui::Uid::INVALID;
+}
+
+void FakeInputDispatcherPolicy::addPackageUidMapping(std::string package, gui::Uid uid) {
+ std::scoped_lock lock(mLock);
+ mPackageUidMap.insert_or_assign(std::move(package), uid);
}
} // namespace android
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
index d83924f..2cc018e 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -115,6 +115,7 @@
void setUnhandledKeyHandler(std::function<std::optional<KeyEvent>(const KeyEvent&)> handler);
void assertUnhandledKeyReported(int32_t keycode);
void assertUnhandledKeyNotReported();
+ void addPackageUidMapping(std::string package, gui::Uid uid);
private:
std::mutex mLock;
@@ -150,6 +151,8 @@
std::queue<int32_t> mReportedUnhandledKeycodes GUARDED_BY(mLock);
std::function<std::optional<KeyEvent>(const KeyEvent&)> mUnhandledKeyHandler GUARDED_BY(mLock);
+ std::map<std::string, gui::Uid> mPackageUidMap 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
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index 8f593b5..e9118a9 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -139,7 +139,7 @@
void FakeInputReaderPolicy::addInputUniqueIdAssociation(const std::string& inputUniqueId,
const std::string& displayUniqueId) {
- mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId});
+ mConfig.uniqueIdAssociationsByPort.insert({inputUniqueId, displayUniqueId});
}
void FakeInputReaderPolicy::addKeyboardLayoutAssociation(const std::string& inputUniqueId,
diff --git a/services/inputflinger/tests/FakeWindows.cpp b/services/inputflinger/tests/FakeWindows.cpp
index 0ac2f0f..a6955ec 100644
--- a/services/inputflinger/tests/FakeWindows.cpp
+++ b/services/inputflinger/tests/FakeWindows.cpp
@@ -298,16 +298,21 @@
const ::testing::Matcher<MotionEvent>& matcher) {
std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
if (event == nullptr) {
- ADD_FAILURE() << "No event";
+ 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;
+ 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()));
- EXPECT_THAT(*motionEvent, matcher);
+ if (motionEvent == nullptr) {
+ return nullptr;
+ }
+ EXPECT_THAT(*motionEvent, matcher) << " on " << mName;
return motionEvent;
}
diff --git a/services/inputflinger/tests/FakeWindows.h b/services/inputflinger/tests/FakeWindows.h
index c0c8975..26c2b4b 100644
--- a/services/inputflinger/tests/FakeWindows.h
+++ b/services/inputflinger/tests/FakeWindows.h
@@ -157,6 +157,16 @@
inline void setSpy(bool spy) { mInfo.setInputConfig(InputConfig::SPY, spy); }
+ inline void setSecure(bool secure) {
+ if (secure) {
+ mInfo.layoutParamsFlags |= gui::WindowInfo::Flag::SECURE;
+ } else {
+ using namespace ftl::flag_operators;
+ mInfo.layoutParamsFlags &= ~gui::WindowInfo::Flag::SECURE;
+ }
+ mInfo.setInputConfig(InputConfig::SENSITIVE_FOR_TRACING, secure);
+ }
+
inline void setInterceptsStylus(bool interceptsStylus) {
mInfo.setInputConfig(InputConfig::INTERCEPTS_STYLUS, interceptsStylus);
}
@@ -229,10 +239,14 @@
std::unique_ptr<KeyEvent> consumeKey(bool handled = true);
- inline void consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) {
+ inline std::unique_ptr<KeyEvent> consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) {
std::unique_ptr<KeyEvent> keyEvent = consumeKey();
- ASSERT_NE(nullptr, keyEvent);
- ASSERT_THAT(*keyEvent, matcher);
+ EXPECT_NE(nullptr, keyEvent);
+ if (!keyEvent) {
+ return nullptr;
+ }
+ EXPECT_THAT(*keyEvent, matcher);
+ return keyEvent;
}
inline void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 4bb64fc..ccd28f3 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -59,6 +59,8 @@
using namespace ftl::flag_operators;
using testing::AllOf;
using testing::Not;
+using testing::Pointee;
+using testing::UnorderedElementsAre;
namespace {
@@ -132,6 +134,40 @@
return event;
}
+/**
+ * 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:
+ ScopedFlagOverride(std::function<bool()> read, std::function<void(bool)> write, bool value)
+ : mInitialValue(read()), mWriteValue(write) {
+ mWriteValue(value);
+ }
+ ~ScopedFlagOverride() { mWriteValue(mInitialValue); }
+
+private:
+ 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 ---
@@ -1257,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);
@@ -1353,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.
@@ -1500,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);
@@ -1542,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);
@@ -1600,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);
@@ -1651,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);
@@ -1704,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);
@@ -1756,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);
@@ -1800,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);
@@ -1885,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) {
@@ -1944,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 =
@@ -2014,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.
@@ -2022,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 =
@@ -2089,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.
@@ -2096,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);
@@ -2169,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);
@@ -2205,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.
@@ -2218,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);
@@ -2290,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);
@@ -2356,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.
*
@@ -2601,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);
@@ -2698,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.
@@ -2910,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);
@@ -2964,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>();
@@ -2996,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 =
@@ -3102,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) {
@@ -3227,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);
@@ -3258,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);
@@ -3296,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 =
@@ -3520,6 +4800,96 @@
}
/**
+ * Three windows:
+ * - Left window
+ * - Right window
+ * - Outside window(watch for ACTION_OUTSIDE events)
+ * The windows "left" and "outside" share the same owner, the window "right" has a different owner,
+ * In order to allow the outside window can receive the ACTION_OUTSIDE events, the outside window is
+ * positioned above the "left" and "right" windows, and it doesn't overlap with them.
+ *
+ * First, device A report a down event landed in the right window, the outside window can receive
+ * an ACTION_OUTSIDE event that with zeroed coordinates, the device B report a down event landed
+ * in the left window, the outside window can receive an ACTION_OUTSIDE event the with valid
+ * coordinates, after these, device A and device B continue report MOVE event, the right and left
+ * window can receive it, but outside window event can't receive it.
+ */
+TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinatesWhenMultiDevice) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
+ ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect{0, 0, 100, 100});
+ leftWindow->setOwnerInfo(gui::Pid{1}, gui::Uid{101});
+
+ sp<FakeWindowHandle> outsideWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Outside Window",
+ ADISPLAY_ID_DEFAULT);
+ outsideWindow->setFrame(Rect{100, 100, 200, 200});
+ outsideWindow->setOwnerInfo(gui::Pid{1}, gui::Uid{101});
+ outsideWindow->setWatchOutsideTouch(true);
+
+ std::shared_ptr<FakeApplicationHandle> anotherApplication =
+ std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(anotherApplication, mDispatcher, "Right Window",
+ ADISPLAY_ID_DEFAULT);
+ rightWindow->setFrame(Rect{100, 0, 200, 100});
+ rightWindow->setOwnerInfo(gui::Pid{2}, gui::Uid{202});
+
+ // OutsideWindow must be above left window and right window to receive ACTION_OUTSIDE events
+ // when left window or right window is tapped
+ mDispatcher->onWindowInfosChanged(
+ {{*outsideWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()},
+ {},
+ 0,
+ 0});
+
+ const DeviceId deviceA = 9;
+ const DeviceId deviceB = 3;
+
+ // Tap on right window use device A
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+ .deviceId(deviceA)
+ .build());
+ leftWindow->assertNoEvents();
+ rightWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+ // Right window is belonged to another owner, so outsideWindow should receive ACTION_OUTSIDE
+ // with zeroed coords.
+ outsideWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_OUTSIDE), WithDeviceId(deviceA), WithCoords(0, 0)));
+
+ // Tap on left window use device B
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+ .deviceId(deviceB)
+ .build());
+ leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+ rightWindow->assertNoEvents();
+ // Because new gesture down on the left window that has the same owner with outside Window, the
+ // outside Window should receive the ACTION_OUTSIDE with coords.
+ outsideWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_OUTSIDE), WithDeviceId(deviceB), WithCoords(-50, -50)));
+
+ // Ensure that windows that can only accept outside do not receive remaining gestures
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(51))
+ .deviceId(deviceA)
+ .build());
+ leftWindow->assertNoEvents();
+ rightWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceA)));
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(51).y(51))
+ .deviceId(deviceB)
+ .build());
+ leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB)));
+ rightWindow->assertNoEvents();
+ outsideWindow->assertNoEvents();
+}
+
+/**
* This test documents the behavior of WATCH_OUTSIDE_TOUCH. The window will get ACTION_OUTSIDE when
* a another pointer causes ACTION_DOWN to be sent to another window for the first time. Only one
* ACTION_OUTSIDE event is sent per gesture.
@@ -6111,6 +7481,60 @@
spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithEventId(notifyArgs.id)));
}
+/**
+ * When a device reports a DOWN event, which lands in a window that supports splits, and then the
+ * device then reports a POINTER_DOWN, which lands in the location of a non-existing window, then
+ * the previous window should receive this event and not be dropped.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, SingleDevicePointerDownEventRetentionWithoutWindowTarget) {
+ 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_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+ .build());
+
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN)));
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(200).y(200))
+ .build());
+
+ window->consumeMotionEvent(AllOf(WithMotionAction(POINTER_1_DOWN)));
+}
+
+/**
+ * When deviceA reports a DOWN event, which lands in a window that supports splits, and then deviceB
+ * also reports a DOWN event, which lands in the location of a non-existing window, then the
+ * previous window should receive deviceB's event and it should be dropped.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, SecondDeviceDownEventDroppedWithoutWindowTarget) {
+ 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 DeviceId deviceA = 9;
+ const DeviceId deviceB = 3;
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+ .deviceId(deviceA)
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(200))
+ .deviceId(deviceB)
+ .build());
+ window->assertNoEvents();
+}
+
class InputDispatcherFallbackKeyTest : public InputDispatcherTest {
protected:
std::shared_ptr<FakeApplicationHandle> mApp;
@@ -10901,7 +12325,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();
@@ -10959,6 +12384,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();
@@ -11423,7 +12925,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",
@@ -11493,4 +12996,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..a48c5a9 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -2907,7 +2907,7 @@
const auto initialGeneration = mDevice->getGeneration();
unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
InputReaderConfiguration::Change::DISPLAY_INFO);
- ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueId());
+ ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueIdByPort());
ASSERT_GT(mDevice->getGeneration(), initialGeneration);
ASSERT_EQ(mDevice->getDeviceInfo().getAssociatedDisplayId(), SECONDARY_DISPLAY_ID);
}
@@ -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/InputTraceSession.cpp b/services/inputflinger/tests/InputTraceSession.cpp
new file mode 100644
index 0000000..32acb5f
--- /dev/null
+++ b/services/inputflinger/tests/InputTraceSession.cpp
@@ -0,0 +1,209 @@
+/*
+ * 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 "InputTraceSession.h"
+
+#include <NotifyArgsBuilders.h>
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <input/PrintTools.h>
+
+#include <utility>
+
+namespace android {
+
+using perfetto::protos::pbzero::AndroidInputEvent;
+using perfetto::protos::pbzero::AndroidInputEventConfig;
+using perfetto::protos::pbzero::AndroidKeyEvent;
+using perfetto::protos::pbzero::AndroidMotionEvent;
+using perfetto::protos::pbzero::AndroidWindowInputDispatchEvent;
+
+// These operator<< definitions must be in the global namespace for them to be accessible to the
+// GTEST library. They cannot be in the anonymous namespace.
+static std::ostream& operator<<(std::ostream& out,
+ const std::variant<KeyEvent, MotionEvent>& event) {
+ std::visit([&](const auto& e) { out << e; }, event);
+ return out;
+}
+
+static std::ostream& operator<<(std::ostream& out,
+ const InputTraceSession::WindowDispatchEvent& event) {
+ out << "Window dispatch to windowId: " << event.window->getId() << ", event: " << event.event;
+ return out;
+}
+
+namespace {
+
+inline uint32_t getId(const std::variant<KeyEvent, MotionEvent>& event) {
+ return std::visit([&](const auto& e) { return e.getId(); }, event);
+}
+
+std::unique_ptr<perfetto::TracingSession> startTrace(
+ const std::function<void(protozero::HeapBuffered<AndroidInputEventConfig>&)>& configure) {
+ protozero::HeapBuffered<AndroidInputEventConfig> inputEventConfig{};
+ configure(inputEventConfig);
+
+ perfetto::TraceConfig config;
+ config.add_buffers()->set_size_kb(1024); // Record up to 1 MiB.
+ auto* dataSourceConfig = config.add_data_sources()->mutable_config();
+ dataSourceConfig->set_name("android.input.inputevent");
+ dataSourceConfig->set_android_input_event_config_raw(inputEventConfig.SerializeAsString());
+
+ std::unique_ptr<perfetto::TracingSession> tracingSession(perfetto::Tracing::NewTrace());
+ tracingSession->Setup(config);
+ tracingSession->StartBlocking();
+ return tracingSession;
+}
+
+std::string stopTrace(std::unique_ptr<perfetto::TracingSession> tracingSession) {
+ tracingSession->StopBlocking();
+ std::vector<char> traceChars(tracingSession->ReadTraceBlocking());
+ return {traceChars.data(), traceChars.size()};
+}
+
+// Decodes the trace, and returns all of the traced input events, and whether they were each
+// traced as a redacted event.
+auto decodeTrace(const std::string& rawTrace) {
+ using namespace perfetto::protos::pbzero;
+
+ ArrayMap<AndroidMotionEvent::Decoder, bool /*redacted*/> tracedMotions;
+ ArrayMap<AndroidKeyEvent::Decoder, bool /*redacted*/> tracedKeys;
+ ArrayMap<AndroidWindowInputDispatchEvent::Decoder, bool /*redacted*/> tracedWindowDispatches;
+
+ Trace::Decoder trace{rawTrace};
+ if (trace.has_packet()) {
+ auto it = trace.packet();
+ while (it) {
+ TracePacket::Decoder packet{it->as_bytes()};
+ if (packet.has_android_input_event()) {
+ AndroidInputEvent::Decoder event{packet.android_input_event()};
+ if (event.has_dispatcher_motion_event()) {
+ tracedMotions.emplace_back(event.dispatcher_motion_event(),
+ /*redacted=*/false);
+ }
+ if (event.has_dispatcher_motion_event_redacted()) {
+ tracedMotions.emplace_back(event.dispatcher_motion_event_redacted(),
+ /*redacted=*/true);
+ }
+ if (event.has_dispatcher_key_event()) {
+ tracedKeys.emplace_back(event.dispatcher_key_event(),
+ /*redacted=*/false);
+ }
+ if (event.has_dispatcher_key_event_redacted()) {
+ tracedKeys.emplace_back(event.dispatcher_key_event_redacted(),
+ /*redacted=*/true);
+ }
+ if (event.has_dispatcher_window_dispatch_event()) {
+ tracedWindowDispatches.emplace_back(event.dispatcher_window_dispatch_event(),
+ /*redacted=*/false);
+ }
+ if (event.has_dispatcher_window_dispatch_event_redacted()) {
+ tracedWindowDispatches
+ .emplace_back(event.dispatcher_window_dispatch_event_redacted(),
+ /*redacted=*/true);
+ }
+ }
+ it++;
+ }
+ }
+ return std::tuple{std::move(tracedMotions), std::move(tracedKeys),
+ std::move(tracedWindowDispatches)};
+}
+
+bool eventMatches(const MotionEvent& expected, const AndroidMotionEvent::Decoder& traced) {
+ return static_cast<uint32_t>(expected.getId()) == traced.event_id();
+}
+
+bool eventMatches(const KeyEvent& expected, const AndroidKeyEvent::Decoder& traced) {
+ return static_cast<uint32_t>(expected.getId()) == traced.event_id();
+}
+
+bool eventMatches(const InputTraceSession::WindowDispatchEvent& expected,
+ const AndroidWindowInputDispatchEvent::Decoder& traced) {
+ return static_cast<uint32_t>(getId(expected.event)) == traced.event_id() &&
+ expected.window->getId() == traced.window_id();
+}
+
+template <typename ExpectedEvents, typename TracedEvents>
+void verifyExpectedEventsTraced(const ExpectedEvents& expectedEvents,
+ const TracedEvents& tracedEvents, std::string_view name) {
+ uint32_t totalExpectedCount = 0;
+
+ for (const auto& [expectedEvent, expectedLevel] : expectedEvents) {
+ int32_t totalMatchCount = 0;
+ int32_t redactedMatchCount = 0;
+ for (const auto& [tracedEvent, isRedacted] : tracedEvents) {
+ if (eventMatches(expectedEvent, tracedEvent)) {
+ totalMatchCount++;
+ if (isRedacted) {
+ redactedMatchCount++;
+ }
+ }
+ }
+ switch (expectedLevel) {
+ case Level::NONE:
+ ASSERT_EQ(totalMatchCount, 0) << "Event should not be traced, but it was traced"
+ << "\n\tExpected event: " << expectedEvent;
+ break;
+ case Level::REDACTED:
+ case Level::COMPLETE:
+ ASSERT_EQ(totalMatchCount, 1)
+ << "Event should match exactly one traced event, but it matched: "
+ << totalMatchCount << "\n\tExpected event: " << expectedEvent;
+ ASSERT_EQ(redactedMatchCount, expectedLevel == Level::REDACTED ? 1 : 0);
+ totalExpectedCount++;
+ break;
+ }
+ }
+
+ ASSERT_EQ(tracedEvents.size(), totalExpectedCount)
+ << "The number of traced " << name
+ << " events does not exactly match the number of expected events";
+}
+
+} // namespace
+
+InputTraceSession::InputTraceSession(
+ std::function<void(protozero::HeapBuffered<AndroidInputEventConfig>&)> configure)
+ : mPerfettoSession(startTrace(std::move(configure))) {}
+
+InputTraceSession::~InputTraceSession() {
+ const auto rawTrace = stopTrace(std::move(mPerfettoSession));
+ verifyExpectations(rawTrace);
+}
+
+void InputTraceSession::expectMotionTraced(Level level, const MotionEvent& event) {
+ mExpectedMotions.emplace_back(event, level);
+}
+
+void InputTraceSession::expectKeyTraced(Level level, const KeyEvent& event) {
+ mExpectedKeys.emplace_back(event, level);
+}
+
+void InputTraceSession::expectDispatchTraced(Level level, const WindowDispatchEvent& event) {
+ mExpectedWindowDispatches.emplace_back(event, level);
+}
+
+void InputTraceSession::verifyExpectations(const std::string& rawTrace) {
+ auto [tracedMotions, tracedKeys, tracedWindowDispatches] = decodeTrace(rawTrace);
+
+ verifyExpectedEventsTraced(mExpectedMotions, tracedMotions, "motion");
+ verifyExpectedEventsTraced(mExpectedKeys, tracedKeys, "key");
+ verifyExpectedEventsTraced(mExpectedWindowDispatches, tracedWindowDispatches,
+ "window dispatch");
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputTraceSession.h b/services/inputflinger/tests/InputTraceSession.h
new file mode 100644
index 0000000..ed20bc8
--- /dev/null
+++ b/services/inputflinger/tests/InputTraceSession.h
@@ -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.
+ */
+
+#pragma once
+
+#include "FakeWindows.h"
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <perfetto/config/android/android_input_event_config.pbzero.h>
+#include <perfetto/trace/android/android_input_event.pbzero.h>
+#include <perfetto/trace/trace.pbzero.h>
+#include <perfetto/tracing.h>
+#include <variant>
+#include <vector>
+
+namespace android {
+
+/**
+ * Tracing level constants used for adding expectations to the InputTraceSession.
+ */
+enum class Level {
+ NONE,
+ REDACTED,
+ COMPLETE,
+};
+
+template <typename K, typename V>
+using ArrayMap = std::vector<std::pair<K, V>>;
+
+/**
+ * A scoped representation of a tracing session that is used to make assertions on the trace.
+ *
+ * When the trace session is created, an "android.input.inputevent" trace will be started
+ * synchronously with the given configuration. While the trace is ongoing, the caller must
+ * specify the events that are expected to be in the trace using the expect* methods.
+ *
+ * When the session is destroyed, the trace is stopped synchronously, and all expectations will
+ * be verified using the gtest framework. This acts as a strict verifier, where the verification
+ * will fail both if an expected event does not show up in the trace and if there is an extra
+ * event in the trace that was not expected. Ordering is NOT verified for any events.
+ */
+class InputTraceSession {
+public:
+ explicit InputTraceSession(
+ std::function<void(
+ protozero::HeapBuffered<perfetto::protos::pbzero::AndroidInputEventConfig>&)>
+ configure);
+
+ ~InputTraceSession();
+
+ void expectMotionTraced(Level level, const MotionEvent& event);
+
+ void expectKeyTraced(Level level, const KeyEvent& event);
+
+ struct WindowDispatchEvent {
+ std::variant<KeyEvent, MotionEvent> event;
+ sp<FakeWindowHandle> window;
+ };
+ void expectDispatchTraced(Level level, const WindowDispatchEvent& event);
+
+private:
+ std::unique_ptr<perfetto::TracingSession> mPerfettoSession;
+ ArrayMap<WindowDispatchEvent, Level> mExpectedWindowDispatches;
+ ArrayMap<MotionEvent, Level> mExpectedMotions;
+ ArrayMap<KeyEvent, Level> mExpectedKeys;
+
+ void verifyExpectations(const std::string& rawTrace);
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputTracingTest.cpp b/services/inputflinger/tests/InputTracingTest.cpp
new file mode 100644
index 0000000..23fa045
--- /dev/null
+++ b/services/inputflinger/tests/InputTracingTest.cpp
@@ -0,0 +1,734 @@
+/*
+ * 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 "../InputCommonConverter.h"
+#include "../dispatcher/InputDispatcher.h"
+#include "../dispatcher/trace/InputTracingPerfettoBackend.h"
+#include "../dispatcher/trace/ThreadedBackend.h"
+#include "FakeApplicationHandle.h"
+#include "FakeInputDispatcherPolicy.h"
+#include "FakeWindows.h"
+#include "InputTraceSession.h"
+#include "TestEventMatchers.h"
+
+#include <NotifyArgsBuilders.h>
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <perfetto/trace/android/android_input_event.pbzero.h>
+#include <perfetto/trace/trace.pbzero.h>
+#include <private/android_filesystem_config.h>
+#include <map>
+#include <vector>
+
+namespace android::inputdispatcher::trace {
+
+using perfetto::protos::pbzero::AndroidInputEventConfig;
+
+namespace {
+
+constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
+
+// Ensure common actions are interchangeable between keys and motions for convenience.
+static_assert(static_cast<int32_t>(AMOTION_EVENT_ACTION_DOWN) ==
+ static_cast<int32_t>(AKEY_EVENT_ACTION_DOWN));
+static_assert(static_cast<int32_t>(AMOTION_EVENT_ACTION_UP) ==
+ static_cast<int32_t>(AKEY_EVENT_ACTION_UP));
+constexpr int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN;
+constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE;
+constexpr int32_t ACTION_UP = AMOTION_EVENT_ACTION_UP;
+constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL;
+
+constexpr gui::Pid PID{1};
+
+constexpr gui::Uid ALLOWED_UID_1{10012};
+constexpr gui::Uid ALLOWED_UID_2{10013};
+constexpr gui::Uid DISALLOWED_UID_1{1};
+constexpr gui::Uid DISALLOWED_UID_2{99};
+constexpr gui::Uid UNLISTED_UID{12345};
+
+const std::string ALLOWED_PKG_1{"allowed.pkg.1"};
+const std::string ALLOWED_PKG_2{"allowed.pkg.2"};
+const std::string DISALLOWED_PKG_1{"disallowed.pkg.1"};
+const std::string DISALLOWED_PKG_2{"disallowed.pkg.2"};
+
+const std::shared_ptr<FakeApplicationHandle> APP = std::make_shared<FakeApplicationHandle>();
+
+} // namespace
+
+// --- InputTracingTest ---
+
+class InputTracingTest : public testing::Test {
+protected:
+ std::unique_ptr<FakeInputDispatcherPolicy> mFakePolicy;
+ std::unique_ptr<InputDispatcher> mDispatcher;
+
+ void SetUp() override {
+ impl::PerfettoBackend::sUseInProcessBackendForTest = true;
+
+ mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>();
+ mFakePolicy->addPackageUidMapping(ALLOWED_PKG_1, ALLOWED_UID_1);
+ mFakePolicy->addPackageUidMapping(ALLOWED_PKG_2, ALLOWED_UID_2);
+ mFakePolicy->addPackageUidMapping(DISALLOWED_PKG_1, DISALLOWED_UID_1);
+ mFakePolicy->addPackageUidMapping(DISALLOWED_PKG_2, DISALLOWED_UID_2);
+
+ auto tracingBackend = std::make_unique<impl::ThreadedBackend<impl::PerfettoBackend>>(
+ impl::PerfettoBackend([this](const auto& pkg) {
+ return static_cast<InputDispatcherPolicyInterface&>(*mFakePolicy)
+ .getPackageUid(pkg);
+ }));
+ mRequestTracerIdle = tracingBackend->getIdleWaiterForTesting();
+ mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy, std::move(tracingBackend));
+
+ mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
+ ASSERT_EQ(OK, mDispatcher->start());
+ }
+
+ void TearDown() override {
+ ASSERT_EQ(OK, mDispatcher->stop());
+ mDispatcher.reset();
+ mFakePolicy.reset();
+ }
+
+ void waitForTracerIdle() {
+ mDispatcher->waitForIdle();
+ mRequestTracerIdle();
+ }
+
+ void setFocusedWindow(const sp<gui::WindowInfoHandle>& window) {
+ gui::FocusRequest request;
+ request.token = window->getToken();
+ request.windowName = window->getName();
+ request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
+ request.displayId = window->getInfo()->displayId;
+ mDispatcher->setFocusedWindow(request);
+ }
+
+ void tapAndExpect(const std::vector<const sp<FakeWindowHandle>>& windows,
+ Level inboundTraceLevel, Level dispatchTraceLevel, InputTraceSession& s) {
+ const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+ .build();
+ mDispatcher->notifyMotion(down);
+ s.expectMotionTraced(inboundTraceLevel, toMotionEvent(down));
+ for (const auto& window : windows) {
+ auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+ s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+ }
+
+ const auto up = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+ .build();
+ mDispatcher->notifyMotion(up);
+ s.expectMotionTraced(inboundTraceLevel, toMotionEvent(up));
+ for (const auto& window : windows) {
+ auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_UP));
+ s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+ }
+ }
+
+ void keypressAndExpect(const std::vector<const sp<FakeWindowHandle>>& windows,
+ Level inboundTraceLevel, Level dispatchTraceLevel,
+ InputTraceSession& s) {
+ const auto down = KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build();
+ mDispatcher->notifyKey(down);
+ s.expectKeyTraced(inboundTraceLevel, toKeyEvent(down));
+ for (const auto& window : windows) {
+ auto consumed = window->consumeKeyEvent(WithKeyAction(ACTION_DOWN));
+ s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+ }
+
+ const auto up = KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).build();
+ mDispatcher->notifyKey(up);
+ s.expectKeyTraced(inboundTraceLevel, toKeyEvent(up));
+ for (const auto& window : windows) {
+ auto consumed = window->consumeKeyEvent(WithKeyAction(ACTION_UP));
+ s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+ }
+ }
+
+private:
+ std::function<void()> mRequestTracerIdle;
+};
+
+TEST_F(InputTracingTest, EmptyConfigTracesNothing) {
+ InputTraceSession s{[](auto& config) {}};
+
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ setFocusedWindow(window);
+ window->consumeFocusEvent(true);
+
+ tapAndExpect({window}, Level::NONE, Level::NONE, s);
+ keypressAndExpect({window}, Level::NONE, Level::NONE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, TraceAll) {
+ InputTraceSession s{
+ [](auto& config) { config->set_mode(AndroidInputEventConfig::TRACE_MODE_TRACE_ALL); }};
+
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ setFocusedWindow(window);
+ window->consumeFocusEvent(true);
+
+ tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+ keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, NoRulesTracesNothing) {
+ InputTraceSession s{[](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ }};
+
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ setFocusedWindow(window);
+ window->consumeFocusEvent(true);
+
+ tapAndExpect({window}, Level::NONE, Level::NONE, s);
+ keypressAndExpect({window}, Level::NONE, Level::NONE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, EmptyRuleMatchesEverything) {
+ InputTraceSession s{[](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Match everything as COMPLETE
+ auto rule = config->add_rules();
+ rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+ }};
+
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ setFocusedWindow(window);
+ window->consumeFocusEvent(true);
+
+ tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+ keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, UnspecifiedTracelLevel) {
+ InputTraceSession s{[](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Match everything, trace level unspecified
+ auto rule = config->add_rules();
+ }};
+
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ setFocusedWindow(window);
+ window->consumeFocusEvent(true);
+
+ // Event is not traced by default if trace level is unspecified
+ tapAndExpect({window}, Level::NONE, Level::NONE, s);
+ keypressAndExpect({window}, Level::NONE, Level::NONE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchSecureWindow) {
+ InputTraceSession s{[](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Match secure windows as COMPLETE
+ auto rule = config->add_rules();
+ rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+ rule->set_match_secure(true);
+ }};
+
+ // Add a normal window and a spy window.
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ spy->setSpy(true);
+ spy->setTrustedOverlay(true);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ // Since neither are secure windows, events should not be traced.
+ tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+ // Events should be matched as secure if any of the target windows is marked as secure.
+ spy->setSecure(true);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+ tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ spy->setSecure(false);
+ window->setSecure(true);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+ tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ spy->setSecure(true);
+ window->setSecure(true);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+ tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ spy->setSecure(false);
+ window->setSecure(false);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+ tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchImeConnectionActive) {
+ InputTraceSession s{[](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Match IME Connection Active as COMPLETE
+ auto rule = config->add_rules();
+ rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+ rule->set_match_ime_connection_active(true);
+ }};
+
+ // Add a normal window and a spy window.
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ spy->setSpy(true);
+ spy->setTrustedOverlay(true);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ // Since IME connection is not active, events should not be traced.
+ tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+ mDispatcher->setInputMethodConnectionIsActive(true);
+ tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ mDispatcher->setInputMethodConnectionIsActive(false);
+ tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchAllPackages) {
+ InputTraceSession s{[](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Match all package as COMPLETE
+ auto rule = config->add_rules();
+ rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+ rule->add_match_all_packages(ALLOWED_PKG_1);
+ rule->add_match_all_packages(ALLOWED_PKG_2);
+ }};
+
+ // All windows are allowlisted.
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ window->setOwnerInfo(PID, ALLOWED_UID_1);
+ auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ spy->setOwnerInfo(PID, ALLOWED_UID_2);
+ spy->setSpy(true);
+ spy->setTrustedOverlay(true);
+ auto systemSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ systemSpy->setOwnerInfo(PID, gui::Uid{AID_SYSTEM});
+ systemSpy->setSpy(true);
+ systemSpy->setTrustedOverlay(true);
+ mDispatcher->onWindowInfosChanged(
+ {{*systemSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({systemSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ // Add a disallowed spy. This will result in the event not being traced for all windows.
+ auto disallowedSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ disallowedSpy->setOwnerInfo(PID, DISALLOWED_UID_1);
+ disallowedSpy->setSpy(true);
+ disallowedSpy->setTrustedOverlay(true);
+ mDispatcher->onWindowInfosChanged({{*systemSpy->getInfo(), *spy->getInfo(),
+ *disallowedSpy->getInfo(), *window->getInfo()},
+ {},
+ 0,
+ 0});
+
+ tapAndExpect({systemSpy, spy, disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+ // Change the owner of the disallowed spy to one for which we don't have a package mapping.
+ disallowedSpy->setOwnerInfo(PID, UNLISTED_UID);
+ mDispatcher->onWindowInfosChanged({{*systemSpy->getInfo(), *spy->getInfo(),
+ *disallowedSpy->getInfo(), *window->getInfo()},
+ {},
+ 0,
+ 0});
+
+ tapAndExpect({systemSpy, spy, disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+ // Remove the disallowed spy. Events are traced again.
+ mDispatcher->onWindowInfosChanged(
+ {{*systemSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({systemSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchAnyPackages) {
+ InputTraceSession s{[](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Match any package as COMPLETE
+ auto rule = config->add_rules();
+ rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+ rule->add_match_any_packages(ALLOWED_PKG_1);
+ rule->add_match_any_packages(ALLOWED_PKG_2);
+ }};
+
+ // Just a disallowed window. Events are not traced.
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ window->setOwnerInfo(PID, DISALLOWED_UID_1);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+ // Add a spy for which we don't have a package mapping. Events are still not traced.
+ auto disallowedSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ disallowedSpy->setOwnerInfo(PID, UNLISTED_UID);
+ disallowedSpy->setSpy(true);
+ disallowedSpy->setTrustedOverlay(true);
+ mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+ // Add an allowed spy. Events are now traced for all packages.
+ auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ spy->setOwnerInfo(PID, ALLOWED_UID_1);
+ spy->setSpy(true);
+ spy->setTrustedOverlay(true);
+ mDispatcher->onWindowInfosChanged(
+ {{*disallowedSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({disallowedSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ // Add another disallowed spy. Events are still traced.
+ auto disallowedSpy2 = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ disallowedSpy2->setOwnerInfo(PID, DISALLOWED_UID_2);
+ disallowedSpy2->setSpy(true);
+ disallowedSpy2->setTrustedOverlay(true);
+ mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *disallowedSpy2->getInfo(),
+ *spy->getInfo(), *window->getInfo()},
+ {},
+ 0,
+ 0});
+
+ tapAndExpect({disallowedSpy, disallowedSpy2, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MultipleMatchersInOneRule) {
+ InputTraceSession s{[](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Match all of the following conditions as COMPLETE
+ auto rule = config->add_rules();
+ rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+ rule->add_match_all_packages(ALLOWED_PKG_1);
+ rule->add_match_all_packages(ALLOWED_PKG_2);
+ rule->add_match_any_packages(ALLOWED_PKG_1);
+ rule->add_match_any_packages(DISALLOWED_PKG_1);
+ rule->set_match_secure(false);
+ rule->set_match_ime_connection_active(false);
+ }};
+
+ // A single window into an allowed UID. Matches all matchers.
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ window->setOwnerInfo(PID, ALLOWED_UID_1);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ // Secure window does not match.
+ window->setSecure(true);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+ // IME Connection Active does not match.
+ window->setSecure(false);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ mDispatcher->setInputMethodConnectionIsActive(true);
+
+ tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+ // Event going to DISALLOWED_PKG_1 does not match because it's not listed in match_all_packages.
+ mDispatcher->setInputMethodConnectionIsActive(false);
+ auto disallowedSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ disallowedSpy->setOwnerInfo(PID, DISALLOWED_UID_1);
+ disallowedSpy->setSpy(true);
+ disallowedSpy->setTrustedOverlay(true);
+ mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+ // Event going to ALLOWED_PKG_1 does not match because it's not listed in match_any_packages.
+ window->setOwnerInfo(PID, ALLOWED_UID_2);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+ // All conditions match.
+ auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ spy->setOwnerInfo(PID, ALLOWED_UID_1);
+ spy->setSpy(true);
+ spy->setTrustedOverlay(true);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MultipleRulesMatchInOrder) {
+ InputTraceSession s{[](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Don't trace secure events
+ auto rule1 = config->add_rules();
+ rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_NONE);
+ rule1->set_match_secure(true);
+ // Rule: Trace matched packages as COMPLETE when IME inactive
+ auto rule2 = config->add_rules();
+ rule2->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+ rule2->add_match_all_packages(ALLOWED_PKG_1);
+ rule2->add_match_all_packages(ALLOWED_PKG_2);
+ rule2->set_match_ime_connection_active(false);
+ // Rule: Trace the rest of the events as REDACTED
+ auto rule3 = config->add_rules();
+ rule3->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+ }};
+
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ window->setOwnerInfo(PID, ALLOWED_UID_1);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ // Verify that the first rule that matches in the order that they are specified is the
+ // one that applies to the event.
+ mDispatcher->setInputMethodConnectionIsActive(true);
+ tapAndExpect({window}, Level::REDACTED, Level::REDACTED, s);
+
+ mDispatcher->setInputMethodConnectionIsActive(false);
+ auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ spy->setOwnerInfo(PID, ALLOWED_UID_2);
+ spy->setSpy(true);
+ spy->setTrustedOverlay(true);
+ spy->setSecure(true);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+ spy->setSecure(false);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ spy->setOwnerInfo(PID, DISALLOWED_UID_1);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({spy, window}, Level::REDACTED, Level::REDACTED, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, TraceInboundEvents) {
+ InputTraceSession s{[](auto& config) {
+ // Only trace inbounds events - don't trace window dispatch
+ config->set_trace_dispatcher_input_events(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Trace everything as REDACTED
+ auto rule1 = config->add_rules();
+ rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+ }};
+
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ window->setOwnerInfo(PID, ALLOWED_UID_1);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ // Only the inbound events are traced. No dispatch events are traced.
+ tapAndExpect({window}, Level::REDACTED, Level::NONE, s);
+
+ // Notify a down event, which should be traced.
+ const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+ .build();
+ s.expectMotionTraced(Level::REDACTED, toMotionEvent(down));
+ mDispatcher->notifyMotion(down);
+ auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+ s.expectDispatchTraced(Level::NONE, {*consumed, window});
+
+ // Force a cancel event to be synthesized. This should not be traced, because only inbound
+ // events are requested.
+ mDispatcher->cancelCurrentTouch();
+ consumed = window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+ s.expectMotionTraced(Level::NONE, *consumed);
+ s.expectDispatchTraced(Level::NONE, {*consumed, window});
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, TraceWindowDispatch) {
+ InputTraceSession s{[](auto& config) {
+ // Only trace window dispatch - don't trace event details
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Trace everything as REDACTED
+ auto rule1 = config->add_rules();
+ rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+ }};
+
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ window->setOwnerInfo(PID, ALLOWED_UID_1);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ // Only dispatch events are traced. No inbound events are traced.
+ tapAndExpect({window}, Level::NONE, Level::REDACTED, s);
+
+ // Notify a down event; the dispatch should be traced.
+ const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+ .build();
+ s.expectMotionTraced(Level::NONE, toMotionEvent(down));
+ mDispatcher->notifyMotion(down);
+ auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+ s.expectDispatchTraced(Level::REDACTED, {*consumed, window});
+
+ // Force a cancel event to be synthesized. All events that are dispatched should be traced.
+ mDispatcher->cancelCurrentTouch();
+ consumed = window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+ s.expectMotionTraced(Level::NONE, *consumed);
+ s.expectDispatchTraced(Level::REDACTED, {*consumed, window});
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, SimultaneousTracingSessions) {
+ auto s1 = std::make_unique<InputTraceSession>(
+ [](auto& config) { config->set_mode(AndroidInputEventConfig::TRACE_MODE_TRACE_ALL); });
+
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ setFocusedWindow(window);
+ window->consumeFocusEvent(true);
+
+ tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+ keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+
+ auto s2 = std::make_unique<InputTraceSession>([](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Trace all events as REDACTED when IME inactive
+ auto rule = config->add_rules();
+ rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+ rule->set_match_ime_connection_active(false);
+ });
+
+ auto s3 = std::make_unique<InputTraceSession>([](auto& config) {
+ // Only trace window dispatch
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Trace non-secure events as COMPLETE
+ auto rule = config->add_rules();
+ rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+ rule->set_match_secure(false);
+ });
+
+ // Down event should be recorded on all traces.
+ const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+ .build();
+ mDispatcher->notifyMotion(down);
+ s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(down));
+ s2->expectMotionTraced(Level::REDACTED, toMotionEvent(down));
+ s3->expectMotionTraced(Level::NONE, toMotionEvent(down));
+ auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+ s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+ s2->expectDispatchTraced(Level::REDACTED, {*consumed, window});
+ s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+
+ // Move event when IME is active.
+ mDispatcher->setInputMethodConnectionIsActive(true);
+ const auto move1 = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+ .build();
+ mDispatcher->notifyMotion(move1);
+ s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(move1));
+ s2->expectMotionTraced(Level::NONE, toMotionEvent(move1));
+ s3->expectMotionTraced(Level::NONE, toMotionEvent(move1));
+ consumed = window->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+ s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+ s2->expectDispatchTraced(Level::NONE, {*consumed, window});
+ s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+
+ // Move event after window became secure.
+ mDispatcher->setInputMethodConnectionIsActive(false);
+ window->setSecure(true);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ const auto move2 = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+ .build();
+ mDispatcher->notifyMotion(move2);
+ s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(move2));
+ s2->expectMotionTraced(Level::REDACTED, toMotionEvent(move2));
+ s3->expectMotionTraced(Level::NONE, toMotionEvent(move2));
+ consumed = window->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+ s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+ s2->expectDispatchTraced(Level::REDACTED, {*consumed, window});
+ s3->expectDispatchTraced(Level::NONE, {*consumed, window});
+
+ waitForTracerIdle();
+ s2.reset();
+
+ // Up event.
+ window->setSecure(false);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ const auto up = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+ .build();
+ mDispatcher->notifyMotion(up);
+ s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(up));
+ s3->expectMotionTraced(Level::NONE, toMotionEvent(up));
+ consumed = window->consumeMotionEvent(WithMotionAction(ACTION_UP));
+ s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+ s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+
+ waitForTracerIdle();
+ s3.reset();
+
+ tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+ keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+
+ waitForTracerIdle();
+ s1.reset();
+}
+
+} // namespace android::inputdispatcher::trace
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/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp
index dc86577..3446f58 100644
--- a/services/sensorservice/SensorEventConnection.cpp
+++ b/services/sensorservice/SensorEventConnection.cpp
@@ -461,16 +461,18 @@
// is pre-Q, still permit delivering events to the app even if permission isn't granted
// (since this permission was only introduced in Q)
if ((event.type == SENSOR_TYPE_STEP_COUNTER || event.type == SENSOR_TYPE_STEP_DETECTOR) &&
- mTargetSdk > 0 && mTargetSdk <= __ANDROID_API_P__) {
+ mTargetSdk > 0 && mTargetSdk <= __ANDROID_API_P__) {
+ success = true;
+ } else if (mUid == AID_SYSTEM) {
+ // Allow access if it is requested from system.
success = true;
} else {
int32_t sensorHandle = event.sensor;
String16 noteMsg("Sensor event (");
noteMsg.append(String16(mService->getSensorStringType(sensorHandle)));
noteMsg.append(String16(")"));
- int32_t appOpMode = mService->sAppOpsManager.noteOp(iter->second, mUid,
- mOpPackageName, mAttributionTag,
- noteMsg);
+ int32_t appOpMode = mService->sAppOpsManager.noteOp(iter->second, mUid, mOpPackageName,
+ mAttributionTag, noteMsg);
success = (appOpMode == AppOpsManager::MODE_ALLOWED);
}
}
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index e1c43c6..69e4309 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -2302,11 +2302,16 @@
// requirement to hold the AR permission to access Step Counter and Step Detector events
// was introduced.
canAccess = true;
+ } else if (IPCThreadState::self()->getCallingUid() == AID_SYSTEM) {
+ // Allow access if it is requested from system.
+ canAccess = true;
} else if (hasPermissionForSensor(sensor)) {
- // Ensure that the AppOp is allowed, or that there is no necessary app op for the sensor
+ // Ensure that the AppOp is allowed, or that there is no necessary app op
+ // for the sensor
if (opCode >= 0) {
- const int32_t appOpMode = sAppOpsManager.checkOp(opCode,
- IPCThreadState::self()->getCallingUid(), opPackageName);
+ const int32_t appOpMode =
+ sAppOpsManager.checkOp(opCode, IPCThreadState::self()->getCallingUid(),
+ opPackageName);
canAccess = (appOpMode == AppOpsManager::MODE_ALLOWED);
} else {
canAccess = true;
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/src/planner/LayerState.cpp b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
index 0e3fdbb..10dc927 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <common/FlagManager.h>
#include <compositionengine/impl/planner/LayerState.h>
namespace {
@@ -70,6 +71,10 @@
if (field->getField() == LayerStateField::Buffer) {
continue;
}
+ if (FlagManager::getInstance().cache_when_source_crop_layer_only_moved() &&
+ field->getField() == LayerStateField::SourceCrop) {
+ continue;
+ }
android::hashCombineSingleHashed(hash, field->getHash());
}
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 575a30e..4612117 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -126,6 +126,7 @@
const std::unordered_map<std::string, bool>&());
MOCK_CONST_METHOD1(dump, void(std::string&));
+ MOCK_CONST_METHOD1(dumpOverlayProperties, void(std::string&));
MOCK_CONST_METHOD0(getComposer, android::Hwc2::Composer*());
MOCK_METHOD(hal::HWDisplayId, getPrimaryHwcDisplayId, (), (const, override));
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..03758b3 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
@@ -18,6 +18,7 @@
#define LOG_TAG "LayerStateTest"
#include <aidl/android/hardware/graphics/common/BufferUsage.h>
+#include <common/include/common/test/FlagUtils.h>
#include <compositionengine/impl/OutputLayer.h>
#include <compositionengine/impl/planner/LayerState.h>
#include <compositionengine/mock/LayerFE.h>
@@ -26,6 +27,7 @@
#include <log/log.h>
#include "android/hardware_buffer.h"
+#include "com_android_graphics_surfaceflinger_flags.h"
#include "compositionengine/LayerFECompositionState.h"
#include <aidl/android/hardware/graphics/composer3/Composition.h>
@@ -304,6 +306,16 @@
EXPECT_EQ(Composition::CLIENT, mLayerState->getCompositionType());
}
+TEST_F(LayerStateTest, getHdrSdrRatio) {
+ OutputLayerCompositionState outputLayerCompositionState;
+ LayerFECompositionState layerFECompositionState;
+ layerFECompositionState.currentHdrSdrRatio = 2.f;
+ setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
+ layerFECompositionState);
+ mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+ EXPECT_EQ(2.f, mLayerState->getHdrSdrRatio());
+}
+
TEST_F(LayerStateTest, updateCompositionType) {
OutputLayerCompositionState outputLayerCompositionState;
LayerFECompositionState layerFECompositionState;
@@ -454,6 +466,9 @@
}
TEST_F(LayerStateTest, compareSourceCrop) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+ cache_when_source_crop_layer_only_moved,
+ false);
OutputLayerCompositionState outputLayerCompositionState;
outputLayerCompositionState.sourceCrop = sFloatRectOne;
LayerFECompositionState layerFECompositionState;
@@ -1033,6 +1048,47 @@
EXPECT_TRUE(otherLayerState->compare(*mLayerState));
}
+TEST_F(LayerStateTest, updateDimmingEnabled) {
+ OutputLayerCompositionState outputLayerCompositionState;
+ LayerFECompositionState layerFECompositionState;
+ layerFECompositionState.dimmingEnabled = true;
+ setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
+ layerFECompositionState);
+ mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+ EXPECT_TRUE(mLayerState->isDimmingEnabled());
+
+ mock::OutputLayer newOutputLayer;
+ sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+ LayerFECompositionState layerFECompositionStateTwo;
+ layerFECompositionStateTwo.dimmingEnabled = false;
+ setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
+ layerFECompositionStateTwo);
+ ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+ EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::DimmingEnabled), updates);
+ EXPECT_FALSE(mLayerState->isDimmingEnabled());
+}
+
+TEST_F(LayerStateTest, compareDimmingEnabled) {
+ OutputLayerCompositionState outputLayerCompositionState;
+ LayerFECompositionState layerFECompositionState;
+ layerFECompositionState.dimmingEnabled = true;
+ setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
+ layerFECompositionState);
+ mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+ mock::OutputLayer newOutputLayer;
+ sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+ LayerFECompositionState layerFECompositionStateTwo;
+ layerFECompositionStateTwo.dimmingEnabled = false;
+ setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
+ layerFECompositionStateTwo);
+ auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
+
+ verifyNonUniqueDifferingFields(*mLayerState, *otherLayerState, LayerStateField::DimmingEnabled);
+
+ EXPECT_TRUE(mLayerState->compare(*otherLayerState));
+ EXPECT_TRUE(otherLayerState->compare(*mLayerState));
+}
+
TEST_F(LayerStateTest, dumpDoesNotCrash) {
OutputLayerCompositionState outputLayerCompositionState;
LayerFECompositionState layerFECompositionState;
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
index 35d0ffb..a1210b4 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
@@ -18,6 +18,9 @@
#undef LOG_TAG
#define LOG_TAG "PredictorTest"
+#include <common/include/common/test/FlagUtils.h>
+#include "com_android_graphics_surfaceflinger_flags.h"
+
#include <compositionengine/impl/planner/Predictor.h>
#include <compositionengine/mock/LayerFE.h>
#include <compositionengine/mock/OutputLayer.h>
@@ -127,6 +130,9 @@
}
TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInSingleLayer) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+ cache_when_source_crop_layer_only_moved,
+ false);
mock::OutputLayer outputLayerOne;
sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -158,6 +164,9 @@
}
TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInMultiLayerStack) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+ cache_when_source_crop_layer_only_moved,
+ false);
mock::OutputLayer outputLayerOne;
sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -304,6 +313,9 @@
}
TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchMultipleApproximations) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+ cache_when_source_crop_layer_only_moved,
+ false);
mock::OutputLayer outputLayerOne;
sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -347,6 +359,9 @@
};
TEST_F(LayerStackTest, reorderingChangesNonBufferHash) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+ cache_when_source_crop_layer_only_moved,
+ false);
mock::OutputLayer outputLayerOne;
sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -467,6 +482,9 @@
}
TEST_F(PredictorTest, getPredictedPlan_recordCandidateAndRetrieveApproximateMatch) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+ cache_when_source_crop_layer_only_moved,
+ false);
mock::OutputLayer outputLayerOne;
sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -504,6 +522,9 @@
}
TEST_F(PredictorTest, recordMissedPlan_skipsApproximateMatch) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+ cache_when_source_crop_layer_only_moved,
+ false);
mock::OutputLayer outputLayerOne;
sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
OutputLayerCompositionState outputLayerCompositionStateOne{
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 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/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 776bcd3..21d49f8 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -77,9 +77,7 @@
using aidl::android::hardware::graphics::common::HdrConversionStrategy;
using aidl::android::hardware::graphics::composer3::Capability;
using aidl::android::hardware::graphics::composer3::DisplayCapability;
-using aidl::android::hardware::graphics::composer3::VrrConfig;
using namespace std::string_literals;
-namespace hal = android::hardware::graphics::composer::hal;
namespace android {
@@ -964,8 +962,45 @@
return mSupportedLayerGenericMetadata;
}
+void HWComposer::dumpOverlayProperties(std::string& result) const {
+ // dump overlay properties
+ result.append("OverlayProperties:\n");
+ base::StringAppendF(&result, "supportMixedColorSpaces: %d\n",
+ mOverlayProperties.supportMixedColorSpaces);
+ base::StringAppendF(&result, "SupportedBufferCombinations(%zu entries)\n",
+ mOverlayProperties.combinations.size());
+ for (const auto& combination : mOverlayProperties.combinations) {
+ result.append(" pixelFormats=\n");
+ for (const auto& pixelFormat : combination.pixelFormats) {
+ base::StringAppendF(&result, " %s (%d)\n",
+ decodePixelFormat(static_cast<PixelFormat>(pixelFormat)).c_str(),
+ static_cast<uint32_t>(pixelFormat));
+ }
+ result.append(" standards=\n");
+ for (const auto& standard : combination.standards) {
+ base::StringAppendF(&result, " %s (%d)\n",
+ decodeStandard(static_cast<android_dataspace>(standard)).c_str(),
+ static_cast<uint32_t>(standard));
+ }
+ result.append(" transfers=\n");
+ for (const auto& transfer : combination.transfers) {
+ base::StringAppendF(&result, " %s (%d)\n",
+ decodeTransferOnly(static_cast<uint32_t>(transfer)).c_str(),
+ static_cast<uint32_t>(transfer));
+ }
+ result.append(" ranges=\n");
+ for (const auto& range : combination.ranges) {
+ base::StringAppendF(&result, " %s (%d)\n",
+ decodeRangeOnly(static_cast<uint32_t>(range)).c_str(),
+ static_cast<uint32_t>(range));
+ }
+ result.append("\n");
+ }
+}
+
void HWComposer::dump(std::string& result) const {
result.append(mComposer->dumpDebugInfo());
+ dumpOverlayProperties(result);
}
std::optional<PhysicalDisplayId> HWComposer::toPhysicalDisplayId(
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 7fbbb1a..bc32cda 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -269,6 +269,8 @@
virtual void dump(std::string& out) const = 0;
+ virtual void dumpOverlayProperties(std::string& out) const = 0;
+
virtual Hwc2::Composer* getComposer() const = 0;
// Returns the first display connected at boot. Its connection via HWComposer::onHotplug,
@@ -468,6 +470,7 @@
// for debugging ----------------------------------------------------------
void dump(std::string& out) const override;
+ void dumpOverlayProperties(std::string& out) const override;
Hwc2::Composer* getComposer() const override { return mComposer.get(); }
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 80eee15..073bad3 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -3792,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/OWNERS b/services/surfaceflinger/OWNERS
index 0aee7d4..ffc1dd7 100644
--- a/services/surfaceflinger/OWNERS
+++ b/services/surfaceflinger/OWNERS
@@ -2,7 +2,6 @@
adyabr@google.com
alecmouri@google.com
-chaviw@google.com
domlaskowski@google.com
jreck@google.com
lpy@google.com
@@ -10,5 +9,6 @@
racarr@google.com
ramindani@google.com
rnlee@google.com
+sallyqi@google.com
scroggo@google.com
vishnun@google.com
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 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/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 1bc4ac2..5f17128 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -55,10 +55,10 @@
bool pendingModeChange, const LayerProps& props) {
lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
- mLastUpdatedTime = std::max(lastPresentTime, now);
*mLayerProps = props;
switch (updateType) {
case LayerUpdateType::AnimationTX:
+ mLastUpdatedTime = std::max(lastPresentTime, now);
mLastAnimationTime = std::max(lastPresentTime, now);
break;
case LayerUpdateType::SetFrameRate:
@@ -67,6 +67,7 @@
}
FALLTHROUGH_INTENDED;
case LayerUpdateType::Buffer:
+ mLastUpdatedTime = std::max(lastPresentTime, now);
FrameTimeData frameTime = {.presentTime = lastPresentTime,
.queueTime = mLastUpdatedTime,
.pendingModeChange = pendingModeChange,
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index c83d81f..005ec05 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -663,13 +663,7 @@
void Scheduler::recordLayerHistory(int32_t id, const LayerProps& layerProps, nsecs_t presentTime,
nsecs_t now, LayerHistory::LayerUpdateType updateType) {
- 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()))) {
+ if (pacesetterSelectorPtr()->canSwitch()) {
mLayerHistory.record(id, layerProps, presentTime, now, updateType);
}
}
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/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 4474355..21f1cb3 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -72,6 +72,7 @@
#include <gui/TraceUtils.h>
#include <hidl/ServiceManagement.h>
#include <layerproto/LayerProtoParser.h>
+#include <linux/sched/types.h>
#include <log/log.h>
#include <private/android_filesystem_config.h>
#include <private/gui/SyncFeatures.h>
@@ -2073,10 +2074,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);
}
@@ -3171,7 +3179,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",
@@ -3230,7 +3239,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;
}
@@ -4143,7 +4152,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())
@@ -4848,6 +4857,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(),
@@ -4870,97 +4881,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;
}
@@ -5181,7 +5202,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});
@@ -5226,17 +5253,19 @@
desiredPresentTime, isAutoTimestamp,
postTime, transactionId);
}
- if ((flags & eAnimation) && resolvedState.state.surface) {
- if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) {
- const auto layerProps = scheduler::LayerProps{
- .visible = layer->isVisible(),
- .bounds = layer->getBounds(),
- .transform = layer->getTransform(),
- .setFrameRateVote = layer->getFrameRateForLayerTree(),
- .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(),
- .isFrontBuffered = layer->isFrontBuffered(),
- };
- layer->recordLayerHistoryAnimationTx(layerProps, now);
+ if (!mLayerLifecycleManagerEnabled) {
+ if ((flags & eAnimation) && resolvedState.state.surface) {
+ if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) {
+ const auto layerProps = scheduler::LayerProps{
+ .visible = layer->isVisible(),
+ .bounds = layer->getBounds(),
+ .transform = layer->getTransform(),
+ .setFrameRateVote = layer->getFrameRateForLayerTree(),
+ .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(),
+ .isFrontBuffered = layer->isFrontBuffered(),
+ };
+ layer->recordLayerHistoryAnimationTx(layerProps, now);
+ }
}
}
}
@@ -6023,7 +6052,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);
@@ -6675,7 +6708,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) {
@@ -6724,17 +6761,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");
}
}
@@ -7770,20 +7808,6 @@
return NO_ERROR;
}
- // Currently, there is no wrapper in bionic: b/183240349.
- struct sched_attr {
- uint32_t size;
- uint32_t sched_policy;
- uint64_t sched_flags;
- int32_t sched_nice;
- uint32_t sched_priority;
- uint64_t sched_runtime;
- uint64_t sched_deadline;
- uint64_t sched_period;
- uint32_t sched_util_min;
- uint32_t sched_util_max;
- };
-
sched_attr attr = {};
attr.size = sizeof(attr);
@@ -7838,17 +7862,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;
}
@@ -7861,6 +7887,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;
}
@@ -7877,7 +7904,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;
}
@@ -7915,6 +7942,7 @@
const auto display = getDisplayDeviceLocked(displayId);
if (!display) {
+ ALOGD("Unable to find display device for captureDisplay");
invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
return;
}
@@ -7932,7 +7960,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;
}
@@ -7979,6 +8007,7 @@
status_t validate = validateScreenshotPermissions(args);
if (validate != OK) {
+ ALOGD("Permission denied to captureLayers");
invokeScreenCaptureError(validate, captureListener);
return;
}
@@ -7990,7 +8019,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;
}
@@ -8000,7 +8029,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;
}
@@ -8019,6 +8048,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;
}
@@ -8029,7 +8059,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;
}
@@ -8038,13 +8068,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) {
@@ -8103,7 +8134,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;
}
@@ -8295,10 +8326,12 @@
const frontend::LayerSnapshot* snapshot = mLayerLifecycleManagerEnabled
? mLayerSnapshotBuilder.getSnapshot(parent->sequence)
: parent->getLayerSnapshot();
- display = findDisplay([layerStack =
- snapshot->outputFilter.layerStack](const auto& display) {
- return display.getLayerStack() == layerStack;
- }).get();
+ if (snapshot) {
+ display = findDisplay([layerStack = snapshot->outputFilter.layerStack](
+ const auto& display) {
+ return display.getLayerStack() == layerStack;
+ }).get();
+ }
}
if (display == nullptr) {
@@ -9014,6 +9047,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;
}
@@ -9214,7 +9249,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) {
@@ -9275,11 +9311,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;
}
@@ -9314,7 +9351,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);
@@ -9356,7 +9394,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,
@@ -9867,6 +9905,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);
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 4a44be6..44fa806 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -770,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);
@@ -803,7 +806,7 @@
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);
@@ -813,8 +816,8 @@
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);
@@ -831,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.
@@ -847,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);
@@ -1136,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);
@@ -1156,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;
@@ -1204,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();
@@ -1482,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
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 d9334d6..5a54f7d 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -140,6 +140,9 @@
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
@@ -229,6 +232,8 @@
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 819e587..0c1f9fb 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -78,6 +78,8 @@
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 a8fd6b7..4a60987 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -21,12 +21,16 @@
}
} # 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/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/OWNERS b/services/surfaceflinger/tests/OWNERS
new file mode 100644
index 0000000..1878326
--- /dev/null
+++ b/services/surfaceflinger/tests/OWNERS
@@ -0,0 +1,5 @@
+per-file HdrSdrRatioOverlay_test.cpp = set noparent
+per-file HdrSdrRatioOverlay_test.cpp = alecmouri@google.com, sallyqi@google.com, jreck@google.com
+
+per-file Layer* = set noparent
+per-file Layer* = pdwilliams@google.com, vishnun@google.com, melodymhsu@google.com
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/SurfaceFlinger_NotifyPowerBoostTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp
index 22b72f9..f2e2c8a 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp
@@ -33,24 +33,45 @@
TEST_F(DisplayTransactionTest, notifyPowerBoostNotifiesTouchEvent) {
using namespace std::chrono_literals;
+ std::mutex timerMutex;
+ std::condition_variable cv;
+
injectDefaultInternalDisplay([](FakeDisplayDeviceInjector&) {});
- mFlinger.scheduler()->replaceTouchTimer(100);
- std::this_thread::sleep_for(10ms); // wait for callback to be triggered
+ std::unique_lock lock(timerMutex);
+ bool didReset = false; // keeps track of what the most recent call was
+
+ auto waitForTimerReset = [&] { cv.wait_for(lock, 100ms, [&] { return didReset; }); };
+ auto waitForTimerExpired = [&] { cv.wait_for(lock, 100ms, [&] { return !didReset; }); };
+
+ // Add extra logic to unblock the test when the timer callbacks get called
+ mFlinger.scheduler()->replaceTouchTimer(10, [&](bool isReset) {
+ {
+ std::unique_lock lock(timerMutex); // guarantee we're waiting on the cv
+ didReset = isReset;
+ }
+ cv.notify_one(); // wake the cv
+ std::unique_lock lock(timerMutex); // guarantee we finished the cv logic
+ });
+
+ waitForTimerReset();
EXPECT_TRUE(mFlinger.scheduler()->isTouchActive()); // Starting timer activates touch
- std::this_thread::sleep_for(110ms); // wait for reset touch timer to expire and trigger callback
- EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
+ waitForTimerExpired();
+ EXPECT_FALSE(mFlinger.scheduler()->isTouchActive()); // Stopping timer deactivates touch
EXPECT_EQ(NO_ERROR, mFlinger.notifyPowerBoost(static_cast<int32_t>(Boost::CAMERA_SHOT)));
- std::this_thread::sleep_for(10ms); // wait for callback to maybe be triggered
- EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
- std::this_thread::sleep_for(110ms); // wait for reset touch timer to expire and trigger callback
+ EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
+ // Wait for the timer to start just in case
+ waitForTimerReset();
+ EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
+ // Wait for the timer to stop, again just in case
+ waitForTimerExpired();
EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
EXPECT_EQ(NO_ERROR, mFlinger.notifyPowerBoost(static_cast<int32_t>(Boost::INTERACTION)));
- std::this_thread::sleep_for(10ms); // wait for callback to be triggered.
+ waitForTimerReset();
EXPECT_TRUE(mFlinger.scheduler()->isTouchActive());
}
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 1472ebf..1e02c67 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -140,14 +140,25 @@
return mLayerHistory.mActiveLayerInfos.size();
}
- void replaceTouchTimer(int64_t millis) {
+ void replaceTouchTimer(int64_t millis,
+ std::function<void(bool isReset)>&& testCallback = nullptr) {
if (mTouchTimer) {
mTouchTimer.reset();
}
mTouchTimer.emplace(
"Testable Touch timer", std::chrono::milliseconds(millis),
- [this] { touchTimerCallback(TimerState::Reset); },
- [this] { touchTimerCallback(TimerState::Expired); });
+ [this, testCallback] {
+ touchTimerCallback(TimerState::Reset);
+ if (testCallback != nullptr) {
+ testCallback(true);
+ }
+ },
+ [this, testCallback] {
+ touchTimerCallback(TimerState::Expired);
+ if (testCallback != nullptr) {
+ testCallback(false);
+ }
+ });
mTouchTimer->start();
}
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/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();
}
});
diff --git a/services/vibratorservice/test/VibratorHalControllerTest.cpp b/services/vibratorservice/test/VibratorHalControllerTest.cpp
index 9b95d74..15fde91 100644
--- a/services/vibratorservice/test/VibratorHalControllerTest.cpp
+++ b/services/vibratorservice/test/VibratorHalControllerTest.cpp
@@ -255,16 +255,17 @@
.WillRepeatedly(Return(vibrator::HalResult<void>::transactionFailed("message")));
}
- std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
- auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+ auto counter = vibrator::TestCounter(0);
- auto onFn = [&](vibrator::HalWrapper* hal) { return hal->on(10ms, callback); };
+ auto onFn = [&](vibrator::HalWrapper* hal) {
+ return hal->on(10ms, [&counter] { counter.increment(); });
+ };
ASSERT_TRUE(mController->doWithRetry<void>(onFn, "on").isOk());
ASSERT_TRUE(mController->doWithRetry<void>(PING_FN, "ping").isFailed());
mMockHal.reset();
- ASSERT_EQ(0, *callbackCounter.get());
+ ASSERT_EQ(0, counter.get());
// Callback triggered even after HalWrapper was reconnected.
- std::this_thread::sleep_for(15ms);
- ASSERT_EQ(1, *callbackCounter.get());
+ counter.tryWaitUntilCountIsAtLeast(1, 500ms);
+ ASSERT_EQ(1, counter.get());
}
diff --git a/services/vibratorservice/test/test_utils.h b/services/vibratorservice/test/test_utils.h
index 1933a11..b584f64 100644
--- a/services/vibratorservice/test/test_utils.h
+++ b/services/vibratorservice/test/test_utils.h
@@ -85,10 +85,36 @@
~TestFactory() = delete;
};
+class TestCounter {
+public:
+ TestCounter(int32_t init) : mCount(init), mMutex(), mCondVar() {}
+
+ int32_t get() {
+ std::unique_lock<std::mutex> lock(mMutex);
+ return mCount;
+ }
+
+ void increment() {
+ std::unique_lock<std::mutex> lock(mMutex);
+ mCount += 1;
+ mCondVar.notify_all();
+ }
+
+ void tryWaitUntilCountIsAtLeast(int32_t count, std::chrono::milliseconds timeout) {
+ std::unique_lock<std::mutex> lock(mMutex);
+ mCondVar.wait_for(lock, timeout, [&] { return mCount >= count; });
+ }
+
+private:
+ int32_t mCount;
+ std::mutex mMutex;
+ std::condition_variable mCondVar;
+};
+
// -------------------------------------------------------------------------------------------------
} // namespace vibrator
} // namespace android
-#endif // VIBRATORSERVICE_UNITTEST_UTIL_H_
\ No newline at end of file
+#endif // VIBRATORSERVICE_UNITTEST_UTIL_H_