Skip to content

Commit d6d7df7

Browse files
author
Arpit Singh
committed
[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
1 parent b61e79d commit d6d7df7

6 files changed

Lines changed: 239 additions & 0 deletions

File tree

include/input/DisplayTopologyGraph.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ struct DisplayTopologyGraph {
5555
ui::LogicalDisplayId primaryDisplayId = ui::LogicalDisplayId::INVALID;
5656
std::unordered_map<ui::LogicalDisplayId, std::vector<DisplayTopologyAdjacentDisplay>> graph;
5757
std::unordered_map<ui::LogicalDisplayId, int> displaysDensity;
58+
59+
bool isValid() const;
5860
};
5961

6062
} // namespace android

libs/input/Android.bp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ cc_library {
225225
srcs: [
226226
"AccelerationCurve.cpp",
227227
"CoordinateFilter.cpp",
228+
"DisplayTopologyGraph.cpp",
228229
"Input.cpp",
229230
"InputConsumer.cpp",
230231
"InputConsumerNoResampling.cpp",
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#define LOG_TAG "DisplayTopologyValidator"
18+
19+
#include <android-base/logging.h>
20+
#include <ftl/enum.h>
21+
#include <input/DisplayTopologyGraph.h>
22+
#include <ui/LogicalDisplayId.h>
23+
24+
#include <algorithm>
25+
26+
namespace android {
27+
28+
namespace {
29+
DisplayTopologyPosition getOppositePosition(DisplayTopologyPosition position) {
30+
switch (position) {
31+
case DisplayTopologyPosition::LEFT:
32+
return DisplayTopologyPosition::RIGHT;
33+
case DisplayTopologyPosition::TOP:
34+
return DisplayTopologyPosition::BOTTOM;
35+
case DisplayTopologyPosition::RIGHT:
36+
return DisplayTopologyPosition::LEFT;
37+
case DisplayTopologyPosition::BOTTOM:
38+
return DisplayTopologyPosition::TOP;
39+
}
40+
}
41+
42+
bool validatePrimaryDisplay(const android::DisplayTopologyGraph& displayTopologyGraph) {
43+
return displayTopologyGraph.primaryDisplayId != ui::LogicalDisplayId::INVALID &&
44+
displayTopologyGraph.graph.contains(displayTopologyGraph.primaryDisplayId);
45+
}
46+
47+
bool validateTopologyGraph(const android::DisplayTopologyGraph& displayTopologyGraph) {
48+
for (const auto& [sourceDisplay, adjacentDisplays] : displayTopologyGraph.graph) {
49+
for (const DisplayTopologyAdjacentDisplay& adjacentDisplay : adjacentDisplays) {
50+
const auto adjacentGraphIt = displayTopologyGraph.graph.find(adjacentDisplay.displayId);
51+
if (adjacentGraphIt == displayTopologyGraph.graph.end()) {
52+
LOG(ERROR) << "Missing adjacent display in topology graph: "
53+
<< adjacentDisplay.displayId << " for source " << sourceDisplay;
54+
return false;
55+
}
56+
const auto reverseEdgeIt =
57+
std::find_if(adjacentGraphIt->second.begin(), adjacentGraphIt->second.end(),
58+
[sourceDisplay](const DisplayTopologyAdjacentDisplay&
59+
reverseAdjacentDisplay) {
60+
return sourceDisplay == reverseAdjacentDisplay.displayId;
61+
});
62+
if (reverseEdgeIt == adjacentGraphIt->second.end()) {
63+
LOG(ERROR) << "Missing reverse edge in topology graph for: " << sourceDisplay
64+
<< " -> " << adjacentDisplay.displayId;
65+
return false;
66+
}
67+
DisplayTopologyPosition expectedPosition =
68+
getOppositePosition(adjacentDisplay.position);
69+
if (reverseEdgeIt->position != expectedPosition) {
70+
LOG(ERROR) << "Unexpected reverse edge for: " << sourceDisplay << " -> "
71+
<< adjacentDisplay.displayId
72+
<< " expected position: " << ftl::enum_string(expectedPosition)
73+
<< " actual " << ftl::enum_string(reverseEdgeIt->position);
74+
return false;
75+
}
76+
if (reverseEdgeIt->offsetDp != -adjacentDisplay.offsetDp) {
77+
LOG(ERROR) << "Unexpected reverse edge offset: " << sourceDisplay << " -> "
78+
<< adjacentDisplay.displayId
79+
<< " expected offset: " << -adjacentDisplay.offsetDp << " actual "
80+
<< reverseEdgeIt->offsetDp;
81+
return false;
82+
}
83+
}
84+
}
85+
return true;
86+
}
87+
88+
bool validateDensities(const android::DisplayTopologyGraph& displayTopologyGraph) {
89+
for (const auto& [sourceDisplay, adjacentDisplays] : displayTopologyGraph.graph) {
90+
if (!displayTopologyGraph.displaysDensity.contains(sourceDisplay)) {
91+
LOG(ERROR) << "Missing density value in topology graph for display: " << sourceDisplay;
92+
return false;
93+
}
94+
}
95+
return true;
96+
}
97+
98+
} // namespace
99+
100+
bool DisplayTopologyGraph::isValid() const {
101+
return validatePrimaryDisplay(*this) && validateTopologyGraph(*this) &&
102+
validateDensities(*this);
103+
}
104+
105+
} // namespace android

libs/input/input_flags.aconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,13 @@ flag {
239239
purpose: PURPOSE_BUGFIX
240240
}
241241
}
242+
243+
flag {
244+
name: "enable_display_topology_validation"
245+
namespace: "input"
246+
description: "Set to true to enable display topology validation"
247+
bug: "401219231"
248+
metadata {
249+
purpose: PURPOSE_BUGFIX
250+
}
251+
}

services/inputflinger/tests/Android.bp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ cc_test {
5252
"AnrTracker_test.cpp",
5353
"CapturedTouchpadEventConverter_test.cpp",
5454
"CursorInputMapper_test.cpp",
55+
"DisplayTopologyGraph_test.cpp",
5556
"EventHub_test.cpp",
5657
"FakeEventHub.cpp",
5758
"FakeInputReaderPolicy.cpp",
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include <gtest/gtest.h>
18+
#include <input/DisplayTopologyGraph.h>
19+
20+
#include <string>
21+
#include <string_view>
22+
#include <tuple>
23+
24+
namespace android {
25+
26+
namespace {
27+
28+
constexpr ui::LogicalDisplayId DISPLAY_ID_1{1};
29+
constexpr ui::LogicalDisplayId DISPLAY_ID_2{2};
30+
constexpr int DENSITY_MEDIUM = 160;
31+
32+
} // namespace
33+
34+
using DisplayTopologyGraphTestFixtureParam =
35+
std::tuple<std::string_view /*name*/, DisplayTopologyGraph, bool /*isValid*/>;
36+
37+
class DisplayTopologyGraphTestFixture
38+
: public testing::Test,
39+
public testing::WithParamInterface<DisplayTopologyGraphTestFixtureParam> {};
40+
41+
TEST_P(DisplayTopologyGraphTestFixture, DisplayTopologyGraphTest) {
42+
const auto& [_, displayTopology, isValid] = GetParam();
43+
EXPECT_EQ(isValid, displayTopology.isValid());
44+
}
45+
46+
INSTANTIATE_TEST_SUITE_P(
47+
DisplayTopologyGraphTest, DisplayTopologyGraphTestFixture,
48+
testing::Values(
49+
std::make_tuple(
50+
"InvalidPrimaryDisplay",
51+
DisplayTopologyGraph{.primaryDisplayId = ui::LogicalDisplayId::INVALID,
52+
.graph = {},
53+
.displaysDensity = {}},
54+
false),
55+
std::make_tuple("PrimaryDisplayNotInGraph",
56+
DisplayTopologyGraph{.primaryDisplayId = DISPLAY_ID_1,
57+
.graph = {},
58+
.displaysDensity = {}},
59+
false),
60+
std::make_tuple("DisplayDensityMissing",
61+
DisplayTopologyGraph{.primaryDisplayId = DISPLAY_ID_1,
62+
.graph = {{DISPLAY_ID_1, {}}},
63+
.displaysDensity = {}},
64+
false),
65+
std::make_tuple("ValidSingleDisplayTopology",
66+
DisplayTopologyGraph{.primaryDisplayId = DISPLAY_ID_1,
67+
.graph = {{DISPLAY_ID_1, {}}},
68+
.displaysDensity = {{DISPLAY_ID_1,
69+
DENSITY_MEDIUM}}},
70+
true),
71+
std::make_tuple(
72+
"MissingReverseEdge",
73+
DisplayTopologyGraph{.primaryDisplayId = DISPLAY_ID_1,
74+
.graph = {{DISPLAY_ID_1,
75+
{{DISPLAY_ID_2,
76+
DisplayTopologyPosition::TOP, 0}}}},
77+
.displaysDensity = {{DISPLAY_ID_1, DENSITY_MEDIUM},
78+
{DISPLAY_ID_2, DENSITY_MEDIUM}}},
79+
false),
80+
std::make_tuple(
81+
"IncorrectReverseEdgeDirection",
82+
DisplayTopologyGraph{.primaryDisplayId = DISPLAY_ID_1,
83+
.graph = {{DISPLAY_ID_1,
84+
{{DISPLAY_ID_2,
85+
DisplayTopologyPosition::TOP, 0}}},
86+
{DISPLAY_ID_2,
87+
{{DISPLAY_ID_1,
88+
DisplayTopologyPosition::TOP, 0}}}},
89+
.displaysDensity = {{DISPLAY_ID_1, DENSITY_MEDIUM},
90+
{DISPLAY_ID_2, DENSITY_MEDIUM}}},
91+
false),
92+
std::make_tuple(
93+
"IncorrectReverseEdgeOffset",
94+
DisplayTopologyGraph{.primaryDisplayId = DISPLAY_ID_1,
95+
.graph = {{DISPLAY_ID_1,
96+
{{DISPLAY_ID_2,
97+
DisplayTopologyPosition::TOP, 10}}},
98+
{DISPLAY_ID_2,
99+
{{DISPLAY_ID_1,
100+
DisplayTopologyPosition::BOTTOM, 20}}}},
101+
.displaysDensity = {{DISPLAY_ID_1, DENSITY_MEDIUM},
102+
{DISPLAY_ID_2, DENSITY_MEDIUM}}},
103+
false),
104+
std::make_tuple(
105+
"ValidMultiDisplayTopology",
106+
DisplayTopologyGraph{.primaryDisplayId = DISPLAY_ID_1,
107+
.graph = {{DISPLAY_ID_1,
108+
{{DISPLAY_ID_2,
109+
DisplayTopologyPosition::TOP, 10}}},
110+
{DISPLAY_ID_2,
111+
{{DISPLAY_ID_1,
112+
DisplayTopologyPosition::BOTTOM, -10}}}},
113+
.displaysDensity = {{DISPLAY_ID_1, DENSITY_MEDIUM},
114+
{DISPLAY_ID_2, DENSITY_MEDIUM}}},
115+
true)),
116+
[](const testing::TestParamInfo<DisplayTopologyGraphTestFixtureParam>& p) {
117+
return std::string{std::get<0>(p.param)};
118+
});
119+
120+
} // namespace android

0 commit comments

Comments
 (0)