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

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

Update unique ID mappings in DisplayTopologyCoordinator

When onDisplayChanged is received and
- the new unique ID isn't null
- the new unique ID is different from the old one
- the display is present in the topology (we already have a mapping for the display)
then update the mapping and restore the topology from the XML file so that the positions by unique ID are preserved.

Bug: 438419983
Test: DisplayTopologyCoordinatorTest
Flag: EXEMPT bugfix
Change-Id: Iece261f7539f3a114b5e707eda92fcfe7d261c16
parent b516ca3a
Loading
Loading
Loading
Loading
+19 −2
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.display;

import android.annotation.Nullable;
import android.hardware.display.DisplayTopology;
import android.hardware.display.DisplayTopologyGraph;
import android.os.Trace;
@@ -44,6 +45,7 @@ import java.util.function.Consumer;
class DisplayTopologyCoordinator {
    private static final String TAG = "DisplayTopologyCoordinator";

    @Nullable
    private static String getUniqueId(DisplayInfo info) {
        if (info.displayId == Display.DEFAULT_DISPLAY && info.type == Display.TYPE_INTERNAL) {
            return "internal";
@@ -155,8 +157,19 @@ class DisplayTopologyCoordinator {
            return;
        }
        synchronized (mSyncRoot) {
            if (mTopology.updateDisplay(info.displayId, info.logicalWidth, info.logicalHeight,
                    info.logicalDensityDpi)) {
            boolean topologyUpdated = mTopology.updateDisplay(info.displayId, info.logicalWidth,
                    info.logicalHeight, info.logicalDensityDpi);

            String uniqueId = getUniqueId(info);
            String oldUniqueId = mDisplayIdToUniqueIdMapping.get(info.displayId);
            if (uniqueId != null && oldUniqueId != null && !uniqueId.equals(oldUniqueId)) {
                addDisplayIdMappingLocked(info);

                // Restore the displays' positions by unique ID
                topologyUpdated |= restoreTopologyLocked();
            }

            if (topologyUpdated) {
                sendTopologyUpdateLocked();
            }
        }
@@ -272,6 +285,10 @@ class DisplayTopologyCoordinator {
    @GuardedBy("mSyncRoot")
    private void addDisplayIdMappingLocked(DisplayInfo info) {
        final String uniqueId = getUniqueId(info);
        if (null == uniqueId) {
            Slog.e(TAG, "Can't find uniqueId for displayId=" + info.displayId);
            return;
        }
        mUniqueIdToDisplayIdMapping.put(uniqueId, info.displayId);
        mDisplayIdToUniqueIdMapping.put(info.displayId, uniqueId);
    }
+137 −38
Original line number Diff line number Diff line
@@ -40,6 +40,9 @@ class DisplayTopologyCoordinatorTest {
    private lateinit var displayInfos: List<DisplayInfo>
    private val topologyChangeExecutor = Runnable::run

    private lateinit var uniqueIdToDisplayIdMapping: Map<String, Int>
    private lateinit var displayIdToUniqueIdMapping: SparseArray<String>

    private val mockTopologyStore = mock<DisplayTopologyStore>()
    private val mockTopology = mock<DisplayTopology>()
    private val mockTopologyCopy = mock<DisplayTopology>()
@@ -56,6 +59,7 @@ class DisplayTopologyCoordinatorTest {
        displayInfos = (1..10).map { i ->
            val info = DisplayInfo()
            info.displayId = i
            info.uniqueId = "uniqueId$i"
            info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP
            info.logicalWidth = i * 300
            info.logicalHeight = i * 200
@@ -69,11 +73,15 @@ class DisplayTopologyCoordinatorTest {
            override fun createTopologyStore(
                displayIdToUniqueId: SparseArray<String>,
                uniqueIdToDisplayId: MutableMap<String, Int>
            ) = mockTopologyStore
            ): DisplayTopologyStore {
                uniqueIdToDisplayIdMapping = uniqueIdToDisplayId
                displayIdToUniqueIdMapping = displayIdToUniqueId
                return mockTopologyStore
            }
        }
        whenever(mockIsExtendedDisplayAllowed()).thenReturn(true)
        whenever(mockTopology.copy()).thenReturn(mockTopologyCopy)
        whenever(mockTopologyCopy.getGraph()).thenReturn(mockTopologyGraph)
        whenever(mockTopologyCopy.graph).thenReturn(mockTopologyGraph)
        coordinator = DisplayTopologyCoordinator(injector, mockIsExtendedDisplayAllowed,
            mockShouldIncludeDefaultDisplayInTopology, mockTopologyChangedCallback,
            topologyChangeExecutor, DisplayManagerService.SyncRoot(), mockTopologySavedCallback,
@@ -87,9 +95,13 @@ class DisplayTopologyCoordinatorTest {

            verify(mockTopology).addDisplay(displayInfo.displayId, displayInfo.logicalWidth,
                    displayInfo.logicalHeight, displayInfo.logicalDensityDpi)
            assertThat(displayIdToUniqueIdMapping.get(displayInfo.displayId))
                .isEqualTo(displayInfo.uniqueId)
            assertThat(uniqueIdToDisplayIdMapping[displayInfo.uniqueId])
                .isEqualTo(displayInfo.displayId)
        }

        verify(mockTopologyCopy, times(displayInfos.size)).getGraph()
        verify(mockTopologyCopy, times(displayInfos.size)).graph

        verify(mockTopologyChangedCallback, times(displayInfos.size)).invoke(
            android.util.Pair(
@@ -107,12 +119,15 @@ class DisplayTopologyCoordinatorTest {

    @Test
    fun addDisplay_internal() {
        displayInfos[0].displayId = Display.DEFAULT_DISPLAY
        displayInfos[0].type = Display.TYPE_INTERNAL
        coordinator.onDisplayAdded(displayInfos[0])
        val displayInfo = displayInfos[0]
        displayInfo.displayId = Display.DEFAULT_DISPLAY
        displayInfo.type = Display.TYPE_INTERNAL
        coordinator.onDisplayAdded(displayInfo)

        verify(mockTopology).addDisplay(displayInfos[0].displayId, displayInfos[0].logicalWidth,
                displayInfos[0].logicalHeight, displayInfos[0].logicalDensityDpi)
        verify(mockTopology).addDisplay(displayInfo.displayId, displayInfo.logicalWidth,
                displayInfo.logicalHeight, displayInfo.logicalDensityDpi)
        assertThat(displayIdToUniqueIdMapping.get(displayInfo.displayId)).isEqualTo("internal")
        assertThat(uniqueIdToDisplayIdMapping["internal"]).isEqualTo(displayInfo.displayId)
        verify(mockTopologyChangedCallback).invoke(
            android.util.Pair(
                mockTopologyCopy,
@@ -123,11 +138,16 @@ class DisplayTopologyCoordinatorTest {

    @Test
    fun addDisplay_overlay() {
        displayInfos[0].type = Display.TYPE_OVERLAY
        coordinator.onDisplayAdded(displayInfos[0])
        val displayInfo = displayInfos[0]
        displayInfo.type = Display.TYPE_OVERLAY
        coordinator.onDisplayAdded(displayInfo)

        verify(mockTopology).addDisplay(displayInfos[0].displayId, displayInfos[0].logicalWidth,
                displayInfos[0].logicalHeight, displayInfos[0].logicalDensityDpi)
        verify(mockTopology).addDisplay(displayInfo.displayId, displayInfo.logicalWidth,
                displayInfo.logicalHeight, displayInfo.logicalDensityDpi)
        assertThat(displayIdToUniqueIdMapping.get(displayInfo.displayId))
            .isEqualTo(displayInfo.uniqueId)
        assertThat(uniqueIdToDisplayIdMapping[displayInfo.uniqueId])
            .isEqualTo(displayInfo.displayId)
        verify(mockTopologyChangedCallback).invoke(
            android.util.Pair(
                mockTopologyCopy,
@@ -205,7 +225,7 @@ class DisplayTopologyCoordinatorTest {

    @Test
    fun addNonDefaultDisplay_defaultDisplayInTopologySwitchDisabled() {
        whenever(mockFlags.isDefaultDisplayInTopologySwitchEnabled()).thenReturn(true)
        whenever(mockFlags.isDefaultDisplayInTopologySwitchEnabled).thenReturn(true)
        whenever(mockShouldIncludeDefaultDisplayInTopology()).thenReturn(false)

        // Add default display and a non-default display into the topology
@@ -222,7 +242,7 @@ class DisplayTopologyCoordinatorTest {

    @Test
    fun addNonDefaultDisplay_defaultDisplayInTopologySwitchEnabled() {
        whenever(mockFlags.isDefaultDisplayInTopologySwitchEnabled()).thenReturn(true)
        whenever(mockFlags.isDefaultDisplayInTopologySwitchEnabled).thenReturn(true)
        whenever(mockShouldIncludeDefaultDisplayInTopology()).thenReturn(true)

        // Add default display and a non-default display into the topology
@@ -239,7 +259,7 @@ class DisplayTopologyCoordinatorTest {

    @Test
    fun addNonDefaultDisplay_flagDisabled() {
        whenever(mockFlags.isDefaultDisplayInTopologySwitchEnabled()).thenReturn(false)
        whenever(mockFlags.isDefaultDisplayInTopologySwitchEnabled).thenReturn(false)
        whenever(mockShouldIncludeDefaultDisplayInTopology()).thenReturn(false)

        // Add default display and a non-default display into the topology
@@ -256,19 +276,33 @@ class DisplayTopologyCoordinatorTest {

    @Test
    fun updateDisplay() {
        whenever(mockTopology.updateDisplay(eq(displayInfos[0].displayId),
                                            anyInt(), anyInt(), anyInt()))
            .thenReturn(true)
        val displayInfo = displayInfos[0]
        whenever(
            mockTopology.updateDisplay(
                eq(displayInfo.displayId),
                anyInt(),
                anyInt(),
                anyInt()
            )
        ).thenReturn(true)
        addDisplay()

        displayInfos[0].logicalWidth += 100
        displayInfos[0].logicalHeight += 100
        coordinator.onDisplayChanged(displayInfos[0])
        displayInfo.logicalWidth += 100
        displayInfo.logicalHeight += 100
        displayInfo.uniqueId = "newUniqueId"
        coordinator.onDisplayChanged(displayInfo)

        verify(mockTopology).updateDisplay(displayInfos[0].displayId, displayInfos[0].logicalWidth,
                displayInfos[0].logicalHeight, displayInfos[0].logicalDensityDpi)
        verify(mockTopology).updateDisplay(
            displayInfo.displayId, displayInfo.logicalWidth,
            displayInfo.logicalHeight, displayInfo.logicalDensityDpi
        )

        verify(mockTopologyCopy).getGraph()
        assertThat(displayIdToUniqueIdMapping.get(displayInfo.displayId))
            .isEqualTo(displayInfo.uniqueId)
        assertThat(uniqueIdToDisplayIdMapping[displayInfo.uniqueId])
            .isEqualTo(displayInfo.displayId)

        verify(mockTopologyCopy).graph

        verify(mockTopologyChangedCallback).invoke(
            android.util.Pair(
@@ -278,6 +312,69 @@ class DisplayTopologyCoordinatorTest {
        )
    }

    @Test
    fun updateDisplay_type_uniqueIdUpdated() {
        val displayInfo = displayInfos[0]
        displayInfo.displayId = Display.DEFAULT_DISPLAY
        addDisplay()
        val restoredTopology = mock<DisplayTopology>()
        whenever(restoredTopology.copy()).thenReturn(mock<DisplayTopology>())
        whenever(mockTopologyStore.restoreTopology(mockTopology)).thenReturn(restoredTopology)

        // Change to internal
        displayInfo.type = Display.TYPE_INTERNAL
        coordinator.onDisplayChanged(displayInfo)

        assertThat(displayIdToUniqueIdMapping.get(displayInfo.displayId)).isEqualTo("internal")
        assertThat(uniqueIdToDisplayIdMapping["internal"]).isEqualTo(displayInfo.displayId)
        verify(mockTopologyStore).restoreTopology(mockTopology)

        // Back to external
        displayInfo.type = Display.TYPE_EXTERNAL
        coordinator.onDisplayChanged(displayInfo)

        assertThat(displayIdToUniqueIdMapping.get(displayInfo.displayId))
            .isEqualTo(displayInfo.uniqueId)
        assertThat(uniqueIdToDisplayIdMapping[displayInfo.uniqueId])
            .isEqualTo(displayInfo.displayId)
        verify(mockTopologyStore).restoreTopology(restoredTopology)
    }

    @Test
    fun updateDisplay_swapUniqueIds() {
        val displayInfo1 = displayInfos[0]
        val displayInfo2 = displayInfos[1]
        addDisplay()
        val restoredTopology = mock<DisplayTopology>()
        whenever(restoredTopology.copy()).thenReturn(mock<DisplayTopology>())
        whenever(mockTopologyStore.restoreTopology(mockTopology)).thenReturn(restoredTopology)

        displayInfo1.uniqueId = displayInfo2.uniqueId
        coordinator.onDisplayChanged(displayInfo1)

        assertThat(displayIdToUniqueIdMapping.get(displayInfo1.displayId))
            .isEqualTo(displayInfo1.uniqueId)
        assertThat(uniqueIdToDisplayIdMapping[displayInfo1.uniqueId])
            .isEqualTo(displayInfo1.displayId)
        verify(mockTopologyStore).restoreTopology(mockTopology)

        // Now temporarily two logical display IDs map to the same unique ID. Make sure that
        // the following does not remove the mapping for the first display.
        displayInfo2.uniqueId = "newUniqueId"
        coordinator.onDisplayChanged(displayInfo2)

        assertThat(displayIdToUniqueIdMapping.get(displayInfo2.displayId))
            .isEqualTo(displayInfo2.uniqueId)
        assertThat(uniqueIdToDisplayIdMapping[displayInfo2.uniqueId])
            .isEqualTo(displayInfo2.displayId)
        // This mapping should still be there
        assertThat(displayIdToUniqueIdMapping.get(displayInfo1.displayId))
            .isEqualTo(displayInfo1.uniqueId)
        assertThat(uniqueIdToDisplayIdMapping[displayInfo1.uniqueId])
            .isEqualTo(displayInfo1.displayId)
        verify(mockTopologyStore).restoreTopology(restoredTopology)
    }

    @Test
    fun updateDisplay_notChanged() {
        addDisplay()
@@ -289,10 +386,12 @@ class DisplayTopologyCoordinatorTest {
        // Try to update a display that does not exist
        val info = DisplayInfo()
        info.displayId = 100
        info.uniqueId = "someUniqueId"
        coordinator.onDisplayChanged(info)

        verify(mockTopologyCopy, never()).getGraph()
        verify(mockTopologyCopy, never()).graph
        verify(mockTopologyChangedCallback, never()).invoke(any())
        verify(mockTopologyStore, never()).restoreTopology(any())
    }

    @Test
@@ -302,7 +401,7 @@ class DisplayTopologyCoordinatorTest {
        coordinator.onDisplayChanged(displayInfos[0])

        verify(mockTopology, never()).updateDisplay(anyInt(), anyInt(), anyInt(), anyInt())
        verify(mockTopologyCopy, never()).getGraph()
        verify(mockTopologyCopy, never()).graph
        verify(mockTopologyChangedCallback, never()).invoke(any())
    }

@@ -313,7 +412,7 @@ class DisplayTopologyCoordinatorTest {
        coordinator.onDisplayChanged(displayInfos[0])

        verify(mockTopology, never()).updateDisplay(anyInt(), anyInt(), anyInt(), anyInt())
        verify(mockTopologyCopy, never()).getGraph()
        verify(mockTopologyCopy, never()).graph
        verify(mockTopologyChangedCallback, never()).invoke(any())
    }

@@ -324,7 +423,7 @@ class DisplayTopologyCoordinatorTest {
        coordinator.onDisplayChanged(displayInfos[0])

        verify(mockTopology, never()).updateDisplay(anyInt(), anyInt(), anyInt(), anyInt())
        verify(mockTopologyCopy, never()).getGraph()
        verify(mockTopologyCopy, never()).graph
        verify(mockTopologyChangedCallback, never()).invoke(any())
    }

@@ -336,7 +435,7 @@ class DisplayTopologyCoordinatorTest {
        coordinator.onDisplayChanged(displayInfos[0])

        verify(mockTopology, never()).updateDisplay(anyInt(), anyInt(), anyInt(), anyInt())
        verify(mockTopologyCopy, never()).getGraph()
        verify(mockTopologyCopy, never()).graph
        verify(mockTopologyChangedCallback, never()).invoke(any())
    }

@@ -349,7 +448,7 @@ class DisplayTopologyCoordinatorTest {
        }

        verify(mockTopology, never()).updateDisplay(anyInt(), anyInt(), anyInt(), anyInt())
        verify(mockTopologyCopy, never()).getGraph()
        verify(mockTopologyCopy, never()).graph
        verify(mockTopologyChangedCallback, never()).invoke(any())
    }

@@ -376,7 +475,7 @@ class DisplayTopologyCoordinatorTest {
            coordinator.onDisplayRemoved(displayInfo.displayId)
        }

        verify(mockTopologyCopy, times(displaysToRemove.size)).getGraph()
        verify(mockTopologyCopy, times(displaysToRemove.size)).graph

        verify(mockTopologyChangedCallback, times(displaysToRemove.size)).invoke(
            android.util.Pair(
@@ -397,7 +496,7 @@ class DisplayTopologyCoordinatorTest {

    @Test
    fun removeNonDefaultDisplay_defaultDisplayInTopologySwitchDisabled() {
        whenever(mockFlags.isDefaultDisplayInTopologySwitchEnabled()).thenReturn(true)
        whenever(mockFlags.isDefaultDisplayInTopologySwitchEnabled).thenReturn(true)
        whenever(mockShouldIncludeDefaultDisplayInTopology()).thenReturn(false)

        // Set up the default display
@@ -408,7 +507,7 @@ class DisplayTopologyCoordinatorTest {
        displayInfos[1].displayId = Display.DEFAULT_DISPLAY + 1
        displayInfos[1].type = Display.TYPE_EXTERNAL
        whenever(mockTopology.removeDisplay(displayInfos[1].displayId)).thenReturn(true)
        whenever(mockTopology.isEmpty()).thenReturn(true)
        whenever(mockTopology.isEmpty).thenReturn(true)
        coordinator.onDisplayRemoved(displayInfos[1].displayId)

        verify(mockTopology).addDisplay(displayInfos[0].displayId, displayInfos[0].logicalWidth,
@@ -417,7 +516,7 @@ class DisplayTopologyCoordinatorTest {

    @Test
    fun removeNonDefaultDisplay_defaultDisplayInTopologySwitchEnabled() {
        whenever(mockFlags.isDefaultDisplayInTopologySwitchEnabled()).thenReturn(true)
        whenever(mockFlags.isDefaultDisplayInTopologySwitchEnabled).thenReturn(true)
        whenever(mockShouldIncludeDefaultDisplayInTopology()).thenReturn(true)

        // Set up the default display
@@ -435,7 +534,7 @@ class DisplayTopologyCoordinatorTest {

    @Test
    fun removeNonDefaultDisplay_flagDisabled() {
        whenever(mockFlags.isDefaultDisplayInTopologySwitchEnabled()).thenReturn(false)
        whenever(mockFlags.isDefaultDisplayInTopologySwitchEnabled).thenReturn(false)
        whenever(mockShouldIncludeDefaultDisplayInTopology()).thenReturn(false)

        // Set up the default display
@@ -462,7 +561,7 @@ class DisplayTopologyCoordinatorTest {
        val topologyCopy = mock<DisplayTopology>()
        val topologyGraph = mock<DisplayTopologyGraph>()
        whenever(topology.copy()).thenReturn(topologyCopy)
        whenever(topologyCopy.getGraph()).thenReturn(topologyGraph)
        whenever(topologyCopy.graph).thenReturn(topologyGraph)
        whenever(mockTopologyStore.saveTopology(topology)).thenReturn(true)

        coordinator.topology = topology