diff --git a/src/com/android/settings/connecteddevice/display/DisplayTopology.kt b/src/com/android/settings/connecteddevice/display/DisplayTopology.kt index 162d9d284fb..76abc031055 100644 --- a/src/com/android/settings/connecteddevice/display/DisplayTopology.kt +++ b/src/com/android/settings/connecteddevice/display/DisplayTopology.kt @@ -41,8 +41,20 @@ import kotlin.math.min * left corner of the pane. It uses a scale optimized for showing all displays with minimal or no * scrolling. The display coordinates are floating point and the origin can be in any position. In * practice the origin will be the upper-left coordinate of the primary display. + * + * @param paneWidth width of the pane in view coordinates + * @param minEdgeLength the smallest length permitted of a display block. This should be set based + * on accessibility requirements, but also accounting for padding that appears + * around each button. + * @param maxBlockRatio the highest allowed ratio of block size to display size. For instance, a + * value of 0.05 means the block will at most be 1/20 the size of the display + * it represents. This limit may be breached to account for minEdgeLength, + * which is considered an a11y requirement. + * @param displaysPos the absolute topology coordinates for each display in the topology. */ -class TopologyScale(paneWidth : Int, displaysPos : Collection) { +class TopologyScale( + paneWidth : Int, minEdgeLength : Int, maxBlockRatio : Float, + displaysPos : Collection) { /** Scale of block sizes to real-world display sizes. Should be less than 1. */ val blockRatio : Float @@ -69,16 +81,14 @@ class TopologyScale(paneWidth : Int, displaysPos : Collection) { biggestDisplayHeight = max(biggestDisplayHeight, pos.height()) } - // Set height according to the width and the aspect ratio of the display bounds. - // 0.05 is a reasonable limit to the size of display blocks. It appears to match the - // ratio used in the ChromeOS topology editor. It prevents blocks from being too large, - // which would make dragging and dropping awkward. - val rawBlockRatio = min(0.05, paneWidth.toDouble() * 0.6 / displayBounds.width()) + // Set height according to the width and the aspect ratio of the display bounds limitted by + // maxBlockRatio. It prevents blocks from being too large, which would make dragging and + // dropping awkward. + val rawBlockRatio = min(maxBlockRatio, paneWidth.toFloat() * 0.6f / displayBounds.width()) // If the `ratio` is set too low because one of the displays will have an edge less than - // 48dp long, increase it such that the smallest edge is that long. This may override the - // 0.05 limit since it is more important than it. - blockRatio = max(48.0 / smallestDisplayDim, rawBlockRatio).toFloat() + // minEdgeLength(dp) long, increase it such that the smallest edge is that long. + blockRatio = max(minEdgeLength.toFloat() / smallestDisplayDim, rawBlockRatio).toFloat() // Essentially, we just set the pane height based on the pre-determined pane width and the // aspect ratio of the display bounds. But we may need to increase it slightly to achieve @@ -99,9 +109,9 @@ class TopologyScale(paneWidth : Int, displaysPos : Collection) { // such that the display bounds rect is centered in the pane. // It is unlikely that either of these coordinates will be negative since blockRatio has // been chosen to allow 20% padding around each side of the display blocks. However, the - // a11y requirement applied above (48.0 / smallestDisplayDim) may cause the blocks to not - // fit. This should be rare in practice, and can be worked around by moving the settings UI - // to a larger display. + // a11y requirement applied above (minEdgeLength / smallestDisplayDim) may cause the blocks + // to not fit. This should be rare in practice, and can be worked around by moving the + // settings UI to a larger display. val blockMostLeft = (paneWidth - displayBounds.width() * blockRatio) / 2 val blockMostTop = (paneHeight - displayBounds.height() * blockRatio) / 2 diff --git a/tests/robotests/src/com/android/settings/connecteddevice/display/TopologyScaleTest.kt b/tests/robotests/src/com/android/settings/connecteddevice/display/TopologyScaleTest.kt index 078436264e7..76f9bfcfd7c 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/display/TopologyScaleTest.kt +++ b/tests/robotests/src/com/android/settings/connecteddevice/display/TopologyScaleTest.kt @@ -35,6 +35,7 @@ class TopologyScaleTest { fun oneDisplay4to3Aspect() { val scale = TopologyScale( /* paneWidth= */ 640, + minEdgeLength = 48, maxBlockRatio = 0.05f, listOf(RectF(0f, 0f, 640f, 480f))) // blockRatio is higher than 0.05 in order to make the smallest display edge (480 dp) 48dp @@ -51,6 +52,7 @@ class TopologyScaleTest { fun twoUnalignedDisplays() { val scale = TopologyScale( /* paneWidth= */ 300, + minEdgeLength = 48, maxBlockRatio = 0.05f, listOf(RectF(0f, 0f, 1920f, 1200f), RectF(1920f, -300f, 3840f, 900f))) assertEquals( @@ -65,6 +67,7 @@ class TopologyScaleTest { fun twoDisplaysBlockRatioBumpedForGarSizeMinimumHorizontal() { val scale = TopologyScale( /* paneWidth= */ 192, + minEdgeLength = 48, maxBlockRatio = 0.05f, listOf(RectF(0f, 0f, 240f, 320f), RectF(-240f, -320f, 0f, 0f))) // blockRatio is higher than 0.05 in order to make the smallest display edge (240 dp) 48dp @@ -81,6 +84,7 @@ class TopologyScaleTest { fun paneVerticalPaddingLimitedByTallestDisplay() { val scale = TopologyScale( /* paneWidth= */ 300, + minEdgeLength = 48, maxBlockRatio = 0.05f, listOf( RectF(0f, 0f, 640f, 480f), RectF(0f, 480f, 640f, 960f), @@ -94,4 +98,35 @@ class TopologyScaleTest { assertEquals(Point(150, 48), scale.displayToPaneCoor(PointF(320f, 0f))) assertPointF(-180f, 2880f, 0.001f, scale.paneToDisplayCoor(Point(100, 336))) } + + @Test + fun limitedByCustomMaxBlockRatio() { + val scale = TopologyScale( + /* paneWidth= */ 300, + minEdgeLength = 24, maxBlockRatio = 0.12f, + listOf( + RectF(0f, 0f, 640f, 480f), + RectF(0f, 480f, 640f, 960f))) + + assertEquals( + "{TopoScale blockRatio=0.120000 originPaneXY=111,57 paneHeight=230}", "" + scale) + assertEquals(Point(149, 57), scale.displayToPaneCoor(PointF(320f, 0f))) + assertPointF(-91.6667f, 2325f, 0.001f, scale.paneToDisplayCoor(Point(100, 336))) + } + + @Test + fun largeCustomMinEdgeLength() { + // minBlockEdgeLength/minDisplayEdgeLength = 80/480 = 1/6, so the block ratio will be 1/6 + val scale = TopologyScale( + /* paneWidth= */ 300, + minEdgeLength = 80, maxBlockRatio = 0.05f, + listOf( + RectF(0f, 0f, 640f, 480f), + RectF(0f, 480f, 640f, 960f))) + + assertEquals( + "{TopoScale blockRatio=0.166667 originPaneXY=96,80 paneHeight=320}", "" + scale) + assertEquals(Point(149, 80), scale.displayToPaneCoor(PointF(320f, 0f))) + assertPointF(24f, 1536f, 0.001f, scale.paneToDisplayCoor(Point(100, 336))) + } }