From d6d7df73241ba78a1e41a7ad1c358164e2358867 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Fri, 7 Mar 2025 11:14:03 +0000 Subject: [CD Cursor] Add DisplayTopologyValidator Add a DisplayTopologyValidator to verify assumptions made in the PointerChoreographer and InputDispatcher, some sanity checks on the topology graph to flag issues realted to topology for debugging. The validator will be used in the InputManager to validated and discard invalid topology updates. Test: atest inputflinger_tests Bug: 401219231 Flag: com.android.input.flags.enable_display_topology_validation Change-Id: I624bc32b73144cc047f317ef889f7f07cc1c3dfc --- include/input/DisplayTopologyGraph.h | 2 + libs/input/Android.bp | 1 + libs/input/DisplayTopologyGraph.cpp | 105 ++++++++++++++++++ libs/input/input_flags.aconfig | 10 ++ services/inputflinger/tests/Android.bp | 1 + .../tests/DisplayTopologyGraph_test.cpp | 120 +++++++++++++++++++++ 6 files changed, 239 insertions(+) create mode 100644 libs/input/DisplayTopologyGraph.cpp create mode 100644 services/inputflinger/tests/DisplayTopologyGraph_test.cpp diff --git a/include/input/DisplayTopologyGraph.h b/include/input/DisplayTopologyGraph.h index 3ae865a33a..ce7259e09a 100644 --- a/include/input/DisplayTopologyGraph.h +++ b/include/input/DisplayTopologyGraph.h @@ -55,6 +55,8 @@ struct DisplayTopologyGraph { ui::LogicalDisplayId primaryDisplayId = ui::LogicalDisplayId::INVALID; std::unordered_map> graph; std::unordered_map displaysDensity; + + bool isValid() const; }; } // namespace android diff --git a/libs/input/Android.bp b/libs/input/Android.bp index ff26184a33..0a180a4304 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -225,6 +225,7 @@ cc_library { srcs: [ "AccelerationCurve.cpp", "CoordinateFilter.cpp", + "DisplayTopologyGraph.cpp", "Input.cpp", "InputConsumer.cpp", "InputConsumerNoResampling.cpp", diff --git a/libs/input/DisplayTopologyGraph.cpp b/libs/input/DisplayTopologyGraph.cpp new file mode 100644 index 0000000000..7ad9f163b8 --- /dev/null +++ b/libs/input/DisplayTopologyGraph.cpp @@ -0,0 +1,105 @@ +/* + * 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. + */ + +#define LOG_TAG "DisplayTopologyValidator" + +#include +#include +#include +#include + +#include + +namespace android { + +namespace { +DisplayTopologyPosition getOppositePosition(DisplayTopologyPosition position) { + switch (position) { + case DisplayTopologyPosition::LEFT: + return DisplayTopologyPosition::RIGHT; + case DisplayTopologyPosition::TOP: + return DisplayTopologyPosition::BOTTOM; + case DisplayTopologyPosition::RIGHT: + return DisplayTopologyPosition::LEFT; + case DisplayTopologyPosition::BOTTOM: + return DisplayTopologyPosition::TOP; + } +} + +bool validatePrimaryDisplay(const android::DisplayTopologyGraph& displayTopologyGraph) { + return displayTopologyGraph.primaryDisplayId != ui::LogicalDisplayId::INVALID && + displayTopologyGraph.graph.contains(displayTopologyGraph.primaryDisplayId); +} + +bool validateTopologyGraph(const android::DisplayTopologyGraph& displayTopologyGraph) { + for (const auto& [sourceDisplay, adjacentDisplays] : displayTopologyGraph.graph) { + for (const DisplayTopologyAdjacentDisplay& adjacentDisplay : adjacentDisplays) { + const auto adjacentGraphIt = displayTopologyGraph.graph.find(adjacentDisplay.displayId); + if (adjacentGraphIt == displayTopologyGraph.graph.end()) { + LOG(ERROR) << "Missing adjacent display in topology graph: " + << adjacentDisplay.displayId << " for source " << sourceDisplay; + return false; + } + const auto reverseEdgeIt = + std::find_if(adjacentGraphIt->second.begin(), adjacentGraphIt->second.end(), + [sourceDisplay](const DisplayTopologyAdjacentDisplay& + reverseAdjacentDisplay) { + return sourceDisplay == reverseAdjacentDisplay.displayId; + }); + if (reverseEdgeIt == adjacentGraphIt->second.end()) { + LOG(ERROR) << "Missing reverse edge in topology graph for: " << sourceDisplay + << " -> " << adjacentDisplay.displayId; + return false; + } + DisplayTopologyPosition expectedPosition = + getOppositePosition(adjacentDisplay.position); + if (reverseEdgeIt->position != expectedPosition) { + LOG(ERROR) << "Unexpected reverse edge for: " << sourceDisplay << " -> " + << adjacentDisplay.displayId + << " expected position: " << ftl::enum_string(expectedPosition) + << " actual " << ftl::enum_string(reverseEdgeIt->position); + return false; + } + if (reverseEdgeIt->offsetDp != -adjacentDisplay.offsetDp) { + LOG(ERROR) << "Unexpected reverse edge offset: " << sourceDisplay << " -> " + << adjacentDisplay.displayId + << " expected offset: " << -adjacentDisplay.offsetDp << " actual " + << reverseEdgeIt->offsetDp; + return false; + } + } + } + return true; +} + +bool validateDensities(const android::DisplayTopologyGraph& displayTopologyGraph) { + for (const auto& [sourceDisplay, adjacentDisplays] : displayTopologyGraph.graph) { + if (!displayTopologyGraph.displaysDensity.contains(sourceDisplay)) { + LOG(ERROR) << "Missing density value in topology graph for display: " << sourceDisplay; + return false; + } + } + return true; +} + +} // namespace + +bool DisplayTopologyGraph::isValid() const { + return validatePrimaryDisplay(*this) && validateTopologyGraph(*this) && + validateDensities(*this); +} + +} // namespace android diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig index 1c0c9e7252..983bbdee6e 100644 --- a/libs/input/input_flags.aconfig +++ b/libs/input/input_flags.aconfig @@ -239,3 +239,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_display_topology_validation" + namespace: "input" + description: "Set to true to enable display topology validation" + bug: "401219231" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 18d47f648b..677cf1e6c4 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -52,6 +52,7 @@ cc_test { "AnrTracker_test.cpp", "CapturedTouchpadEventConverter_test.cpp", "CursorInputMapper_test.cpp", + "DisplayTopologyGraph_test.cpp", "EventHub_test.cpp", "FakeEventHub.cpp", "FakeInputReaderPolicy.cpp", diff --git a/services/inputflinger/tests/DisplayTopologyGraph_test.cpp b/services/inputflinger/tests/DisplayTopologyGraph_test.cpp new file mode 100644 index 0000000000..fd2f21c4cb --- /dev/null +++ b/services/inputflinger/tests/DisplayTopologyGraph_test.cpp @@ -0,0 +1,120 @@ +/* + * 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 +#include + +#include +#include +#include + +namespace android { + +namespace { + +constexpr ui::LogicalDisplayId DISPLAY_ID_1{1}; +constexpr ui::LogicalDisplayId DISPLAY_ID_2{2}; +constexpr int DENSITY_MEDIUM = 160; + +} // namespace + +using DisplayTopologyGraphTestFixtureParam = + std::tuple; + +class DisplayTopologyGraphTestFixture + : public testing::Test, + public testing::WithParamInterface {}; + +TEST_P(DisplayTopologyGraphTestFixture, DisplayTopologyGraphTest) { + const auto& [_, displayTopology, isValid] = GetParam(); + EXPECT_EQ(isValid, displayTopology.isValid()); +} + +INSTANTIATE_TEST_SUITE_P( + DisplayTopologyGraphTest, DisplayTopologyGraphTestFixture, + testing::Values( + std::make_tuple( + "InvalidPrimaryDisplay", + DisplayTopologyGraph{.primaryDisplayId = ui::LogicalDisplayId::INVALID, + .graph = {}, + .displaysDensity = {}}, + false), + std::make_tuple("PrimaryDisplayNotInGraph", + DisplayTopologyGraph{.primaryDisplayId = DISPLAY_ID_1, + .graph = {}, + .displaysDensity = {}}, + false), + std::make_tuple("DisplayDensityMissing", + DisplayTopologyGraph{.primaryDisplayId = DISPLAY_ID_1, + .graph = {{DISPLAY_ID_1, {}}}, + .displaysDensity = {}}, + false), + std::make_tuple("ValidSingleDisplayTopology", + DisplayTopologyGraph{.primaryDisplayId = DISPLAY_ID_1, + .graph = {{DISPLAY_ID_1, {}}}, + .displaysDensity = {{DISPLAY_ID_1, + DENSITY_MEDIUM}}}, + true), + std::make_tuple( + "MissingReverseEdge", + DisplayTopologyGraph{.primaryDisplayId = DISPLAY_ID_1, + .graph = {{DISPLAY_ID_1, + {{DISPLAY_ID_2, + DisplayTopologyPosition::TOP, 0}}}}, + .displaysDensity = {{DISPLAY_ID_1, DENSITY_MEDIUM}, + {DISPLAY_ID_2, DENSITY_MEDIUM}}}, + false), + std::make_tuple( + "IncorrectReverseEdgeDirection", + DisplayTopologyGraph{.primaryDisplayId = DISPLAY_ID_1, + .graph = {{DISPLAY_ID_1, + {{DISPLAY_ID_2, + DisplayTopologyPosition::TOP, 0}}}, + {DISPLAY_ID_2, + {{DISPLAY_ID_1, + DisplayTopologyPosition::TOP, 0}}}}, + .displaysDensity = {{DISPLAY_ID_1, DENSITY_MEDIUM}, + {DISPLAY_ID_2, DENSITY_MEDIUM}}}, + false), + std::make_tuple( + "IncorrectReverseEdgeOffset", + DisplayTopologyGraph{.primaryDisplayId = DISPLAY_ID_1, + .graph = {{DISPLAY_ID_1, + {{DISPLAY_ID_2, + DisplayTopologyPosition::TOP, 10}}}, + {DISPLAY_ID_2, + {{DISPLAY_ID_1, + DisplayTopologyPosition::BOTTOM, 20}}}}, + .displaysDensity = {{DISPLAY_ID_1, DENSITY_MEDIUM}, + {DISPLAY_ID_2, DENSITY_MEDIUM}}}, + false), + std::make_tuple( + "ValidMultiDisplayTopology", + DisplayTopologyGraph{.primaryDisplayId = DISPLAY_ID_1, + .graph = {{DISPLAY_ID_1, + {{DISPLAY_ID_2, + DisplayTopologyPosition::TOP, 10}}}, + {DISPLAY_ID_2, + {{DISPLAY_ID_1, + DisplayTopologyPosition::BOTTOM, -10}}}}, + .displaysDensity = {{DISPLAY_ID_1, DENSITY_MEDIUM}, + {DISPLAY_ID_2, DENSITY_MEDIUM}}}, + true)), + [](const testing::TestParamInfo& p) { + return std::string{std::get<0>(p.param)}; + }); + +} // namespace android -- cgit v1.2.3-59-g8ed1b