Loading services/core/java/com/android/server/display/DisplayManagerService.java +6 −0 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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()); } Loading services/core/java/com/android/server/display/DisplayTopology.java +81 −19 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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 { 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); mPrimaryDisplayId = Display.INVALID_DISPLAY; } Slog.i(TAG, "Primary display with ID " + displayId + " removed, new primary display: " + mPrimaryDisplayId); } else { Slog.i(TAG, "Display with ID " + displayId + " removed"); } } Loading @@ -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. Loading Loading @@ -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 { Loading services/core/java/com/android/server/display/DisplayTopologyCoordinator.java +10 −0 Original line number Diff line number Diff line Loading @@ -65,6 +65,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. Loading services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt +130 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.display import android.view.Display import com.google.common.truth.Truth.assertThat import org.junit.Test Loading @@ -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 Loading @@ -40,7 +41,7 @@ class DisplayTopologyTest { } @Test fun twoDisplays() { fun addTwoDisplays() { val displayId1 = 1 val width1 = 800.0 val height1 = 600.0 Loading Loading @@ -71,7 +72,7 @@ class DisplayTopologyTest { } @Test fun manyDisplays() { fun addManyDisplays() { val displayId1 = 1 val width1 = 800.0 val height1 = 600.0 Loading Loading @@ -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 Loading
services/core/java/com/android/server/display/DisplayManagerService.java +6 −0 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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()); } Loading
services/core/java/com/android/server/display/DisplayTopology.java +81 −19 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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 { 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); mPrimaryDisplayId = Display.INVALID_DISPLAY; } Slog.i(TAG, "Primary display with ID " + displayId + " removed, new primary display: " + mPrimaryDisplayId); } else { Slog.i(TAG, "Display with ID " + displayId + " removed"); } } Loading @@ -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. Loading Loading @@ -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 { Loading
services/core/java/com/android/server/display/DisplayTopologyCoordinator.java +10 −0 Original line number Diff line number Diff line Loading @@ -65,6 +65,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. Loading
services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt +130 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.display import android.view.Display import com.google.common.truth.Truth.assertThat import org.junit.Test Loading @@ -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 Loading @@ -40,7 +41,7 @@ class DisplayTopologyTest { } @Test fun twoDisplays() { fun addTwoDisplays() { val displayId1 = 1 val width1 = 800.0 val height1 = 600.0 Loading Loading @@ -71,7 +72,7 @@ class DisplayTopologyTest { } @Test fun manyDisplays() { fun addManyDisplays() { val displayId1 = 1 val width1 = 800.0 val height1 = 600.0 Loading Loading @@ -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