Merge "SlopController: fix formatting of logged values" into main
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp
index a5d176d..fdb032b 100644
--- a/cmds/dumpstate/Android.bp
+++ b/cmds/dumpstate/Android.bp
@@ -117,6 +117,7 @@
"libdumpsys",
"libserviceutils",
"android.tracing.flags_c_lib",
+ "perfetto_flags_c_lib",
],
}
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 888fb67..9e3e2b0 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -57,6 +57,7 @@
#include <log/log_read.h>
#include <math.h>
#include <openssl/sha.h>
+#include <perfetto_flags.h>
#include <poll.h>
#include <private/android_filesystem_config.h>
#include <private/android_logger.h>
@@ -190,7 +191,7 @@
#define SNAPSHOTCTL_LOG_DIR "/data/misc/snapshotctl_log"
#define LINKERCONFIG_DIR "/linkerconfig"
#define PACKAGE_DEX_USE_LIST "/data/system/package-dex-usage.list"
-#define SYSTEM_TRACE_SNAPSHOT "/data/misc/perfetto-traces/bugreport/systrace.pftrace"
+#define SYSTEM_TRACE_DIR "/data/misc/perfetto-traces/bugreport"
#define CGROUPFS_DIR "/sys/fs/cgroup"
#define SDK_EXT_INFO "/apex/com.android.sdkext/bin/derive_sdk"
#define DROPBOX_DIR "/data/system/dropbox"
@@ -359,6 +360,31 @@
return CopyFileToFd(input_file, out_fd.get());
}
+template <typename Func>
+size_t ForEachTrace(Func func) {
+ std::unique_ptr<DIR, decltype(&closedir)> traces_dir(opendir(SYSTEM_TRACE_DIR), closedir);
+
+ if (traces_dir == nullptr) {
+ MYLOGW("Unable to open directory %s: %s\n", SYSTEM_TRACE_DIR, strerror(errno));
+ return 0;
+ }
+
+ size_t traces_found = 0;
+ struct dirent* entry = nullptr;
+ while ((entry = readdir(traces_dir.get()))) {
+ if (entry->d_type != DT_REG) {
+ continue;
+ }
+ std::string trace_path = std::string(SYSTEM_TRACE_DIR) + "/" + entry->d_name;
+ if (access(trace_path.c_str(), F_OK) != 0) {
+ continue;
+ }
+ ++traces_found;
+ func(trace_path);
+ }
+ return traces_found;
+}
+
} // namespace
} // namespace os
} // namespace android
@@ -1101,20 +1127,16 @@
// This function copies into the .zip the system trace that was snapshotted
// by the early call to MaybeSnapshotSystemTraceAsync(), if any background
// tracing was happening.
- bool system_trace_exists = access(SYSTEM_TRACE_SNAPSHOT, F_OK) == 0;
- if (!system_trace_exists) {
- // No background trace was happening at the time MaybeSnapshotSystemTraceAsync() was invoked
- if (!PropertiesHelper::IsUserBuild()) {
- MYLOGI(
- "No system traces found. Check for previously uploaded traces by looking for "
- "go/trace-uuid in logcat")
- }
- return;
+ size_t traces_found = android::os::ForEachTrace([&](const std::string& trace_path) {
+ ds.AddZipEntry(ZIP_ROOT_DIR + trace_path, trace_path);
+ android::os::UnlinkAndLogOnError(trace_path);
+ });
+
+ if (traces_found == 0 && !PropertiesHelper::IsUserBuild()) {
+ MYLOGI(
+ "No system traces found. Check for previously uploaded traces by looking for "
+ "go/trace-uuid in logcat")
}
- ds.AddZipEntry(
- ZIP_ROOT_DIR + SYSTEM_TRACE_SNAPSHOT,
- SYSTEM_TRACE_SNAPSHOT);
- android::os::UnlinkAndLogOnError(SYSTEM_TRACE_SNAPSHOT);
}
static void DumpVisibleWindowViews() {
@@ -3412,8 +3434,8 @@
// duration is logged into MYLOG instead.
PrintHeader();
- bool system_trace_exists = access(SYSTEM_TRACE_SNAPSHOT, F_OK) == 0;
- if (options_->use_predumped_ui_data && !system_trace_exists) {
+ size_t trace_count = android::os::ForEachTrace([](const std::string&) {});
+ if (options_->use_predumped_ui_data && trace_count == 0) {
MYLOGW("Ignoring 'use predumped data' flag because no predumped data is available");
options_->use_predumped_ui_data = false;
}
@@ -3560,20 +3582,24 @@
}
// If a stale file exists already, remove it.
- unlink(SYSTEM_TRACE_SNAPSHOT);
+ android::os::ForEachTrace([&](const std::string& trace_path) { unlink(trace_path.c_str()); });
MYLOGI("Launching async '%s'", SERIALIZE_PERFETTO_TRACE_TASK.c_str())
+
return std::async(
std::launch::async, [this, outPath = std::move(outPath), outFd = std::move(outFd)] {
- // If a background system trace is happening and is marked as "suitable for
- // bugreport" (i.e. bugreport_score > 0 in the trace config), this command
- // will stop it and serialize into SYSTEM_TRACE_SNAPSHOT. In the (likely)
- // case that no trace is ongoing, this command is a no-op.
+ // If one or more background system traces are happening and are marked as
+ // "suitable for bugreport" (bugreport_score > 0 in the trace config), this command
+ // will snapshot them into SYSTEM_TRACE_DIR.
+ // In the (likely) case that no trace is ongoing, this command is a no-op.
// Note: this should not be enqueued as we need to freeze the trace before
// dumpstate starts. Otherwise the trace ring buffers will contain mostly
// the dumpstate's own activity which is irrelevant.
+ const char* cmd_arg = perfetto::flags::save_all_traces_in_bugreport()
+ ? "--save-all-for-bugreport"
+ : "--save-for-bugreport";
RunCommand(
- SERIALIZE_PERFETTO_TRACE_TASK, {"perfetto", "--save-for-bugreport"},
+ SERIALIZE_PERFETTO_TRACE_TASK, {"perfetto", cmd_arg},
CommandOptions::WithTimeout(30).DropRoot().CloseAllFileDescriptorsOnExec().Build(),
false, outFd);
// MaybeAddSystemTraceToZip() will take care of copying the trace in the zip
diff --git a/cmds/dumpstate/dumpstate_smoke_test.xml b/cmds/dumpstate/dumpstate_smoke_test.xml
index 0aff200..7e3307d 100644
--- a/cmds/dumpstate/dumpstate_smoke_test.xml
+++ b/cmds/dumpstate/dumpstate_smoke_test.xml
@@ -22,7 +22,9 @@
<option name="cleanup" value="true" />
<option name="push" value="dumpstate_smoke_test->/data/local/tmp/dumpstate_smoke_test" />
</target_preparer>
-
+ <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer">
+ <option name="flag-value" value="perfetto/perfetto.flags.save_all_traces_in_bugreport=true" />
+ </target_preparer>
<test class="com.android.tradefed.testtype.GTest" >
<option name="native-test-device-path" value="/data/local/tmp" />
<option name="module-name" value="dumpstate_smoke_test" />
diff --git a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
index a29923a..c72847c 100644
--- a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
@@ -24,8 +24,10 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <libgen.h>
+#include <signal.h>
#include <ziparchive/zip_archive.h>
+#include <cstdio>
#include <fstream>
#include <regex>
@@ -603,6 +605,93 @@
listener1->getErrorCode() == IDumpstateListener::BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
}
+class DumpstateTracingTest : public Test {
+ protected:
+ void TearDown() override {
+ for (int pid : bg_process_pids) {
+ kill(pid, SIGKILL);
+ }
+ }
+
+ void StartTracing(const std::string& config) {
+ // Write the perfetto config into a file.
+ const int id = static_cast<int>(bg_process_pids.size());
+ char cfg[64];
+ snprintf(cfg, sizeof(cfg), "/data/misc/perfetto-configs/br-%d", id);
+ unlink(cfg); // Remove the config file if it exists already.
+ FILE* f = fopen(cfg, "w");
+ ASSERT_NE(f, nullptr);
+ fputs(config.c_str(), f);
+ fclose(f);
+
+ // Invoke perfetto to start tracing.
+ char cmd[255];
+ snprintf(cmd, sizeof(cmd), "perfetto --background-wait --txt -o /dev/null -c %s", cfg);
+ FILE* proc = popen(cmd, "r");
+ ASSERT_NE(proc, nullptr);
+
+ // Read back the PID of the background process. We will use it to kill
+ // all tracing sessions when the test ends or fails.
+ char pid_str[32]{};
+ ASSERT_NE(fgets(pid_str, sizeof(pid_str), proc), nullptr);
+ int pid = atoi(pid_str);
+ bg_process_pids.push_back(pid);
+
+ pclose(proc);
+ unlink(cfg);
+ }
+
+ std::vector<int> bg_process_pids;
+};
+
+TEST_F(DumpstateTracingTest, ManyTracesInBugreport) {
+ // Note the trace duration is irrelevant and is only an upper bound.
+ // Tracing is stopped as soon as the bugreport.zip creation ends.
+ StartTracing(R"(
+buffers { size_kb: 4096 }
+data_sources {
+ config {
+ name: "linux.ftrace"
+ }
+}
+
+duration_ms: 120000
+bugreport_filename: "sys.pftrace"
+bugreport_score: 100
+)");
+
+ StartTracing(R"(
+buffers { size_kb: 4096 }
+data_sources {
+ config {
+ name: "linux.ftrace"
+ }
+}
+
+duration_ms: 120000
+bugreport_score: 50
+bugreport_filename: "mem.pftrace"
+)");
+
+ ZippedBugreportGenerationTest::GenerateBugreport();
+ std::string zip_path = ZippedBugreportGenerationTest::getZipFilePath();
+ ZipArchiveHandle handle;
+ ASSERT_EQ(OpenArchive(zip_path.c_str(), &handle), 0);
+
+ const char* kExpectedEntries[]{
+ "FS/data/misc/perfetto-traces/bugreport/sys.pftrace",
+ "FS/data/misc/perfetto-traces/bugreport/mem.pftrace",
+ };
+
+ // Check that the bugreport contains both traces.
+ for (const char* file_path : kExpectedEntries) {
+ ZipEntry entry{};
+ GetEntry(handle, file_path, &entry);
+ EXPECT_GT(entry.uncompressed_length, 100);
+ }
+ CloseArchive(handle);
+}
+
} // namespace dumpstate
} // namespace os
} // namespace android
diff --git a/include/input/InputFlags.h b/include/input/InputFlags.h
new file mode 100644
index 0000000..0e194ea
--- /dev/null
+++ b/include/input/InputFlags.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace android {
+
+class InputFlags {
+public:
+ /**
+ * Check if connected displays feature is enabled, either via the feature flag or settings
+ * override.
+ */
+ static bool connectedDisplaysCursorEnabled();
+};
+
+} // namespace android
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index d2e4320..ff26184 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -230,6 +230,7 @@
"InputConsumerNoResampling.cpp",
"InputDevice.cpp",
"InputEventLabels.cpp",
+ "InputFlags.cpp",
"InputTransport.cpp",
"InputVerifier.cpp",
"KeyCharacterMap.cpp",
diff --git a/libs/input/InputFlags.cpp b/libs/input/InputFlags.cpp
new file mode 100644
index 0000000..555b138
--- /dev/null
+++ b/libs/input/InputFlags.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <input/InputFlags.h>
+
+#include <android-base/logging.h>
+#include <com_android_input_flags.h>
+#include <cutils/properties.h>
+
+#include <string>
+
+namespace android {
+
+bool InputFlags::connectedDisplaysCursorEnabled() {
+ static std::optional<bool> cachedDevOption;
+ if (!cachedDevOption.has_value()) {
+ char value[PROPERTY_VALUE_MAX];
+ constexpr static auto sysprop_name = "persist.wm.debug.desktop_experience_devopts";
+ const int devOptionEnabled =
+ property_get(sysprop_name, value, nullptr) > 0 ? std::atoi(value) : 0;
+ cachedDevOption = devOptionEnabled == 1;
+ }
+ if (cachedDevOption.value_or(false)) {
+ return true;
+ }
+ return com::android::input::flags::connected_displays_cursor();
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index d02e643..85f842c 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -23,6 +23,7 @@
#if defined(__ANDROID__)
#include <gui/SurfaceComposerClient.h>
#endif
+#include <input/InputFlags.h>
#include <input/Keyboard.h>
#include <input/PrintTools.h>
#include <unordered_set>
@@ -328,7 +329,7 @@
filterPointerMotionForAccessibilityLocked(pc.getPosition(), vec2{deltaX, deltaY},
newArgs.displayId);
vec2 unconsumedDelta = pc.move(filteredDelta.x, filteredDelta.y);
- if (com::android::input::flags::connected_displays_cursor() &&
+ if (InputFlags::connectedDisplaysCursorEnabled() &&
(std::abs(unconsumedDelta.x) > 0 || std::abs(unconsumedDelta.y) > 0)) {
handleUnconsumedDeltaLocked(pc, unconsumedDelta);
// pointer may have moved to a different viewport
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index a2543b1..a77dc43 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -32,6 +32,7 @@
#include <gui/SurfaceComposerClient.h>
#endif
#include <input/InputDevice.h>
+#include <input/InputFlags.h>
#include <input/PrintTools.h>
#include <input/TraceTools.h>
#include <openssl/mem.h>
@@ -2808,8 +2809,9 @@
}
void InputDispatcher::addDragEventLocked(const MotionEntry& entry) {
- if (!mDragState || mDragState->dragWindow->getInfo()->displayId != entry.displayId ||
- mDragState->deviceId != entry.deviceId) {
+ if (!mDragState || mDragState->deviceId != entry.deviceId ||
+ !mWindowInfos.areDisplaysConnected(mDragState->dragWindow->getInfo()->displayId,
+ entry.displayId)) {
return;
}
@@ -5198,6 +5200,12 @@
return displayId;
}
+bool InputDispatcher::DispatcherWindowInfo::areDisplaysConnected(
+ ui::LogicalDisplayId display1, ui::LogicalDisplayId display2) const {
+ return display1 == display2 ||
+ (mTopology.graph.contains(display1) && mTopology.graph.contains(display2));
+}
+
std::string InputDispatcher::DispatcherWindowInfo::dumpDisplayAndWindowInfo() const {
std::string dump;
if (!mWindowHandlesByDisplay.empty()) {
@@ -7499,8 +7507,7 @@
return;
}
- if (com::android::input::flags::connected_displays_cursor() &&
- isMouseOrTouchpad(entry.source)) {
+ if (InputFlags::connectedDisplaysCursorEnabled() && isMouseOrTouchpad(entry.source)) {
mCursorStateByDisplay[windowInfos.getPrimaryDisplayId(entry.displayId)] =
std::move(touchState);
} else {
@@ -7511,8 +7518,7 @@
void InputDispatcher::DispatcherTouchState::eraseTouchStateForMotionEntry(
const android::inputdispatcher::MotionEntry& entry,
const DispatcherWindowInfo& windowInfos) {
- if (com::android::input::flags::connected_displays_cursor() &&
- isMouseOrTouchpad(entry.source)) {
+ if (InputFlags::connectedDisplaysCursorEnabled() && isMouseOrTouchpad(entry.source)) {
mCursorStateByDisplay.erase(windowInfos.getPrimaryDisplayId(entry.displayId));
} else {
mTouchStatesByDisplay.erase(entry.displayId);
@@ -7522,8 +7528,7 @@
const TouchState* InputDispatcher::DispatcherTouchState::getTouchStateForMotionEntry(
const android::inputdispatcher::MotionEntry& entry,
const DispatcherWindowInfo& windowInfos) const {
- if (com::android::input::flags::connected_displays_cursor() &&
- isMouseOrTouchpad(entry.source)) {
+ if (InputFlags::connectedDisplaysCursorEnabled() && isMouseOrTouchpad(entry.source)) {
auto touchStateIt =
mCursorStateByDisplay.find(windowInfos.getPrimaryDisplayId(entry.displayId));
if (touchStateIt != mCursorStateByDisplay.end()) {
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index a949900..7e8e142 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -343,6 +343,9 @@
// same displayId.
ui::LogicalDisplayId getPrimaryDisplayId(ui::LogicalDisplayId displayId) const;
+ bool areDisplaysConnected(ui::LogicalDisplayId display1,
+ ui::LogicalDisplayId display2) const;
+
std::string dumpDisplayAndWindowInfo() const;
private:
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 5abd65a..d21c4d7 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -16,6 +16,7 @@
#include "DebugConfig.h"
#include "input/InputDevice.h"
+#include "input/InputFlags.h"
#include "InputState.h"
@@ -234,8 +235,8 @@
ssize_t InputState::findMotionMemento(const MotionEntry& entry, bool hovering) const {
// If we have connected displays a mouse can move between displays and displayId may change
// while a gesture is in-progress.
- const bool skipDisplayCheck = com::android::input::flags::connected_displays_cursor() &&
- isMouseOrTouchpad(entry.source);
+ const bool skipDisplayCheck =
+ InputFlags::connectedDisplaysCursorEnabled() && isMouseOrTouchpad(entry.source);
for (size_t i = 0; i < mMotionMementos.size(); i++) {
const MotionMemento& memento = mMotionMementos[i];
if (memento.deviceId == entry.deviceId && memento.source == entry.source &&
@@ -355,7 +356,7 @@
// it's unlikely that those two streams would be consistent with each other. Therefore,
// cancel the previous gesture if the display id changes.
// Except when we have connected-displays where a mouse may move across display boundaries.
- const bool skipDisplayCheck = (com::android::input::flags::connected_displays_cursor() &&
+ const bool skipDisplayCheck = (InputFlags::connectedDisplaysCursorEnabled() &&
isMouseOrTouchpad(motionEntry.source));
if (!skipDisplayCheck && motionEntry.displayId != lastMemento.displayId) {
LOG(INFO) << "Canceling stream: last displayId was " << lastMemento.displayId
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 7e7d954..6c5d94d 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -12374,6 +12374,11 @@
sp<FakeWindowHandle> mSecondWindow;
sp<FakeWindowHandle> mDragWindow;
sp<FakeWindowHandle> mSpyWindow;
+
+ std::vector<gui::DisplayInfo> mDisplayInfos;
+
+ std::shared_ptr<FakeApplicationHandle> mSecondApplication;
+ sp<FakeWindowHandle> mWindowOnSecondDisplay;
// Mouse would force no-split, set the id as non-zero to verify if drag state could track it.
static constexpr int32_t MOUSE_POINTER_ID = 1;
@@ -12394,10 +12399,17 @@
mSpyWindow->setTrustedOverlay(true);
mSpyWindow->setFrame(Rect(0, 0, 200, 100));
+ mSecondApplication = std::make_shared<FakeApplicationHandle>();
+ mWindowOnSecondDisplay =
+ sp<FakeWindowHandle>::make(mSecondApplication, mDispatcher,
+ "TestWindowOnSecondDisplay", SECOND_DISPLAY_ID);
+ mWindowOnSecondDisplay->setFrame({0, 0, 100, 100});
+
mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApp);
mDispatcher->onWindowInfosChanged(
- {{*mSpyWindow->getInfo(), *mWindow->getInfo(), *mSecondWindow->getInfo()},
- {},
+ {{*mSpyWindow->getInfo(), *mWindow->getInfo(), *mSecondWindow->getInfo(),
+ *mWindowOnSecondDisplay->getInfo()},
+ mDisplayInfos,
0,
0});
}
@@ -12482,11 +12494,12 @@
mDragWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "DragWindow",
ui::LogicalDisplayId::DEFAULT);
mDragWindow->setTouchableRegion(Region{{0, 0, 0, 0}});
- mDispatcher->onWindowInfosChanged({{*mDragWindow->getInfo(), *mSpyWindow->getInfo(),
- *mWindow->getInfo(), *mSecondWindow->getInfo()},
- {},
- 0,
- 0});
+ mDispatcher->onWindowInfosChanged(
+ {{*mDragWindow->getInfo(), *mSpyWindow->getInfo(), *mWindow->getInfo(),
+ *mSecondWindow->getInfo(), *mWindowOnSecondDisplay->getInfo()},
+ mDisplayInfos,
+ 0,
+ 0});
// Transfer touch focus to the drag window
bool transferred =
@@ -12499,6 +12512,13 @@
}
return transferred;
}
+
+ void addDisplay(ui::LogicalDisplayId displayId, ui::Transform transform) {
+ gui::DisplayInfo displayInfo;
+ displayInfo.displayId = displayId;
+ displayInfo.transform = transform;
+ mDisplayInfos.push_back(displayInfo);
+ }
};
TEST_F(InputDispatcherDragTests, DragEnterAndDragExit) {
@@ -15185,7 +15205,7 @@
INSTANTIATE_TEST_SUITE_P(WithAndWithoutTransfer, TransferOrDontTransferFixture, testing::Bool());
-class InputDispatcherConnectedDisplayTest : public InputDispatcherTest {
+class InputDispatcherConnectedDisplayTest : public InputDispatcherDragTests {
constexpr static int DENSITY_MEDIUM = 160;
const DisplayTopologyGraph
@@ -15198,26 +15218,15 @@
{SECOND_DISPLAY_ID, DENSITY_MEDIUM}}};
protected:
- sp<FakeWindowHandle> mWindow;
-
void SetUp() override {
- InputDispatcherTest::SetUp();
+ addDisplay(DISPLAY_ID, ui::Transform());
+ addDisplay(SECOND_DISPLAY_ID,
+ ui::Transform(ui::Transform::ROT_270, /*logicalDisplayWidth=*/
+ 500, /*logicalDisplayHeight=*/500));
+
+ InputDispatcherDragTests::SetUp();
+
mDispatcher->setDisplayTopology(mTopology);
- mWindow = sp<FakeWindowHandle>::make(std::make_shared<FakeApplicationHandle>(), mDispatcher,
- "Window", DISPLAY_ID);
- mWindow->setFrame({0, 0, 100, 100});
-
- gui::DisplayInfo displayInfo1;
- displayInfo1.displayId = DISPLAY_ID;
-
- ui::Transform transform(ui::Transform::ROT_270, /*logicalDisplayWidth=*/500,
- /*logicalDisplayHeight=*/500);
- gui::DisplayInfo displayInfo2;
- displayInfo2.displayId = SECOND_DISPLAY_ID;
- displayInfo2.transform = transform;
-
- mDispatcher->onWindowInfosChanged(
- {{*mWindow->getInfo()}, {displayInfo1, displayInfo2}, 0, 0});
}
};
@@ -15283,4 +15292,58 @@
mWindow->consumeMotionUp(SECOND_DISPLAY_ID);
}
+TEST_F(InputDispatcherConnectedDisplayTest, MultiDisplayMouseDragAndDrop) {
+ SCOPED_FLAG_OVERRIDE(connected_displays_cursor, true);
+
+ startDrag(true, AINPUT_SOURCE_MOUSE);
+ // Move on window.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+ .displayId(DISPLAY_ID)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE).x(50).y(50))
+ .build());
+ mDragWindow->consumeMotionMove(DISPLAY_ID, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+ mWindow->consumeDragEvent(false, 50, 50);
+ mSecondWindow->assertNoEvents();
+ mWindowOnSecondDisplay->assertNoEvents();
+
+ // Move to another window.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+ .displayId(DISPLAY_ID)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE).x(150).y(50))
+ .build());
+ mDragWindow->consumeMotionMove(DISPLAY_ID, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+ mWindow->consumeDragEvent(true, 150, 50);
+ mSecondWindow->consumeDragEvent(false, 50, 50);
+ mWindowOnSecondDisplay->assertNoEvents();
+
+ // Move to window on the second display
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+ .displayId(SECOND_DISPLAY_ID)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE).x(50).y(50))
+ .build());
+ mDragWindow->consumeMotionMove(SECOND_DISPLAY_ID, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+ mWindow->assertNoEvents();
+ mSecondWindow->consumeDragEvent(true, -50, 50);
+ mWindowOnSecondDisplay->consumeDragEvent(false, 50, 50);
+
+ // drop on the second display
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE)
+ .displayId(SECOND_DISPLAY_ID)
+ .buttonState(0)
+ .pointer(PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE).x(50).y(50))
+ .build());
+ mDragWindow->consumeMotionUp(SECOND_DISPLAY_ID, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+ mFakePolicy->assertDropTargetEquals(*mDispatcher, mWindowOnSecondDisplay->getToken());
+ mWindow->assertNoEvents();
+ mSecondWindow->assertNoEvents();
+ mWindowOnSecondDisplay->assertNoEvents();
+}
+
} // namespace android::inputdispatcher