summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java6
-rw-r--r--services/core/java/com/android/server/display/DisplayTopology.java100
-rw-r--r--services/core/java/com/android/server/display/DisplayTopologyCoordinator.java10
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt133
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