diff options
4 files changed, 227 insertions, 22 deletions
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 3dc531e86da1..d71826ffc273 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2302,6 +2302,9 @@ public final class DisplayManagerService extends SystemService { updateLogicalDisplayState(display); mExternalDisplayPolicy.handleLogicalDisplayAddedLocked(display); + if (mDisplayTopologyCoordinator != null) { + mDisplayTopologyCoordinator.onDisplayAdded(display.getDisplayInfoLocked()); + } } private void handleLogicalDisplayChangedLocked(@NonNull LogicalDisplay display) { @@ -2389,6 +2392,9 @@ public final class DisplayManagerService extends SystemService { } else { releaseDisplayAndEmitEvent(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); } + if (mDisplayTopologyCoordinator != null) { + mDisplayTopologyCoordinator.onDisplayRemoved(display.getDisplayIdLocked()); + } Slog.i(TAG, "Logical display removed: " + display.getDisplayIdLocked()); } diff --git a/services/core/java/com/android/server/display/DisplayTopology.java b/services/core/java/com/android/server/display/DisplayTopology.java index 90038a09850f..b01d61721c9c 100644 --- a/services/core/java/com/android/server/display/DisplayTopology.java +++ b/services/core/java/com/android/server/display/DisplayTopology.java @@ -20,12 +20,15 @@ import android.annotation.Nullable; import android.util.IndentingPrintWriter; import android.util.Pair; import android.util.Slog; +import android.view.Display; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; +import java.util.Queue; /** * Represents the relative placement of extended displays. @@ -45,35 +48,50 @@ class DisplayTopology { * This is not necessarily the same as the default display. */ @VisibleForTesting - int mPrimaryDisplayId; + int mPrimaryDisplayId = Display.INVALID_DISPLAY; /** * Add a display to the topology. * If this is the second display in the topology, it will be placed above the first display. * Subsequent displays will be places to the left or right of the second display. - * @param displayId The ID of the display + * @param displayId The logical display ID * @param width The width of the display * @param height The height of the display */ void addDisplay(int displayId, double width, double height) { - if (mRoot == null) { - mRoot = new TreeNode(displayId, width, height, /* position= */ null, /* offset= */ 0); - mPrimaryDisplayId = displayId; - Slog.i(TAG, "First display added: " + mRoot); - } else if (mRoot.mChildren.isEmpty()) { - // This is the 2nd display. Align the middles of the top and bottom edges. - double offset = mRoot.mWidth / 2 - width / 2; - TreeNode display = new TreeNode(displayId, width, height, - TreeNode.Position.POSITION_TOP, offset); - mRoot.mChildren.add(display); - Slog.i(TAG, "Second display added: " + display + ", parent ID: " + mRoot.mDisplayId); + addDisplay(displayId, width, height, /* shouldLog= */ true); + } + + /** + * Remove a display from the topology. + * The default topology is created from the remaining displays, as if they were reconnected + * one by one. + * @param displayId The logical display ID + */ + void removeDisplay(int displayId) { + if (!isDisplayPresent(displayId, mRoot)) { + return; + } + Queue<TreeNode> queue = new LinkedList<>(); + queue.add(mRoot); + mRoot = null; + while (!queue.isEmpty()) { + TreeNode node = queue.poll(); + if (node.mDisplayId != displayId) { + addDisplay(node.mDisplayId, node.mWidth, node.mHeight, /* shouldLog= */ false); + } + queue.addAll(node.mChildren); + } + if (mPrimaryDisplayId == displayId) { + if (mRoot != null) { + mPrimaryDisplayId = mRoot.mDisplayId; + } else { + mPrimaryDisplayId = Display.INVALID_DISPLAY; + } + Slog.i(TAG, "Primary display with ID " + displayId + + " removed, new primary display: " + mPrimaryDisplayId); } else { - TreeNode rightMostDisplay = findRightMostDisplay(mRoot, mRoot.mWidth).first; - TreeNode newDisplay = new TreeNode(displayId, width, height, - TreeNode.Position.POSITION_RIGHT, /* offset= */ 0); - rightMostDisplay.mChildren.add(newDisplay); - Slog.i(TAG, "Display added: " + newDisplay + ", parent ID: " - + rightMostDisplay.mDisplayId); + Slog.i(TAG, "Display with ID " + displayId + " removed"); } } @@ -97,6 +115,35 @@ class DisplayTopology { } } + private void addDisplay(int displayId, double width, double height, boolean shouldLog) { + if (mRoot == null) { + mRoot = new TreeNode(displayId, width, height, /* position= */ null, /* offset= */ 0); + mPrimaryDisplayId = displayId; + if (shouldLog) { + Slog.i(TAG, "First display added: " + mRoot); + } + } else if (mRoot.mChildren.isEmpty()) { + // This is the 2nd display. Align the middles of the top and bottom edges. + double offset = mRoot.mWidth / 2 - width / 2; + TreeNode display = new TreeNode(displayId, width, height, + TreeNode.Position.POSITION_TOP, offset); + mRoot.mChildren.add(display); + if (shouldLog) { + Slog.i(TAG, "Second display added: " + display + ", parent ID: " + + mRoot.mDisplayId); + } + } else { + TreeNode rightMostDisplay = findRightMostDisplay(mRoot, mRoot.mWidth).first; + TreeNode newDisplay = new TreeNode(displayId, width, height, + TreeNode.Position.POSITION_RIGHT, /* offset= */ 0); + rightMostDisplay.mChildren.add(newDisplay); + if (shouldLog) { + Slog.i(TAG, "Display added: " + newDisplay + ", parent ID: " + + rightMostDisplay.mDisplayId); + } + } + } + /** * @param display The display from which the search should start. * @param xPos The x position of the right edge of that display. @@ -126,6 +173,21 @@ class DisplayTopology { return result; } + private boolean isDisplayPresent(int displayId, TreeNode node) { + if (node == null) { + return false; + } + if (node.mDisplayId == displayId) { + return true; + } + for (TreeNode child : node.mChildren) { + if (isDisplayPresent(displayId, child)) { + return true; + } + } + return false; + } + @VisibleForTesting static class TreeNode { diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java index cbd224c3d842..46358dfd90ec 100644 --- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java +++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java @@ -66,6 +66,16 @@ class DisplayTopologyCoordinator { } /** + * Remove a display from the topology. + * @param displayId The logical display ID + */ + void onDisplayRemoved(int displayId) { + synchronized (mLock) { + mTopology.removeDisplay(displayId); + } + } + + /** * Print the object's state and debug information into the given stream. * @param pw The stream to dump information to. */ diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt index 1fad14b0a062..f3a8d8415c19 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt @@ -16,6 +16,7 @@ package com.android.server.display +import android.view.Display import com.google.common.truth.Truth.assertThat import org.junit.Test @@ -23,7 +24,7 @@ class DisplayTopologyTest { private val topology = DisplayTopology() @Test - fun oneDisplay() { + fun addOneDisplay() { val displayId = 1 val width = 800.0 val height = 600.0 @@ -40,7 +41,7 @@ class DisplayTopologyTest { } @Test - fun twoDisplays() { + fun addTwoDisplays() { val displayId1 = 1 val width1 = 800.0 val height1 = 600.0 @@ -71,7 +72,7 @@ class DisplayTopologyTest { } @Test - fun manyDisplays() { + fun addManyDisplays() { val displayId1 = 1 val width1 = 800.0 val height1 = 600.0 @@ -118,4 +119,130 @@ class DisplayTopologyTest { assertThat(display.mOffset).isEqualTo(0) } } + + @Test + fun removeDisplays() { + val displayId1 = 1 + val width1 = 800.0 + val height1 = 600.0 + + val displayId2 = 2 + val width2 = 1000.0 + val height2 = 1500.0 + + topology.addDisplay(displayId1, width1, height1) + topology.addDisplay(displayId2, width2, height2) + + val noOfDisplays = 30 + for (i in 3..noOfDisplays) { + topology.addDisplay(/* displayId= */ i, width1, height1) + } + + var removedDisplays = arrayOf(20) + topology.removeDisplay(20) + + assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1) + + var display1 = topology.mRoot!! + assertThat(display1.mDisplayId).isEqualTo(displayId1) + assertThat(display1.mWidth).isEqualTo(width1) + assertThat(display1.mHeight).isEqualTo(height1) + assertThat(display1.mChildren).hasSize(1) + + var display2 = display1.mChildren[0] + assertThat(display2.mDisplayId).isEqualTo(displayId2) + assertThat(display2.mWidth).isEqualTo(width2) + assertThat(display2.mHeight).isEqualTo(height2) + assertThat(display2.mChildren).hasSize(1) + assertThat(display2.mPosition).isEqualTo( + DisplayTopology.TreeNode.Position.POSITION_TOP) + assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2) + + var display = display2 + for (i in 3..noOfDisplays) { + if (i in removedDisplays) { + continue + } + display = display.mChildren[0] + assertThat(display.mDisplayId).isEqualTo(i) + assertThat(display.mWidth).isEqualTo(width1) + assertThat(display.mHeight).isEqualTo(height1) + // The last display should have no children + assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0) + assertThat(display.mPosition).isEqualTo( + DisplayTopology.TreeNode.Position.POSITION_RIGHT) + assertThat(display.mOffset).isEqualTo(0) + } + + topology.removeDisplay(22) + removedDisplays += 22 + topology.removeDisplay(23) + removedDisplays += 23 + topology.removeDisplay(25) + removedDisplays += 25 + + assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1) + + display1 = topology.mRoot!! + assertThat(display1.mDisplayId).isEqualTo(displayId1) + assertThat(display1.mWidth).isEqualTo(width1) + assertThat(display1.mHeight).isEqualTo(height1) + assertThat(display1.mChildren).hasSize(1) + + display2 = display1.mChildren[0] + assertThat(display2.mDisplayId).isEqualTo(displayId2) + assertThat(display2.mWidth).isEqualTo(width2) + assertThat(display2.mHeight).isEqualTo(height2) + assertThat(display2.mChildren).hasSize(1) + assertThat(display2.mPosition).isEqualTo( + DisplayTopology.TreeNode.Position.POSITION_TOP) + assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2) + + display = display2 + for (i in 3..noOfDisplays) { + if (i in removedDisplays) { + continue + } + display = display.mChildren[0] + assertThat(display.mDisplayId).isEqualTo(i) + assertThat(display.mWidth).isEqualTo(width1) + assertThat(display.mHeight).isEqualTo(height1) + // The last display should have no children + assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0) + assertThat(display.mPosition).isEqualTo( + DisplayTopology.TreeNode.Position.POSITION_RIGHT) + assertThat(display.mOffset).isEqualTo(0) + } + } + + @Test + fun removeAllDisplays() { + val displayId = 1 + val width = 800.0 + val height = 600.0 + + topology.addDisplay(displayId, width, height) + topology.removeDisplay(displayId) + + assertThat(topology.mPrimaryDisplayId).isEqualTo(Display.INVALID_DISPLAY) + assertThat(topology.mRoot).isNull() + } + + @Test + fun removeDisplayThatDoesNotExist() { + val displayId = 1 + val width = 800.0 + val height = 600.0 + + topology.addDisplay(displayId, width, height) + topology.removeDisplay(3) + + assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId) + + val display = topology.mRoot!! + assertThat(display.mDisplayId).isEqualTo(displayId) + assertThat(display.mWidth).isEqualTo(width) + assertThat(display.mHeight).isEqualTo(height) + assertThat(display.mChildren).isEmpty() + } }
\ No newline at end of file |