Loading core/java/android/hardware/display/DisplayTopology.java +159 −139 Original line number Original line Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.graphics.RectF; import android.os.Parcel; import android.os.Parcel; import android.os.Parcelable; import android.os.Parcelable; import android.util.IndentingPrintWriter; import android.util.IndentingPrintWriter; import android.util.MathUtils; import android.util.Pair; import android.util.Pair; import android.util.Slog; import android.util.Slog; import android.view.Display; import android.view.Display; Loading Loading @@ -283,6 +284,146 @@ public final class DisplayTopology implements Parcelable { normalize(); normalize(); } } /** * Clamp offsets and remove any overlaps between displays. */ public void normalize() { if (mRoot == null) { return; } clampOffsets(mRoot); Map<TreeNode, RectF> bounds = new HashMap<>(); Map<TreeNode, Integer> depths = new HashMap<>(); Map<TreeNode, TreeNode> parents = new HashMap<>(); getInfo(bounds, depths, parents, mRoot, /* x= */ 0, /* y= */ 0, /* depth= */ 0); // Sort the displays first by their depth in the tree, then by the distance of their top // left point from the root display's origin (0, 0). This way we process the displays // starting at the root and we push out a display if necessary. Comparator<TreeNode> comparator = (d1, d2) -> { if (d1 == d2) { return 0; } int compareDepths = Integer.compare(depths.get(d1), depths.get(d2)); if (compareDepths != 0) { return compareDepths; } RectF bounds1 = bounds.get(d1); RectF bounds2 = bounds.get(d2); return Double.compare(Math.hypot(bounds1.left, bounds1.top), Math.hypot(bounds2.left, bounds2.top)); }; List<TreeNode> displays = new ArrayList<>(bounds.keySet()); displays.sort(comparator); for (int i = 1; i < displays.size(); i++) { TreeNode targetDisplay = displays.get(i); TreeNode lastIntersectingSourceDisplay = null; float lastOffsetX = 0; float lastOffsetY = 0; for (int j = 0; j < i; j++) { TreeNode sourceDisplay = displays.get(j); RectF sourceBounds = bounds.get(sourceDisplay); RectF targetBounds = bounds.get(targetDisplay); if (!RectF.intersects(sourceBounds, targetBounds)) { continue; } // Find the offset by which to move the display. Pick the smaller one among the x // and y axes. float offsetX = targetBounds.left >= 0 ? sourceBounds.right - targetBounds.left : sourceBounds.left - targetBounds.right; float offsetY = targetBounds.top >= 0 ? sourceBounds.bottom - targetBounds.top : sourceBounds.top - targetBounds.bottom; if (Math.abs(offsetX) <= Math.abs(offsetY)) { targetBounds.left += offsetX; targetBounds.right += offsetX; // We need to also update the offset in the tree if (targetDisplay.mPosition == POSITION_TOP || targetDisplay.mPosition == POSITION_BOTTOM) { targetDisplay.mOffset += offsetX; } offsetY = 0; } else { targetBounds.top += offsetY; targetBounds.bottom += offsetY; // We need to also update the offset in the tree if (targetDisplay.mPosition == POSITION_LEFT || targetDisplay.mPosition == POSITION_RIGHT) { targetDisplay.mOffset += offsetY; } offsetX = 0; } lastIntersectingSourceDisplay = sourceDisplay; lastOffsetX = offsetX; lastOffsetY = offsetY; } // Now re-parent the target display to the last intersecting source display if it no // longer touches its parent. if (lastIntersectingSourceDisplay == null) { // There was no overlap. continue; } TreeNode parent = parents.get(targetDisplay); if (parent == lastIntersectingSourceDisplay) { // The displays are moved in such a way that they're adjacent to the intersecting // display. If the last intersecting display happens to be the parent then we // already know that the display is adjacent to its parent. continue; } RectF childBounds = bounds.get(targetDisplay); RectF parentBounds = bounds.get(parent); // Check that the edges are on the same line boolean areTouching = switch (targetDisplay.mPosition) { case POSITION_LEFT -> floatEquals(parentBounds.left, childBounds.right); case POSITION_RIGHT -> floatEquals(parentBounds.right, childBounds.left); case POSITION_TOP -> floatEquals(parentBounds.top, childBounds.bottom); case POSITION_BOTTOM -> floatEquals(parentBounds.bottom, childBounds.top); default -> throw new IllegalStateException( "Unexpected value: " + targetDisplay.mPosition); }; // Check that the offset is within bounds areTouching &= switch (targetDisplay.mPosition) { case POSITION_LEFT, POSITION_RIGHT -> childBounds.bottom + EPSILON >= parentBounds.top && childBounds.top <= parentBounds.bottom + EPSILON; case POSITION_TOP, POSITION_BOTTOM -> childBounds.right + EPSILON >= parentBounds.left && childBounds.left <= parentBounds.right + EPSILON; default -> throw new IllegalStateException( "Unexpected value: " + targetDisplay.mPosition); }; if (!areTouching) { // Re-parent the display. parent.mChildren.remove(targetDisplay); RectF lastIntersectingSourceDisplayBounds = bounds.get(lastIntersectingSourceDisplay); lastIntersectingSourceDisplay.mChildren.add(targetDisplay); if (lastOffsetX != 0) { targetDisplay.mPosition = lastOffsetX > 0 ? POSITION_RIGHT : POSITION_LEFT; targetDisplay.mOffset = childBounds.top - lastIntersectingSourceDisplayBounds.top; } else if (lastOffsetY != 0) { targetDisplay.mPosition = lastOffsetY > 0 ? POSITION_BOTTOM : POSITION_TOP; targetDisplay.mOffset = childBounds.left - lastIntersectingSourceDisplayBounds.left; } } } } /** /** * @return A deep copy of the topology that will not be modified by the system. * @return A deep copy of the topology that will not be modified by the system. */ */ Loading Loading @@ -441,145 +582,6 @@ public final class DisplayTopology implements Parcelable { } } } } /** * Update the topology to remove any overlaps between displays. */ @VisibleForTesting public void normalize() { if (mRoot == null) { return; } Map<TreeNode, RectF> bounds = new HashMap<>(); Map<TreeNode, Integer> depths = new HashMap<>(); Map<TreeNode, TreeNode> parents = new HashMap<>(); getInfo(bounds, depths, parents, mRoot, /* x= */ 0, /* y= */ 0, /* depth= */ 0); // Sort the displays first by their depth in the tree, then by the distance of their top // left point from the root display's origin (0, 0). This way we process the displays // starting at the root and we push out a display if necessary. Comparator<TreeNode> comparator = (d1, d2) -> { if (d1 == d2) { return 0; } int compareDepths = Integer.compare(depths.get(d1), depths.get(d2)); if (compareDepths != 0) { return compareDepths; } RectF bounds1 = bounds.get(d1); RectF bounds2 = bounds.get(d2); return Double.compare(Math.hypot(bounds1.left, bounds1.top), Math.hypot(bounds2.left, bounds2.top)); }; List<TreeNode> displays = new ArrayList<>(bounds.keySet()); displays.sort(comparator); for (int i = 1; i < displays.size(); i++) { TreeNode targetDisplay = displays.get(i); TreeNode lastIntersectingSourceDisplay = null; float lastOffsetX = 0; float lastOffsetY = 0; for (int j = 0; j < i; j++) { TreeNode sourceDisplay = displays.get(j); RectF sourceBounds = bounds.get(sourceDisplay); RectF targetBounds = bounds.get(targetDisplay); if (!RectF.intersects(sourceBounds, targetBounds)) { continue; } // Find the offset by which to move the display. Pick the smaller one among the x // and y axes. float offsetX = targetBounds.left >= 0 ? sourceBounds.right - targetBounds.left : sourceBounds.left - targetBounds.right; float offsetY = targetBounds.top >= 0 ? sourceBounds.bottom - targetBounds.top : sourceBounds.top - targetBounds.bottom; if (Math.abs(offsetX) <= Math.abs(offsetY)) { targetBounds.left += offsetX; targetBounds.right += offsetX; // We need to also update the offset in the tree if (targetDisplay.mPosition == POSITION_TOP || targetDisplay.mPosition == POSITION_BOTTOM) { targetDisplay.mOffset += offsetX; } offsetY = 0; } else { targetBounds.top += offsetY; targetBounds.bottom += offsetY; // We need to also update the offset in the tree if (targetDisplay.mPosition == POSITION_LEFT || targetDisplay.mPosition == POSITION_RIGHT) { targetDisplay.mOffset += offsetY; } offsetX = 0; } lastIntersectingSourceDisplay = sourceDisplay; lastOffsetX = offsetX; lastOffsetY = offsetY; } // Now re-parent the target display to the last intersecting source display if it no // longer touches its parent. if (lastIntersectingSourceDisplay == null) { // There was no overlap. continue; } TreeNode parent = parents.get(targetDisplay); if (parent == lastIntersectingSourceDisplay) { // The displays are moved in such a way that they're adjacent to the intersecting // display. If the last intersecting display happens to be the parent then we // already know that the display is adjacent to its parent. continue; } RectF childBounds = bounds.get(targetDisplay); RectF parentBounds = bounds.get(parent); // Check that the edges are on the same line boolean areTouching = switch (targetDisplay.mPosition) { case POSITION_LEFT -> floatEquals(parentBounds.left, childBounds.right); case POSITION_RIGHT -> floatEquals(parentBounds.right, childBounds.left); case POSITION_TOP -> floatEquals(parentBounds.top, childBounds.bottom); case POSITION_BOTTOM -> floatEquals(parentBounds.bottom, childBounds.top); default -> throw new IllegalStateException( "Unexpected value: " + targetDisplay.mPosition); }; // Check that the offset is within bounds areTouching &= switch (targetDisplay.mPosition) { case POSITION_LEFT, POSITION_RIGHT -> childBounds.bottom + EPSILON >= parentBounds.top && childBounds.top <= parentBounds.bottom + EPSILON; case POSITION_TOP, POSITION_BOTTOM -> childBounds.right + EPSILON >= parentBounds.left && childBounds.left <= parentBounds.right + EPSILON; default -> throw new IllegalStateException( "Unexpected value: " + targetDisplay.mPosition); }; if (!areTouching) { // Re-parent the display. parent.mChildren.remove(targetDisplay); RectF lastIntersectingSourceDisplayBounds = bounds.get(lastIntersectingSourceDisplay); lastIntersectingSourceDisplay.mChildren.add(targetDisplay); if (lastOffsetX != 0) { targetDisplay.mPosition = lastOffsetX > 0 ? POSITION_RIGHT : POSITION_LEFT; targetDisplay.mOffset = childBounds.top - lastIntersectingSourceDisplayBounds.top; } else if (lastOffsetY != 0) { targetDisplay.mPosition = lastOffsetY > 0 ? POSITION_BOTTOM : POSITION_TOP; targetDisplay.mOffset = childBounds.left - lastIntersectingSourceDisplayBounds.left; } } } } /** /** * Tests whether two brightness float values are within a small enough tolerance * Tests whether two brightness float values are within a small enough tolerance * of each other. * of each other. Loading @@ -605,6 +607,24 @@ public final class DisplayTopology implements Parcelable { return found; return found; } } /** * Ensure that the offsets of all displays within the given tree are within bounds. * @param display The starting node */ private void clampOffsets(TreeNode display) { if (display == null) { return; } for (TreeNode child : display.mChildren) { if (child.mPosition == POSITION_LEFT || child.mPosition == POSITION_RIGHT) { child.mOffset = MathUtils.constrain(child.mOffset, -child.mHeight, display.mHeight); } else if (child.mPosition == POSITION_TOP || child.mPosition == POSITION_BOTTOM) { child.mOffset = MathUtils.constrain(child.mOffset, -child.mWidth, display.mWidth); } clampOffsets(child); } } public static final class TreeNode implements Parcelable { public static final class TreeNode implements Parcelable { public static final int POSITION_LEFT = 0; public static final int POSITION_LEFT = 0; public static final int POSITION_TOP = 1; public static final int POSITION_TOP = 1; Loading core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt +106 −183 File changed.Preview size limit exceeded, changes collapsed. Show changes services/core/java/com/android/server/display/DisplayTopologyCoordinator.java +2 −1 Original line number Original line Diff line number Diff line Loading @@ -100,13 +100,14 @@ class DisplayTopologyCoordinator { */ */ DisplayTopology getTopology() { DisplayTopology getTopology() { synchronized (mSyncRoot) { synchronized (mSyncRoot) { return mTopology; return mTopology.copy(); } } } } void setTopology(DisplayTopology topology) { void setTopology(DisplayTopology topology) { synchronized (mSyncRoot) { synchronized (mSyncRoot) { mTopology = topology; mTopology = topology; mTopology.normalize(); sendTopologyUpdateLocked(); sendTopologyUpdateLocked(); } } } } Loading services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt +18 −0 Original line number Original line Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.hardware.display.DisplayTopology import android.util.DisplayMetrics import android.util.DisplayMetrics import android.view.Display import android.view.Display import android.view.DisplayInfo import android.view.DisplayInfo import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Before import org.junit.Test import org.junit.Test import org.mockito.ArgumentMatchers.anyFloat import org.mockito.ArgumentMatchers.anyFloat Loading Loading @@ -87,4 +88,21 @@ class DisplayTopologyCoordinatorTest { verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat()) verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat()) verify(mockTopologyChangedCallback, never()).invoke(any()) verify(mockTopologyChangedCallback, never()).invoke(any()) } } @Test fun getTopology_copy() { assertThat(coordinator.topology).isEqualTo(mockTopologyCopy) } @Test fun setTopology_normalize() { val topology = mock<DisplayTopology>() val topologyCopy = mock<DisplayTopology>() whenever(topology.copy()).thenReturn(topologyCopy) coordinator.topology = topology verify(topology).normalize() verify(mockTopologyChangedCallback).invoke(topologyCopy) } } } No newline at end of file Loading
core/java/android/hardware/display/DisplayTopology.java +159 −139 Original line number Original line Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.graphics.RectF; import android.os.Parcel; import android.os.Parcel; import android.os.Parcelable; import android.os.Parcelable; import android.util.IndentingPrintWriter; import android.util.IndentingPrintWriter; import android.util.MathUtils; import android.util.Pair; import android.util.Pair; import android.util.Slog; import android.util.Slog; import android.view.Display; import android.view.Display; Loading Loading @@ -283,6 +284,146 @@ public final class DisplayTopology implements Parcelable { normalize(); normalize(); } } /** * Clamp offsets and remove any overlaps between displays. */ public void normalize() { if (mRoot == null) { return; } clampOffsets(mRoot); Map<TreeNode, RectF> bounds = new HashMap<>(); Map<TreeNode, Integer> depths = new HashMap<>(); Map<TreeNode, TreeNode> parents = new HashMap<>(); getInfo(bounds, depths, parents, mRoot, /* x= */ 0, /* y= */ 0, /* depth= */ 0); // Sort the displays first by their depth in the tree, then by the distance of their top // left point from the root display's origin (0, 0). This way we process the displays // starting at the root and we push out a display if necessary. Comparator<TreeNode> comparator = (d1, d2) -> { if (d1 == d2) { return 0; } int compareDepths = Integer.compare(depths.get(d1), depths.get(d2)); if (compareDepths != 0) { return compareDepths; } RectF bounds1 = bounds.get(d1); RectF bounds2 = bounds.get(d2); return Double.compare(Math.hypot(bounds1.left, bounds1.top), Math.hypot(bounds2.left, bounds2.top)); }; List<TreeNode> displays = new ArrayList<>(bounds.keySet()); displays.sort(comparator); for (int i = 1; i < displays.size(); i++) { TreeNode targetDisplay = displays.get(i); TreeNode lastIntersectingSourceDisplay = null; float lastOffsetX = 0; float lastOffsetY = 0; for (int j = 0; j < i; j++) { TreeNode sourceDisplay = displays.get(j); RectF sourceBounds = bounds.get(sourceDisplay); RectF targetBounds = bounds.get(targetDisplay); if (!RectF.intersects(sourceBounds, targetBounds)) { continue; } // Find the offset by which to move the display. Pick the smaller one among the x // and y axes. float offsetX = targetBounds.left >= 0 ? sourceBounds.right - targetBounds.left : sourceBounds.left - targetBounds.right; float offsetY = targetBounds.top >= 0 ? sourceBounds.bottom - targetBounds.top : sourceBounds.top - targetBounds.bottom; if (Math.abs(offsetX) <= Math.abs(offsetY)) { targetBounds.left += offsetX; targetBounds.right += offsetX; // We need to also update the offset in the tree if (targetDisplay.mPosition == POSITION_TOP || targetDisplay.mPosition == POSITION_BOTTOM) { targetDisplay.mOffset += offsetX; } offsetY = 0; } else { targetBounds.top += offsetY; targetBounds.bottom += offsetY; // We need to also update the offset in the tree if (targetDisplay.mPosition == POSITION_LEFT || targetDisplay.mPosition == POSITION_RIGHT) { targetDisplay.mOffset += offsetY; } offsetX = 0; } lastIntersectingSourceDisplay = sourceDisplay; lastOffsetX = offsetX; lastOffsetY = offsetY; } // Now re-parent the target display to the last intersecting source display if it no // longer touches its parent. if (lastIntersectingSourceDisplay == null) { // There was no overlap. continue; } TreeNode parent = parents.get(targetDisplay); if (parent == lastIntersectingSourceDisplay) { // The displays are moved in such a way that they're adjacent to the intersecting // display. If the last intersecting display happens to be the parent then we // already know that the display is adjacent to its parent. continue; } RectF childBounds = bounds.get(targetDisplay); RectF parentBounds = bounds.get(parent); // Check that the edges are on the same line boolean areTouching = switch (targetDisplay.mPosition) { case POSITION_LEFT -> floatEquals(parentBounds.left, childBounds.right); case POSITION_RIGHT -> floatEquals(parentBounds.right, childBounds.left); case POSITION_TOP -> floatEquals(parentBounds.top, childBounds.bottom); case POSITION_BOTTOM -> floatEquals(parentBounds.bottom, childBounds.top); default -> throw new IllegalStateException( "Unexpected value: " + targetDisplay.mPosition); }; // Check that the offset is within bounds areTouching &= switch (targetDisplay.mPosition) { case POSITION_LEFT, POSITION_RIGHT -> childBounds.bottom + EPSILON >= parentBounds.top && childBounds.top <= parentBounds.bottom + EPSILON; case POSITION_TOP, POSITION_BOTTOM -> childBounds.right + EPSILON >= parentBounds.left && childBounds.left <= parentBounds.right + EPSILON; default -> throw new IllegalStateException( "Unexpected value: " + targetDisplay.mPosition); }; if (!areTouching) { // Re-parent the display. parent.mChildren.remove(targetDisplay); RectF lastIntersectingSourceDisplayBounds = bounds.get(lastIntersectingSourceDisplay); lastIntersectingSourceDisplay.mChildren.add(targetDisplay); if (lastOffsetX != 0) { targetDisplay.mPosition = lastOffsetX > 0 ? POSITION_RIGHT : POSITION_LEFT; targetDisplay.mOffset = childBounds.top - lastIntersectingSourceDisplayBounds.top; } else if (lastOffsetY != 0) { targetDisplay.mPosition = lastOffsetY > 0 ? POSITION_BOTTOM : POSITION_TOP; targetDisplay.mOffset = childBounds.left - lastIntersectingSourceDisplayBounds.left; } } } } /** /** * @return A deep copy of the topology that will not be modified by the system. * @return A deep copy of the topology that will not be modified by the system. */ */ Loading Loading @@ -441,145 +582,6 @@ public final class DisplayTopology implements Parcelable { } } } } /** * Update the topology to remove any overlaps between displays. */ @VisibleForTesting public void normalize() { if (mRoot == null) { return; } Map<TreeNode, RectF> bounds = new HashMap<>(); Map<TreeNode, Integer> depths = new HashMap<>(); Map<TreeNode, TreeNode> parents = new HashMap<>(); getInfo(bounds, depths, parents, mRoot, /* x= */ 0, /* y= */ 0, /* depth= */ 0); // Sort the displays first by their depth in the tree, then by the distance of their top // left point from the root display's origin (0, 0). This way we process the displays // starting at the root and we push out a display if necessary. Comparator<TreeNode> comparator = (d1, d2) -> { if (d1 == d2) { return 0; } int compareDepths = Integer.compare(depths.get(d1), depths.get(d2)); if (compareDepths != 0) { return compareDepths; } RectF bounds1 = bounds.get(d1); RectF bounds2 = bounds.get(d2); return Double.compare(Math.hypot(bounds1.left, bounds1.top), Math.hypot(bounds2.left, bounds2.top)); }; List<TreeNode> displays = new ArrayList<>(bounds.keySet()); displays.sort(comparator); for (int i = 1; i < displays.size(); i++) { TreeNode targetDisplay = displays.get(i); TreeNode lastIntersectingSourceDisplay = null; float lastOffsetX = 0; float lastOffsetY = 0; for (int j = 0; j < i; j++) { TreeNode sourceDisplay = displays.get(j); RectF sourceBounds = bounds.get(sourceDisplay); RectF targetBounds = bounds.get(targetDisplay); if (!RectF.intersects(sourceBounds, targetBounds)) { continue; } // Find the offset by which to move the display. Pick the smaller one among the x // and y axes. float offsetX = targetBounds.left >= 0 ? sourceBounds.right - targetBounds.left : sourceBounds.left - targetBounds.right; float offsetY = targetBounds.top >= 0 ? sourceBounds.bottom - targetBounds.top : sourceBounds.top - targetBounds.bottom; if (Math.abs(offsetX) <= Math.abs(offsetY)) { targetBounds.left += offsetX; targetBounds.right += offsetX; // We need to also update the offset in the tree if (targetDisplay.mPosition == POSITION_TOP || targetDisplay.mPosition == POSITION_BOTTOM) { targetDisplay.mOffset += offsetX; } offsetY = 0; } else { targetBounds.top += offsetY; targetBounds.bottom += offsetY; // We need to also update the offset in the tree if (targetDisplay.mPosition == POSITION_LEFT || targetDisplay.mPosition == POSITION_RIGHT) { targetDisplay.mOffset += offsetY; } offsetX = 0; } lastIntersectingSourceDisplay = sourceDisplay; lastOffsetX = offsetX; lastOffsetY = offsetY; } // Now re-parent the target display to the last intersecting source display if it no // longer touches its parent. if (lastIntersectingSourceDisplay == null) { // There was no overlap. continue; } TreeNode parent = parents.get(targetDisplay); if (parent == lastIntersectingSourceDisplay) { // The displays are moved in such a way that they're adjacent to the intersecting // display. If the last intersecting display happens to be the parent then we // already know that the display is adjacent to its parent. continue; } RectF childBounds = bounds.get(targetDisplay); RectF parentBounds = bounds.get(parent); // Check that the edges are on the same line boolean areTouching = switch (targetDisplay.mPosition) { case POSITION_LEFT -> floatEquals(parentBounds.left, childBounds.right); case POSITION_RIGHT -> floatEquals(parentBounds.right, childBounds.left); case POSITION_TOP -> floatEquals(parentBounds.top, childBounds.bottom); case POSITION_BOTTOM -> floatEquals(parentBounds.bottom, childBounds.top); default -> throw new IllegalStateException( "Unexpected value: " + targetDisplay.mPosition); }; // Check that the offset is within bounds areTouching &= switch (targetDisplay.mPosition) { case POSITION_LEFT, POSITION_RIGHT -> childBounds.bottom + EPSILON >= parentBounds.top && childBounds.top <= parentBounds.bottom + EPSILON; case POSITION_TOP, POSITION_BOTTOM -> childBounds.right + EPSILON >= parentBounds.left && childBounds.left <= parentBounds.right + EPSILON; default -> throw new IllegalStateException( "Unexpected value: " + targetDisplay.mPosition); }; if (!areTouching) { // Re-parent the display. parent.mChildren.remove(targetDisplay); RectF lastIntersectingSourceDisplayBounds = bounds.get(lastIntersectingSourceDisplay); lastIntersectingSourceDisplay.mChildren.add(targetDisplay); if (lastOffsetX != 0) { targetDisplay.mPosition = lastOffsetX > 0 ? POSITION_RIGHT : POSITION_LEFT; targetDisplay.mOffset = childBounds.top - lastIntersectingSourceDisplayBounds.top; } else if (lastOffsetY != 0) { targetDisplay.mPosition = lastOffsetY > 0 ? POSITION_BOTTOM : POSITION_TOP; targetDisplay.mOffset = childBounds.left - lastIntersectingSourceDisplayBounds.left; } } } } /** /** * Tests whether two brightness float values are within a small enough tolerance * Tests whether two brightness float values are within a small enough tolerance * of each other. * of each other. Loading @@ -605,6 +607,24 @@ public final class DisplayTopology implements Parcelable { return found; return found; } } /** * Ensure that the offsets of all displays within the given tree are within bounds. * @param display The starting node */ private void clampOffsets(TreeNode display) { if (display == null) { return; } for (TreeNode child : display.mChildren) { if (child.mPosition == POSITION_LEFT || child.mPosition == POSITION_RIGHT) { child.mOffset = MathUtils.constrain(child.mOffset, -child.mHeight, display.mHeight); } else if (child.mPosition == POSITION_TOP || child.mPosition == POSITION_BOTTOM) { child.mOffset = MathUtils.constrain(child.mOffset, -child.mWidth, display.mWidth); } clampOffsets(child); } } public static final class TreeNode implements Parcelable { public static final class TreeNode implements Parcelable { public static final int POSITION_LEFT = 0; public static final int POSITION_LEFT = 0; public static final int POSITION_TOP = 1; public static final int POSITION_TOP = 1; Loading
core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt +106 −183 File changed.Preview size limit exceeded, changes collapsed. Show changes
services/core/java/com/android/server/display/DisplayTopologyCoordinator.java +2 −1 Original line number Original line Diff line number Diff line Loading @@ -100,13 +100,14 @@ class DisplayTopologyCoordinator { */ */ DisplayTopology getTopology() { DisplayTopology getTopology() { synchronized (mSyncRoot) { synchronized (mSyncRoot) { return mTopology; return mTopology.copy(); } } } } void setTopology(DisplayTopology topology) { void setTopology(DisplayTopology topology) { synchronized (mSyncRoot) { synchronized (mSyncRoot) { mTopology = topology; mTopology = topology; mTopology.normalize(); sendTopologyUpdateLocked(); sendTopologyUpdateLocked(); } } } } Loading
services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt +18 −0 Original line number Original line Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.hardware.display.DisplayTopology import android.util.DisplayMetrics import android.util.DisplayMetrics import android.view.Display import android.view.Display import android.view.DisplayInfo import android.view.DisplayInfo import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Before import org.junit.Test import org.junit.Test import org.mockito.ArgumentMatchers.anyFloat import org.mockito.ArgumentMatchers.anyFloat Loading Loading @@ -87,4 +88,21 @@ class DisplayTopologyCoordinatorTest { verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat()) verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat()) verify(mockTopologyChangedCallback, never()).invoke(any()) verify(mockTopologyChangedCallback, never()).invoke(any()) } } @Test fun getTopology_copy() { assertThat(coordinator.topology).isEqualTo(mockTopologyCopy) } @Test fun setTopology_normalize() { val topology = mock<DisplayTopology>() val topologyCopy = mock<DisplayTopology>() whenever(topology.copy()).thenReturn(topologyCopy) coordinator.topology = topology verify(topology).normalize() verify(mockTopologyChangedCallback).invoke(topologyCopy) } } } No newline at end of file