Skip to content

Commit 269580e

Browse files
Arpit SinghAndroid (Google) Code Review
authored andcommitted
Merge "[CD Cursor] Add DisplayTopologyValidator" into main
2 parents 0864e4f + d6d7df7 commit 269580e

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)