Loading include/input/DisplayTopologyGraph.h +2 −0 Original line number Diff line number Diff line Loading @@ -55,6 +55,8 @@ struct DisplayTopologyGraph { ui::LogicalDisplayId primaryDisplayId = ui::LogicalDisplayId::INVALID; std::unordered_map<ui::LogicalDisplayId, std::vector<DisplayTopologyAdjacentDisplay>> graph; std::unordered_map<ui::LogicalDisplayId, int> displaysDensity; bool isValid() const; }; } // namespace android libs/input/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -225,6 +225,7 @@ cc_library { srcs: [ "AccelerationCurve.cpp", "CoordinateFilter.cpp", "DisplayTopologyGraph.cpp", "Input.cpp", "InputConsumer.cpp", "InputConsumerNoResampling.cpp", Loading libs/input/DisplayTopologyGraph.cpp 0 → 100644 +105 −0 Original line number Diff line number Diff line /* * 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 <android-base/logging.h> #include <ftl/enum.h> #include <input/DisplayTopologyGraph.h> #include <ui/LogicalDisplayId.h> #include <algorithm> 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 libs/input/input_flags.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -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 } } services/inputflinger/tests/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -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", Loading Loading
include/input/DisplayTopologyGraph.h +2 −0 Original line number Diff line number Diff line Loading @@ -55,6 +55,8 @@ struct DisplayTopologyGraph { ui::LogicalDisplayId primaryDisplayId = ui::LogicalDisplayId::INVALID; std::unordered_map<ui::LogicalDisplayId, std::vector<DisplayTopologyAdjacentDisplay>> graph; std::unordered_map<ui::LogicalDisplayId, int> displaysDensity; bool isValid() const; }; } // namespace android
libs/input/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -225,6 +225,7 @@ cc_library { srcs: [ "AccelerationCurve.cpp", "CoordinateFilter.cpp", "DisplayTopologyGraph.cpp", "Input.cpp", "InputConsumer.cpp", "InputConsumerNoResampling.cpp", Loading
libs/input/DisplayTopologyGraph.cpp 0 → 100644 +105 −0 Original line number Diff line number Diff line /* * 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 <android-base/logging.h> #include <ftl/enum.h> #include <input/DisplayTopologyGraph.h> #include <ui/LogicalDisplayId.h> #include <algorithm> 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
libs/input/input_flags.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -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 } }
services/inputflinger/tests/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -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", Loading