Loading core/java/android/hardware/display/DisplayTopology.java +29 −4 Original line number Diff line number Diff line Loading @@ -128,15 +128,39 @@ public final class DisplayTopology implements Parcelable { addDisplay(displayId, width, height, /* shouldLog= */ true); } /** * Update the size of a display and normalize the topology. * @param displayId The logical display ID * @param width The new width * @param height The new height * @return True if the topology has changed. */ public boolean updateDisplay(int displayId, float width, float height) { TreeNode display = findDisplay(displayId, mRoot); if (display == null) { return false; } if (floatEquals(display.mWidth, width) && floatEquals(display.mHeight, height)) { return false; } display.mWidth = width; display.mHeight = height; normalize(); Slog.i(TAG, "Display with ID " + displayId + " updated, new width: " + width + ", new height: " + height); return 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 * @return True if the display was present in the topology and removed. */ public void removeDisplay(int displayId) { public boolean removeDisplay(int displayId) { if (findDisplay(displayId, mRoot) == null) { return; return false; } Queue<TreeNode> queue = new ArrayDeque<>(); queue.add(mRoot); Loading @@ -159,6 +183,7 @@ public final class DisplayTopology implements Parcelable { } else { Slog.i(TAG, "Display with ID " + displayId + " removed"); } return true; } /** Loading Loading @@ -685,12 +710,12 @@ public final class DisplayTopology implements Parcelable { /** * The width of the display in density-independent pixels (dp). */ private final float mWidth; private float mWidth; /** * The height of the display in density-independent pixels (dp). */ private final float mHeight; private float mHeight; /** * The position of this display relative to its parent. Loading core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt +78 −8 Original line number Diff line number Diff line Loading @@ -86,7 +86,7 @@ class DisplayTopologyTest { verifyDisplay(display1, displayId1, width1, height1, noOfChildren = 1) val display2 = display1.children[0] verifyDisplay(display1.children[0], displayId2, width2, height2, POSITION_TOP, verifyDisplay(display2, displayId2, width2, height2, POSITION_TOP, offset = width1 / 2 - width2 / 2, noOfChildren = 1) var display = display2 Loading @@ -98,6 +98,76 @@ class DisplayTopologyTest { } } @Test fun updateDisplay() { val displayId = 1 val width = 800f val height = 600f val newWidth = 1000f val newHeight = 500f topology.addDisplay(displayId, width, height) assertThat(topology.updateDisplay(displayId, newWidth, newHeight)).isTrue() assertThat(topology.primaryDisplayId).isEqualTo(displayId) verifyDisplay(topology.root!!, displayId, newWidth, newHeight, noOfChildren = 0) } @Test fun updateDisplay_notUpdated() { val displayId = 1 val width = 800f val height = 600f topology.addDisplay(displayId, width, height) // Same size assertThat(topology.updateDisplay(displayId, width, height)).isFalse() // Display doesn't exist assertThat(topology.updateDisplay(/* displayId= */ 100, width, height)).isFalse() assertThat(topology.primaryDisplayId).isEqualTo(displayId) verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0) } @Test fun updateDisplayDoesNotAffectDefaultTopology() { val width1 = 700f val height = 600f topology.addDisplay(/* displayId= */ 1, width1, height) val width2 = 800f val noOfDisplays = 30 for (i in 2..noOfDisplays) { topology.addDisplay(/* displayId= */ i, width2, height) } val displaysToUpdate = arrayOf(3, 7, 18) val newWidth = 1000f val newHeight = 1500f for (i in displaysToUpdate) { assertThat(topology.updateDisplay(/* displayId= */ i, newWidth, newHeight)).isTrue() } assertThat(topology.primaryDisplayId).isEqualTo(1) val display1 = topology.root!! verifyDisplay(display1, id = 1, width1, height, noOfChildren = 1) val display2 = display1.children[0] verifyDisplay(display2, id = 2, width2, height, POSITION_TOP, offset = width1 / 2 - width2 / 2, noOfChildren = 1) var display = display2 for (i in 3..noOfDisplays) { display = display.children[0] // The last display should have no children verifyDisplay(display, id = i, if (i in displaysToUpdate) newWidth else width2, if (i in displaysToUpdate) newHeight else height, POSITION_RIGHT, offset = 0f, noOfChildren = if (i < noOfDisplays) 1 else 0) } } @Test fun removeDisplays() { val displayId1 = 1 Loading @@ -117,7 +187,7 @@ class DisplayTopologyTest { } var removedDisplays = arrayOf(20) topology.removeDisplay(20) assertThat(topology.removeDisplay(20)).isTrue() assertThat(topology.primaryDisplayId).isEqualTo(displayId1) Loading @@ -139,11 +209,11 @@ class DisplayTopologyTest { noOfChildren = if (i < noOfDisplays) 1 else 0) } topology.removeDisplay(22) assertThat(topology.removeDisplay(22)).isTrue() removedDisplays += 22 topology.removeDisplay(23) assertThat(topology.removeDisplay(23)).isTrue() removedDisplays += 23 topology.removeDisplay(25) assertThat(topology.removeDisplay(25)).isTrue() removedDisplays += 25 assertThat(topology.primaryDisplayId).isEqualTo(displayId1) Loading Loading @@ -174,7 +244,7 @@ class DisplayTopologyTest { val height = 600f topology.addDisplay(displayId, width, height) topology.removeDisplay(displayId) assertThat(topology.removeDisplay(displayId)).isTrue() assertThat(topology.primaryDisplayId).isEqualTo(Display.INVALID_DISPLAY) assertThat(topology.root).isNull() Loading @@ -187,7 +257,7 @@ class DisplayTopologyTest { val height = 600f topology.addDisplay(displayId, width, height) topology.removeDisplay(3) assertThat(topology.removeDisplay(3)).isFalse() assertThat(topology.primaryDisplayId).isEqualTo(displayId) verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0) Loading @@ -203,7 +273,7 @@ class DisplayTopologyTest { topology = DisplayTopology(/* root= */ null, displayId2) topology.addDisplay(displayId1, width, height) topology.addDisplay(displayId2, width, height) topology.removeDisplay(displayId2) assertThat(topology.removeDisplay(displayId2)).isTrue() assertThat(topology.primaryDisplayId).isEqualTo(displayId1) verifyDisplay(topology.root!!, displayId1, width, height, noOfChildren = 0) Loading services/core/java/com/android/server/display/DisplayManagerService.java +4 −0 Original line number Diff line number Diff line Loading @@ -2400,6 +2400,10 @@ public final class DisplayManagerService extends SystemService { sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED); applyDisplayChangedLocked(display); if (mDisplayTopologyCoordinator != null) { mDisplayTopologyCoordinator.onDisplayChanged(display.getDisplayInfoLocked()); } } private void applyDisplayChangedLocked(@NonNull LogicalDisplay display) { Loading services/core/java/com/android/server/display/DisplayTopologyCoordinator.java +15 −2 Original line number Diff line number Diff line Loading @@ -84,16 +84,29 @@ class DisplayTopologyCoordinator { } } /** * Update the topology with display changes. * @param info The new display info */ void onDisplayChanged(DisplayInfo info) { synchronized (mSyncRoot) { if (mTopology.updateDisplay(info.displayId, getWidth(info), getHeight(info))) { sendTopologyUpdateLocked(); } } } /** * Remove a display from the topology. * @param displayId The logical display ID */ void onDisplayRemoved(int displayId) { synchronized (mSyncRoot) { mTopology.removeDisplay(displayId); if (mTopology.removeDisplay(displayId)) { sendTopologyUpdateLocked(); } } } /** * @return A deep copy of the topology. Loading services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt +40 −1 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import org.junit.Test import org.mockito.ArgumentMatchers.anyFloat import org.mockito.ArgumentMatchers.anyInt import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify Loading @@ -43,7 +44,7 @@ class DisplayTopologyCoordinatorTest { @Before fun setUp() { displayInfo.displayId = 2 displayInfo.displayId = Display.DEFAULT_DISPLAY displayInfo.logicalWidth = 300 displayInfo.logicalHeight = 200 displayInfo.logicalDensityDpi = 100 Loading Loading @@ -89,6 +90,44 @@ class DisplayTopologyCoordinatorTest { verify(mockTopologyChangedCallback, never()).invoke(any()) } @Test fun updateDisplay() { whenever(mockTopology.updateDisplay(eq(Display.DEFAULT_DISPLAY), anyFloat(), anyFloat())) .thenReturn(true) coordinator.onDisplayChanged(displayInfo) verify(mockTopologyChangedCallback).invoke(mockTopologyCopy) } @Test fun updateDisplay_notChanged() { whenever(mockTopology.updateDisplay(eq(Display.DEFAULT_DISPLAY), anyFloat(), anyFloat())) .thenReturn(false) coordinator.onDisplayChanged(displayInfo) verify(mockTopologyChangedCallback, never()).invoke(any()) } @Test fun removeDisplay() { whenever(mockTopology.removeDisplay(Display.DEFAULT_DISPLAY)).thenReturn(true) coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY) verify(mockTopologyChangedCallback).invoke(mockTopologyCopy) } @Test fun removeDisplay_notChanged() { whenever(mockTopology.removeDisplay(Display.DEFAULT_DISPLAY)).thenReturn(false) coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY) verify(mockTopologyChangedCallback, never()).invoke(any()) } @Test fun getTopology_copy() { assertThat(coordinator.topology).isEqualTo(mockTopologyCopy) Loading Loading
core/java/android/hardware/display/DisplayTopology.java +29 −4 Original line number Diff line number Diff line Loading @@ -128,15 +128,39 @@ public final class DisplayTopology implements Parcelable { addDisplay(displayId, width, height, /* shouldLog= */ true); } /** * Update the size of a display and normalize the topology. * @param displayId The logical display ID * @param width The new width * @param height The new height * @return True if the topology has changed. */ public boolean updateDisplay(int displayId, float width, float height) { TreeNode display = findDisplay(displayId, mRoot); if (display == null) { return false; } if (floatEquals(display.mWidth, width) && floatEquals(display.mHeight, height)) { return false; } display.mWidth = width; display.mHeight = height; normalize(); Slog.i(TAG, "Display with ID " + displayId + " updated, new width: " + width + ", new height: " + height); return 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 * @return True if the display was present in the topology and removed. */ public void removeDisplay(int displayId) { public boolean removeDisplay(int displayId) { if (findDisplay(displayId, mRoot) == null) { return; return false; } Queue<TreeNode> queue = new ArrayDeque<>(); queue.add(mRoot); Loading @@ -159,6 +183,7 @@ public final class DisplayTopology implements Parcelable { } else { Slog.i(TAG, "Display with ID " + displayId + " removed"); } return true; } /** Loading Loading @@ -685,12 +710,12 @@ public final class DisplayTopology implements Parcelable { /** * The width of the display in density-independent pixels (dp). */ private final float mWidth; private float mWidth; /** * The height of the display in density-independent pixels (dp). */ private final float mHeight; private float mHeight; /** * The position of this display relative to its parent. Loading
core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt +78 −8 Original line number Diff line number Diff line Loading @@ -86,7 +86,7 @@ class DisplayTopologyTest { verifyDisplay(display1, displayId1, width1, height1, noOfChildren = 1) val display2 = display1.children[0] verifyDisplay(display1.children[0], displayId2, width2, height2, POSITION_TOP, verifyDisplay(display2, displayId2, width2, height2, POSITION_TOP, offset = width1 / 2 - width2 / 2, noOfChildren = 1) var display = display2 Loading @@ -98,6 +98,76 @@ class DisplayTopologyTest { } } @Test fun updateDisplay() { val displayId = 1 val width = 800f val height = 600f val newWidth = 1000f val newHeight = 500f topology.addDisplay(displayId, width, height) assertThat(topology.updateDisplay(displayId, newWidth, newHeight)).isTrue() assertThat(topology.primaryDisplayId).isEqualTo(displayId) verifyDisplay(topology.root!!, displayId, newWidth, newHeight, noOfChildren = 0) } @Test fun updateDisplay_notUpdated() { val displayId = 1 val width = 800f val height = 600f topology.addDisplay(displayId, width, height) // Same size assertThat(topology.updateDisplay(displayId, width, height)).isFalse() // Display doesn't exist assertThat(topology.updateDisplay(/* displayId= */ 100, width, height)).isFalse() assertThat(topology.primaryDisplayId).isEqualTo(displayId) verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0) } @Test fun updateDisplayDoesNotAffectDefaultTopology() { val width1 = 700f val height = 600f topology.addDisplay(/* displayId= */ 1, width1, height) val width2 = 800f val noOfDisplays = 30 for (i in 2..noOfDisplays) { topology.addDisplay(/* displayId= */ i, width2, height) } val displaysToUpdate = arrayOf(3, 7, 18) val newWidth = 1000f val newHeight = 1500f for (i in displaysToUpdate) { assertThat(topology.updateDisplay(/* displayId= */ i, newWidth, newHeight)).isTrue() } assertThat(topology.primaryDisplayId).isEqualTo(1) val display1 = topology.root!! verifyDisplay(display1, id = 1, width1, height, noOfChildren = 1) val display2 = display1.children[0] verifyDisplay(display2, id = 2, width2, height, POSITION_TOP, offset = width1 / 2 - width2 / 2, noOfChildren = 1) var display = display2 for (i in 3..noOfDisplays) { display = display.children[0] // The last display should have no children verifyDisplay(display, id = i, if (i in displaysToUpdate) newWidth else width2, if (i in displaysToUpdate) newHeight else height, POSITION_RIGHT, offset = 0f, noOfChildren = if (i < noOfDisplays) 1 else 0) } } @Test fun removeDisplays() { val displayId1 = 1 Loading @@ -117,7 +187,7 @@ class DisplayTopologyTest { } var removedDisplays = arrayOf(20) topology.removeDisplay(20) assertThat(topology.removeDisplay(20)).isTrue() assertThat(topology.primaryDisplayId).isEqualTo(displayId1) Loading @@ -139,11 +209,11 @@ class DisplayTopologyTest { noOfChildren = if (i < noOfDisplays) 1 else 0) } topology.removeDisplay(22) assertThat(topology.removeDisplay(22)).isTrue() removedDisplays += 22 topology.removeDisplay(23) assertThat(topology.removeDisplay(23)).isTrue() removedDisplays += 23 topology.removeDisplay(25) assertThat(topology.removeDisplay(25)).isTrue() removedDisplays += 25 assertThat(topology.primaryDisplayId).isEqualTo(displayId1) Loading Loading @@ -174,7 +244,7 @@ class DisplayTopologyTest { val height = 600f topology.addDisplay(displayId, width, height) topology.removeDisplay(displayId) assertThat(topology.removeDisplay(displayId)).isTrue() assertThat(topology.primaryDisplayId).isEqualTo(Display.INVALID_DISPLAY) assertThat(topology.root).isNull() Loading @@ -187,7 +257,7 @@ class DisplayTopologyTest { val height = 600f topology.addDisplay(displayId, width, height) topology.removeDisplay(3) assertThat(topology.removeDisplay(3)).isFalse() assertThat(topology.primaryDisplayId).isEqualTo(displayId) verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0) Loading @@ -203,7 +273,7 @@ class DisplayTopologyTest { topology = DisplayTopology(/* root= */ null, displayId2) topology.addDisplay(displayId1, width, height) topology.addDisplay(displayId2, width, height) topology.removeDisplay(displayId2) assertThat(topology.removeDisplay(displayId2)).isTrue() assertThat(topology.primaryDisplayId).isEqualTo(displayId1) verifyDisplay(topology.root!!, displayId1, width, height, noOfChildren = 0) Loading
services/core/java/com/android/server/display/DisplayManagerService.java +4 −0 Original line number Diff line number Diff line Loading @@ -2400,6 +2400,10 @@ public final class DisplayManagerService extends SystemService { sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED); applyDisplayChangedLocked(display); if (mDisplayTopologyCoordinator != null) { mDisplayTopologyCoordinator.onDisplayChanged(display.getDisplayInfoLocked()); } } private void applyDisplayChangedLocked(@NonNull LogicalDisplay display) { Loading
services/core/java/com/android/server/display/DisplayTopologyCoordinator.java +15 −2 Original line number Diff line number Diff line Loading @@ -84,16 +84,29 @@ class DisplayTopologyCoordinator { } } /** * Update the topology with display changes. * @param info The new display info */ void onDisplayChanged(DisplayInfo info) { synchronized (mSyncRoot) { if (mTopology.updateDisplay(info.displayId, getWidth(info), getHeight(info))) { sendTopologyUpdateLocked(); } } } /** * Remove a display from the topology. * @param displayId The logical display ID */ void onDisplayRemoved(int displayId) { synchronized (mSyncRoot) { mTopology.removeDisplay(displayId); if (mTopology.removeDisplay(displayId)) { sendTopologyUpdateLocked(); } } } /** * @return A deep copy of the topology. Loading
services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt +40 −1 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import org.junit.Test import org.mockito.ArgumentMatchers.anyFloat import org.mockito.ArgumentMatchers.anyInt import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify Loading @@ -43,7 +44,7 @@ class DisplayTopologyCoordinatorTest { @Before fun setUp() { displayInfo.displayId = 2 displayInfo.displayId = Display.DEFAULT_DISPLAY displayInfo.logicalWidth = 300 displayInfo.logicalHeight = 200 displayInfo.logicalDensityDpi = 100 Loading Loading @@ -89,6 +90,44 @@ class DisplayTopologyCoordinatorTest { verify(mockTopologyChangedCallback, never()).invoke(any()) } @Test fun updateDisplay() { whenever(mockTopology.updateDisplay(eq(Display.DEFAULT_DISPLAY), anyFloat(), anyFloat())) .thenReturn(true) coordinator.onDisplayChanged(displayInfo) verify(mockTopologyChangedCallback).invoke(mockTopologyCopy) } @Test fun updateDisplay_notChanged() { whenever(mockTopology.updateDisplay(eq(Display.DEFAULT_DISPLAY), anyFloat(), anyFloat())) .thenReturn(false) coordinator.onDisplayChanged(displayInfo) verify(mockTopologyChangedCallback, never()).invoke(any()) } @Test fun removeDisplay() { whenever(mockTopology.removeDisplay(Display.DEFAULT_DISPLAY)).thenReturn(true) coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY) verify(mockTopologyChangedCallback).invoke(mockTopologyCopy) } @Test fun removeDisplay_notChanged() { whenever(mockTopology.removeDisplay(Display.DEFAULT_DISPLAY)).thenReturn(false) coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY) verify(mockTopologyChangedCallback, never()).invoke(any()) } @Test fun getTopology_copy() { assertThat(coordinator.topology).isEqualTo(mockTopologyCopy) Loading