blob: 2b316cde271857f2534a11cf0257ef11a79ed6d3 [file]
/*
* 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 <android-base/stringprintf.h>
#include <com_android_input_flags.h>
#include <ftl/enum.h>
#include <input/DisplayTopologyGraph.h>
#include <input/PrintTools.h>
#include <log/log_main.h>
#include <ui/LogicalDisplayId.h>
#include <algorithm>
#define INDENT " "
namespace input_flags = com::android::input::flags;
namespace android {
namespace {
std::string logicalDisplayIdToString(const ui::LogicalDisplayId& displayId) {
return base::StringPrintf("displayId(%d)", displayId.val());
}
std::string adjacentDisplayToString(const DisplayTopologyAdjacentDisplay& adjacentDisplay) {
return adjacentDisplay.dump();
}
std::string floatRectToString(const FloatRect& floatRect) {
std::string dump;
dump += base::StringPrintf("FloatRect(%f, %f, %f, %f)", floatRect.left, floatRect.top,
floatRect.right, floatRect.bottom);
return dump;
}
std::string displayPropertiesToString(const DisplayTopologyGraph::Properties& displayProperties) {
std::string dump;
dump += "AdjacentDisplays: ";
dump += dumpVector(displayProperties.adjacentDisplays, adjacentDisplayToString);
dump += '\n';
dump += base::StringPrintf("Density: %d", displayProperties.density);
dump += '\n';
dump += "Bounds: ";
dump += floatRectToString(displayProperties.boundsInGlobalDp);
dump += '\n';
return dump;
}
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(
ui::LogicalDisplayId primaryDisplayId,
const std::unordered_map<ui::LogicalDisplayId, DisplayTopologyGraph::Properties>&
topologyGraph) {
return primaryDisplayId != ui::LogicalDisplayId::INVALID &&
topologyGraph.contains(primaryDisplayId);
}
bool validateTopologyGraph(
const std::unordered_map<ui::LogicalDisplayId, DisplayTopologyGraph::Properties>&
topologyGraph) {
for (const auto& [sourceDisplay, displayProperties] : topologyGraph) {
if (!sourceDisplay.isValid()) {
LOG(ERROR) << "Invalid display in topology graph: " << sourceDisplay;
return false;
}
if (displayProperties.boundsInGlobalDp.getHeight() <= 0 ||
displayProperties.boundsInGlobalDp.getWidth() <= 0) {
LOG(ERROR) << "Invalid display-bounds for " << logicalDisplayIdToString(sourceDisplay)
<< " in topology graph: "
<< floatRectToString(displayProperties.boundsInGlobalDp);
return false;
}
if (displayProperties.density <= 0) {
LOG(ERROR) << "Invalid density for " << logicalDisplayIdToString(sourceDisplay)
<< "in topology graph: " << displayProperties.density;
return false;
}
for (const DisplayTopologyAdjacentDisplay& adjacentDisplay :
displayProperties.adjacentDisplays) {
const auto adjacentGraphIt = topologyGraph.find(adjacentDisplay.displayId);
if (adjacentGraphIt == topologyGraph.end()) {
LOG(ERROR) << "Missing adjacent display in topology graph: "
<< adjacentDisplay.displayId << " for source " << sourceDisplay;
return false;
}
std::vector<DisplayTopologyAdjacentDisplay> reverseEdges;
for (const auto& edge : adjacentGraphIt->second.adjacentDisplays) {
if (edge.displayId == sourceDisplay) {
reverseEdges.push_back(edge);
}
}
if (reverseEdges.empty()) {
LOG(ERROR) << "Missing reverse edge in topology graph for: " << sourceDisplay
<< " -> " << adjacentDisplay.displayId;
return false;
}
DisplayTopologyPosition expectedOppositePosition =
getOppositePosition(adjacentDisplay.position);
const auto reverseEdgeIt =
std::find_if(reverseEdges.begin(), reverseEdges.end(),
[expectedOppositePosition](
const DisplayTopologyAdjacentDisplay& edge) {
return expectedOppositePosition == edge.position;
});
if (reverseEdgeIt == reverseEdges.end()) {
std::string positions;
for (const auto& edge : reverseEdges) {
positions += ftl::enum_string(edge.position);
positions += " ";
}
LOG(ERROR) << "Reverse edges for: " << sourceDisplay << " -> "
<< adjacentDisplay.displayId
<< " found, but none had the expected position: "
<< ftl::enum_string(expectedOppositePosition) << " actual [" << positions
<< "]";
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 areTopologyGraphComponentsValid(
ui::LogicalDisplayId primaryDisplayId,
const std::unordered_map<ui::LogicalDisplayId, DisplayTopologyGraph::Properties>&
topologyGraph) {
if (!input_flags::enable_display_topology_validation()) {
return true;
}
return validatePrimaryDisplay(primaryDisplayId, topologyGraph) &&
validateTopologyGraph(topologyGraph);
}
std::string dumpTopologyGraphComponents(
ui::LogicalDisplayId primaryDisplayId,
const std::unordered_map<ui::LogicalDisplayId, DisplayTopologyGraph::Properties>&
topologyGraph) {
std::string dump;
dump += base::StringPrintf("PrimaryDisplayId: %d\n", primaryDisplayId.val());
dump += base::StringPrintf("TopologyGraph:\n");
dump += addLinePrefix(dumpMap(topologyGraph, logicalDisplayIdToString,
displayPropertiesToString),
INDENT);
dump += "\n";
return dump;
}
} // namespace
ui::Transform DisplayTopologyGraph::localPxToGlobalDpTransform(
ui::LogicalDisplayId displayId) const {
const auto displayPropertiesIt = graph.find(displayId);
LOG_ALWAYS_FATAL_IF(displayPropertiesIt == graph.end(), "Invalid display %d in %s",
displayId.val(), __func__);
const auto& displayProperties = displayPropertiesIt->second;
// Scale to convert from px to DP.
const float pxToDpScaleFactor = static_cast<float>(ACONFIGURATION_DENSITY_MEDIUM) /
static_cast<float>(displayProperties.density);
ui::Transform pxToDpScaleTransform;
pxToDpScaleTransform.set(pxToDpScaleFactor, 0.0f, 0.0f, pxToDpScaleFactor);
// Translate origin from local to the topology origin to convert to the global coordinates.
const auto& displayBounds = displayProperties.boundsInGlobalDp;
ui::Transform localDpToGlobalDpTransform;
localDpToGlobalDpTransform.set(displayBounds.left, displayBounds.top);
return localDpToGlobalDpTransform * pxToDpScaleTransform;
}
ui::Transform DisplayTopologyGraph::globalDpToLocalPxTransform(
ui::LogicalDisplayId displayId) const {
const auto displayPropertiesIt = graph.find(displayId);
LOG_ALWAYS_FATAL_IF(displayPropertiesIt == graph.end(), "Invalid display %d in %s",
displayId.val(), __func__);
const auto& displayProperties = displayPropertiesIt->second;
// Translate from the topology origin to the destination-display's origin.
const auto& displayBounds = displayProperties.boundsInGlobalDp;
ui::Transform globalDpToLocalDpTransform;
globalDpToLocalDpTransform.set(-displayBounds.left, -displayBounds.top);
// Scale to convert from dp to px.
const float dpToPxScaleFactor = static_cast<float>(displayProperties.density) /
static_cast<float>(ACONFIGURATION_DENSITY_MEDIUM);
ui::Transform dpToPxScaleTransform;
dpToPxScaleTransform.set(dpToPxScaleFactor, 0.0f, 0.0f, dpToPxScaleFactor);
return dpToPxScaleTransform * globalDpToLocalDpTransform;
}
std::string DisplayTopologyAdjacentDisplay::dump() const {
std::string dump;
dump += base::StringPrintf("DisplayTopologyAdjacentDisplay: {displayId: %d, position: %s, "
"offsetDp: %f}",
displayId.val(), ftl::enum_string(position).c_str(), offsetDp);
return dump;
}
DisplayTopologyGraph::DisplayTopologyGraph(
ui::LogicalDisplayId primaryDisplay,
std::unordered_map<ui::LogicalDisplayId, Properties>&& topologyGraph)
: primaryDisplayId(primaryDisplay), graph(std::move(topologyGraph)) {}
std::string DisplayTopologyGraph::dump() const {
return dumpTopologyGraphComponents(primaryDisplayId, graph);
}
base::Result<const DisplayTopologyGraph> DisplayTopologyGraph::create(
ui::LogicalDisplayId primaryDisplay,
std::unordered_map<ui::LogicalDisplayId, Properties>&& topologyGraph) {
if (areTopologyGraphComponentsValid(primaryDisplay, topologyGraph)) {
return DisplayTopologyGraph(primaryDisplay, std::move(topologyGraph));
}
return base::Error() << "Invalid display topology components: "
<< dumpTopologyGraphComponents(primaryDisplay, topologyGraph);
}
} // namespace android