Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d6b9ae17 authored by Piotr Wilczyński's avatar Piotr Wilczyński
Browse files

Update topology when display changes

It is enough to update the display size - rotation, resolution and density changes will all change the display size in dp.

Run normalization after the update.

Bug: 364909708
Flag: com.android.server.display.feature.flags.display_topology
Test: DisplayTopologyTest, DisplayCoordinatorTest, DisplayManagerServiceTest
Change-Id: I84db5a199344b38e25fd7f6d03963d008603ab73
parent 6ee3b28e
Loading
Loading
Loading
Loading
+29 −4
Original line number Original line Diff line number Diff line
@@ -128,15 +128,39 @@ public final class DisplayTopology implements Parcelable {
        addDisplay(displayId, width, height, /* shouldLog= */ true);
        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.
     * Remove a display from the topology.
     * The default topology is created from the remaining displays, as if they were reconnected
     * The default topology is created from the remaining displays, as if they were reconnected
     * one by one.
     * one by one.
     * @param displayId The logical display ID
     * @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) {
        if (findDisplay(displayId, mRoot) == null) {
            return;
            return false;
        }
        }
        Queue<TreeNode> queue = new ArrayDeque<>();
        Queue<TreeNode> queue = new ArrayDeque<>();
        queue.add(mRoot);
        queue.add(mRoot);
@@ -159,6 +183,7 @@ public final class DisplayTopology implements Parcelable {
        } else {
        } else {
            Slog.i(TAG, "Display with ID " + displayId + " removed");
            Slog.i(TAG, "Display with ID " + displayId + " removed");
        }
        }
        return true;
    }
    }


    /**
    /**
@@ -685,12 +710,12 @@ public final class DisplayTopology implements Parcelable {
        /**
        /**
         * The width of the display in density-independent pixels (dp).
         * 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).
         * 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.
         * The position of this display relative to its parent.
+78 −8
Original line number Original line Diff line number Diff line
@@ -86,7 +86,7 @@ class DisplayTopologyTest {
        verifyDisplay(display1, displayId1, width1, height1, noOfChildren = 1)
        verifyDisplay(display1, displayId1, width1, height1, noOfChildren = 1)


        val display2 = display1.children[0]
        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)
            offset = width1 / 2 - width2 / 2, noOfChildren = 1)


        var display = display2
        var display = display2
@@ -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
    @Test
    fun removeDisplays() {
    fun removeDisplays() {
        val displayId1 = 1
        val displayId1 = 1
@@ -117,7 +187,7 @@ class DisplayTopologyTest {
        }
        }


        var removedDisplays = arrayOf(20)
        var removedDisplays = arrayOf(20)
        topology.removeDisplay(20)
        assertThat(topology.removeDisplay(20)).isTrue()


        assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
        assertThat(topology.primaryDisplayId).isEqualTo(displayId1)


@@ -139,11 +209,11 @@ class DisplayTopologyTest {
                noOfChildren = if (i < noOfDisplays) 1 else 0)
                noOfChildren = if (i < noOfDisplays) 1 else 0)
        }
        }


        topology.removeDisplay(22)
        assertThat(topology.removeDisplay(22)).isTrue()
        removedDisplays += 22
        removedDisplays += 22
        topology.removeDisplay(23)
        assertThat(topology.removeDisplay(23)).isTrue()
        removedDisplays += 23
        removedDisplays += 23
        topology.removeDisplay(25)
        assertThat(topology.removeDisplay(25)).isTrue()
        removedDisplays += 25
        removedDisplays += 25


        assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
        assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
@@ -174,7 +244,7 @@ class DisplayTopologyTest {
        val height = 600f
        val height = 600f


        topology.addDisplay(displayId, width, height)
        topology.addDisplay(displayId, width, height)
        topology.removeDisplay(displayId)
        assertThat(topology.removeDisplay(displayId)).isTrue()


        assertThat(topology.primaryDisplayId).isEqualTo(Display.INVALID_DISPLAY)
        assertThat(topology.primaryDisplayId).isEqualTo(Display.INVALID_DISPLAY)
        assertThat(topology.root).isNull()
        assertThat(topology.root).isNull()
@@ -187,7 +257,7 @@ class DisplayTopologyTest {
        val height = 600f
        val height = 600f


        topology.addDisplay(displayId, width, height)
        topology.addDisplay(displayId, width, height)
        topology.removeDisplay(3)
        assertThat(topology.removeDisplay(3)).isFalse()


        assertThat(topology.primaryDisplayId).isEqualTo(displayId)
        assertThat(topology.primaryDisplayId).isEqualTo(displayId)
        verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0)
        verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0)
@@ -203,7 +273,7 @@ class DisplayTopologyTest {
        topology = DisplayTopology(/* root= */ null, displayId2)
        topology = DisplayTopology(/* root= */ null, displayId2)
        topology.addDisplay(displayId1, width, height)
        topology.addDisplay(displayId1, width, height)
        topology.addDisplay(displayId2, width, height)
        topology.addDisplay(displayId2, width, height)
        topology.removeDisplay(displayId2)
        assertThat(topology.removeDisplay(displayId2)).isTrue()


        assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
        assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
        verifyDisplay(topology.root!!, displayId1, width, height, noOfChildren = 0)
        verifyDisplay(topology.root!!, displayId1, width, height, noOfChildren = 0)
+4 −0
Original line number Original line Diff line number Diff line
@@ -2400,6 +2400,10 @@ public final class DisplayManagerService extends SystemService {
        sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
        sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);


        applyDisplayChangedLocked(display);
        applyDisplayChangedLocked(display);

        if (mDisplayTopologyCoordinator != null) {
            mDisplayTopologyCoordinator.onDisplayChanged(display.getDisplayInfoLocked());
        }
    }
    }


    private void applyDisplayChangedLocked(@NonNull LogicalDisplay display) {
    private void applyDisplayChangedLocked(@NonNull LogicalDisplay display) {
+15 −2
Original line number Original line Diff line number Diff line
@@ -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.
     * Remove a display from the topology.
     * @param displayId The logical display ID
     * @param displayId The logical display ID
     */
     */
    void onDisplayRemoved(int displayId) {
    void onDisplayRemoved(int displayId) {
        synchronized (mSyncRoot) {
        synchronized (mSyncRoot) {
            mTopology.removeDisplay(displayId);
            if (mTopology.removeDisplay(displayId)) {
                sendTopologyUpdateLocked();
                sendTopologyUpdateLocked();
            }
            }
        }
        }
    }


    /**
    /**
     * @return A deep copy of the topology.
     * @return A deep copy of the topology.
+40 −1
Original line number Original line Diff line number Diff line
@@ -26,6 +26,7 @@ import org.junit.Test
import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.any
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.verify
@@ -43,7 +44,7 @@ class DisplayTopologyCoordinatorTest {


    @Before
    @Before
    fun setUp() {
    fun setUp() {
        displayInfo.displayId = 2
        displayInfo.displayId = Display.DEFAULT_DISPLAY
        displayInfo.logicalWidth = 300
        displayInfo.logicalWidth = 300
        displayInfo.logicalHeight = 200
        displayInfo.logicalHeight = 200
        displayInfo.logicalDensityDpi = 100
        displayInfo.logicalDensityDpi = 100
@@ -89,6 +90,44 @@ class DisplayTopologyCoordinatorTest {
        verify(mockTopologyChangedCallback, never()).invoke(any())
        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
    @Test
    fun getTopology_copy() {
    fun getTopology_copy() {
        assertThat(coordinator.topology).isEqualTo(mockTopologyCopy)
        assertThat(coordinator.topology).isEqualTo(mockTopologyCopy)