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

Commit 004420c1 authored by Matthew DeVore's avatar Matthew DeVore
Browse files

DisplayTopology: remove density param of getGraph

This density map is no longer needed since TreeNode objects have a
logical density and logical px size fields.

This includes a cleanup of proximate code in DisplayTopology which I had
wanted to do for a while. The getInfo method now returns a single List
with a new record type, rather than build a HashMap for each desired
piece of information.

I estimate the List is around as memory-heavy as a single HashMap, as a
HashMap requires an allocation for each Entry and the List requires an
allocation for each NodeDerivedInfo. In this CL, the the RectF bounds
objects are constructed on-demand.

More importantly, the changes were done for the sake of maintainability
and there should be no effect on performance.

Flag: EXEMPT refactor
Bug: b/407028459
Test: DisplayTopologyTest DisplayManagerServiceTest DisplayTopologyCoordinatorTest
Change-Id: I2aeab51d6f43254a438efb4afdba1107380d5533
parent f72d2dc9
Loading
Loading
Loading
Loading
+91 −111
Original line number Diff line number Diff line
@@ -35,7 +35,6 @@ import android.util.MathUtils;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.Display;

import androidx.annotation.NonNull;
@@ -404,42 +403,37 @@ public final class DisplayTopology implements Parcelable {
        }
        clampOffsets(mRoot);

        Map<TreeNode, RectF> bounds = new HashMap<>();
        Map<TreeNode, Integer> depths = new HashMap<>();
        Map<TreeNode, TreeNode> parents = new HashMap<>();
        getInfo(bounds, depths, parents, mRoot, /* x= */ 0, /* y= */ 0, /* depth= */ 0);
        List<NodeDerivedInfo> infoList = getInfo();

        // Sort the displays first by their depth in the tree, then by the distance of their top
        // left point from the root display's origin (0, 0). This way we process the displays
        // starting at the root and we push out a display if necessary.
        Comparator<TreeNode> comparator = (d1, d2) -> {
            if (d1 == d2) {
        Comparator<NodeDerivedInfo> comparator = (info1, info2) -> {
            if (info1 == info2) {
                return 0;
            }

            int compareDepths = Integer.compare(depths.get(d1), depths.get(d2));
            int compareDepths = Integer.compare(info1.depth, info2.depth);
            if (compareDepths != 0) {
                return compareDepths;
            }

            RectF bounds1 = bounds.get(d1);
            RectF bounds2 = bounds.get(d2);
            return Double.compare(Math.hypot(bounds1.left, bounds1.top),
                    Math.hypot(bounds2.left, bounds2.top));
            return Double.compare(Math.hypot(info1.left, info1.top),
                    Math.hypot(info2.left, info2.top));
        };
        List<TreeNode> displays = new ArrayList<>(bounds.keySet());
        displays.sort(comparator);
        infoList.sort(comparator);

        for (int i = 1; i < displays.size(); i++) {
            TreeNode targetDisplay = displays.get(i);
            TreeNode lastIntersectingSourceDisplay = null;
        for (int i = 1; i < infoList.size(); i++) {
            NodeDerivedInfo target = infoList.get(i);
            TreeNode targetDisplay = target.node;
            RectF targetBounds = target.absoluteBounds();
            NodeDerivedInfo lastIntersectingSourceDisplay = null;
            float lastOffsetX = 0;
            float lastOffsetY = 0;

            for (int j = 0; j < i; j++) {
                TreeNode sourceDisplay = displays.get(j);
                RectF sourceBounds = bounds.get(sourceDisplay);
                RectF targetBounds = bounds.get(targetDisplay);
                var source = infoList.get(j);
                RectF sourceBounds = source.absoluteBounds();

                if (!RectF.intersects(sourceBounds, targetBounds)) {
                    continue;
@@ -473,7 +467,7 @@ public final class DisplayTopology implements Parcelable {
                    offsetX = 0;
                }

                lastIntersectingSourceDisplay = sourceDisplay;
                lastIntersectingSourceDisplay = source;
                lastOffsetX = offsetX;
                lastOffsetY = offsetY;
            }
@@ -484,7 +478,8 @@ public final class DisplayTopology implements Parcelable {
                // There was no overlap.
                continue;
            }
            TreeNode parent = parents.get(targetDisplay);
            NodeDerivedInfo parent = target.parent;
            RectF parentBounds = parent.absoluteBounds();
            if (parent == lastIntersectingSourceDisplay) {
                // The displays are moved in such a way that they're adjacent to the intersecting
                // display. If the last intersecting display happens to be the parent then we
@@ -492,8 +487,7 @@ public final class DisplayTopology implements Parcelable {
                continue;
            }

            RectF childBounds = bounds.get(targetDisplay);
            RectF parentBounds = bounds.get(parent);
            RectF childBounds = targetBounds;
            // Check that the edges are on the same line
            boolean areTouching = switch (targetDisplay.mPosition) {
                case POSITION_LEFT -> floatEquals(parentBounds.left, childBounds.right);
@@ -517,10 +511,10 @@ public final class DisplayTopology implements Parcelable {

            if (!areTouching) {
                // Re-parent the display.
                parent.mChildren.remove(targetDisplay);
                parent.node.mChildren.remove(targetDisplay);
                RectF lastIntersectingSourceDisplayBounds =
                        bounds.get(lastIntersectingSourceDisplay);
                lastIntersectingSourceDisplay.mChildren.add(targetDisplay);
                        lastIntersectingSourceDisplay.absoluteBounds();
                lastIntersectingSourceDisplay.node.mChildren.add(targetDisplay);

                if (lastOffsetX != 0) {
                    targetDisplay.mPosition = lastOffsetX > 0 ? POSITION_RIGHT : POSITION_LEFT;
@@ -538,8 +532,8 @@ public final class DisplayTopology implements Parcelable {
        final Comparator<TreeNode> idComparator = (d1, d2) -> {
            return Integer.compare(d1.mDisplayId, d2.mDisplayId);
        };
        for (TreeNode display : displays) {
            display.mChildren.sort(idComparator);
        for (NodeDerivedInfo info : infoList) {
            info.node.mChildren.sort(idComparator);
        }
    }

@@ -559,12 +553,10 @@ public final class DisplayTopology implements Parcelable {
     */
    @NonNull
    public SparseArray<RectF> getAbsoluteBounds() {
        Map<TreeNode, RectF> bounds = new HashMap<>();
        getInfo(bounds, /* depths= */ null, /* parents= */ null, mRoot, /* x= */ 0, /* y= */ 0,
                /* depth= */ 0);
        List<NodeDerivedInfo> infoList = getInfo();
        SparseArray<RectF> boundsById = new SparseArray<>();
        for (Map.Entry<TreeNode, RectF> entry : bounds.entrySet()) {
            boundsById.append(entry.getKey().mDisplayId, entry.getValue());
        for (NodeDerivedInfo info : infoList) {
            boundsById.append(info.node.mDisplayId, info.absoluteBounds());
        }
        return boundsById;
    }
@@ -671,46 +663,49 @@ public final class DisplayTopology implements Parcelable {
        return null;
    }

    /**
     * Get information about the topology.
     * Assigns positions to each display to compute the bounds. The root is at position (0, 0).
     * @param bounds The map where the bounds of each display will be put
     * @param depths The map where the depths of each display in the tree will be put
     * @param parents The map where the parent of each display will be put
     * @param display The starting node
     * @param x The starting x position
     * @param y The starting y position
     * @param depth The starting depth
     */
    private static void getInfo(@Nullable Map<TreeNode, RectF> bounds,
            @Nullable Map<TreeNode, Integer> depths, @Nullable Map<TreeNode, TreeNode> parents,
            @Nullable TreeNode display, float x, float y, int depth) {
        if (display == null) {
            return;
        }
        if (bounds != null) {
            bounds.put(display, new RectF(x, y, x + display.getWidth(), y + display.getHeight()));
    private record NodeDerivedInfo(
            TreeNode node, float left, float top, @Nullable NodeDerivedInfo parent, int depth) {
        RectF absoluteBounds() {
            return new RectF(left, top, left + node.getWidth(), top + node.getHeight());
        }
        if (depths != null) {
            depths.put(display, depth);
    }
        for (TreeNode child : display.mChildren) {
            if (parents != null) {
                parents.put(child, display);

    /**
     * Derives information about each node of the topology. Assigns positions to each display to
     * compute the bounds. The root is at position (0, 0).
     */
    private List<NodeDerivedInfo> getInfo() {
        List<NodeDerivedInfo> info = new ArrayList<>();
        if (mRoot != null) {
            NodeDerivedInfo rootInfo = new NodeDerivedInfo(
                    mRoot, /* left= */ 0f, /* top= */ 0f, /* parent= */ null, /* depth= */ 0);
            getSubTreeInfo(info, rootInfo);
        }
            if (child.mPosition == POSITION_LEFT) {
                getInfo(bounds, depths, parents, child, x - child.getWidth(), y + child.mOffset,
                        depth + 1);
            } else if (child.mPosition == POSITION_RIGHT) {
                getInfo(bounds, depths, parents, child, x + display.getWidth(), y + child.mOffset,
                        depth + 1);
            } else if (child.mPosition == POSITION_TOP) {
                getInfo(bounds, depths, parents, child, x + child.mOffset, y - child.getHeight(),
                        depth + 1);
            } else if (child.mPosition == POSITION_BOTTOM) {
                getInfo(bounds, depths, parents, child, x + child.mOffset, y + display.getHeight(),
                        depth + 1);
        return info;
    }

    /**
     * Get information about a part of the topology rooted at the given start node. This method will
     * add the start node to the {@code info} list automatically.
     * @param info the list to store all node information
     * @param startNode the starting node
     */
    private static void getSubTreeInfo(List<NodeDerivedInfo> info, NodeDerivedInfo startNode) {
        info.add(startNode);
        for (TreeNode child : startNode.node.mChildren) {
            float xDiff = switch (child.mPosition) {
                case POSITION_LEFT -> -child.getWidth();
                case POSITION_RIGHT -> startNode.node.getWidth();
                default -> child.mOffset;
            };
            float yDiff = switch (child.mPosition) {
                case POSITION_TOP -> -child.getHeight();
                case POSITION_BOTTOM -> startNode.node.getHeight();
                default -> child.mOffset;
            };
            var childInfo = new NodeDerivedInfo(child, startNode.left + xDiff,
                    startNode.top + yDiff, startNode, startNode.depth + 1);
            getSubTreeInfo(info, childInfo);
        }
    }

@@ -747,53 +742,38 @@ public final class DisplayTopology implements Parcelable {
    }

    /**
     * @param densityPerDisplay The logical display densities, indexed by logical display ID
     * @return The graph representation of the topology. If there is a corner adjacency, the same
     * display will appear twice in the list of adjacent displays with both possible placements.
     * @hide
     */
    @Nullable
    public DisplayTopologyGraph getGraph(SparseIntArray densityPerDisplay) {
    public DisplayTopologyGraph getGraph() {
        // Sort the displays by position
        SparseArray<RectF> bounds = getAbsoluteBounds();
        Comparator<Integer> comparator = (displayId1, displayId2) -> {
            RectF bounds1 = bounds.get(displayId1);
            RectF bounds2 = bounds.get(displayId2);

            int compareX = Float.compare(bounds1.left, bounds2.left);
        List<NodeDerivedInfo> infoList = getInfo();
        Comparator<NodeDerivedInfo> byPosition = (display1, display2) -> {
            int compareX = Float.compare(display1.left, display2.left);
            if (compareX != 0) {
                return compareX;
            }
            return Float.compare(bounds1.top, bounds2.top);
            return Float.compare(display1.top, display2.top);
        };
        List<Integer> displayIds = new ArrayList<>(bounds.size());
        for (int i = 0; i < bounds.size(); i++) {
            displayIds.add(bounds.keyAt(i));
        }
        displayIds.sort(comparator);
        infoList.sort(byPosition);

        SparseArray<List<DisplayTopologyGraph.AdjacentDisplay>> adjacentDisplaysPerId =
                new SparseArray<>();
        for (int id : displayIds) {
            if (densityPerDisplay.get(id) == 0) {
                Slog.e(TAG, "Cannot construct graph, no density for display " + id);
                return null;
            }
            adjacentDisplaysPerId.append(id, new ArrayList<>(Math.min(10, displayIds.size())));
        List<DisplayTopologyGraph.AdjacentDisplay>[] adjacentDisplays = new List[infoList.size()];

        for (int i = 0; i < infoList.size(); i++) {
            adjacentDisplays[i] = new ArrayList<>(Math.min(10, infoList.size()));
        }

        // Find touching displays
        for (int i = 0; i < displayIds.size(); i++) {
            int displayId1 = displayIds.get(i);
            RectF bounds1 = bounds.get(displayId1);
            List<DisplayTopologyGraph.AdjacentDisplay> adjacentDisplays1 =
                    adjacentDisplaysPerId.get(displayId1);

            for (int j = i + 1; j < displayIds.size(); j++) {
                int displayId2 = displayIds.get(j);
                RectF bounds2 = bounds.get(displayId2);
                List<DisplayTopologyGraph.AdjacentDisplay> adjacentDisplays2 =
                        adjacentDisplaysPerId.get(displayId2);
        for (int i = 0; i < infoList.size(); i++) {
            int displayId1 = infoList.get(i).node.mDisplayId;
            RectF bounds1 = infoList.get(i).absoluteBounds();
            List<DisplayTopologyGraph.AdjacentDisplay> adjacentDisplays1 = adjacentDisplays[i];

            for (int j = i + 1; j < infoList.size(); j++) {
                int displayId2 = infoList.get(j).node.mDisplayId;
                RectF bounds2 = infoList.get(j).absoluteBounds();
                List<DisplayTopologyGraph.AdjacentDisplay> adjacentDisplays2 = adjacentDisplays[j];

                List<Pair<Integer, Float>> placements1 = findDisplayPlacements(bounds1, bounds2);
                List<Pair<Integer, Float>> placements2 = findDisplayPlacements(bounds2, bounds1);
@@ -813,12 +793,12 @@ public final class DisplayTopology implements Parcelable {
        }

        DisplayTopologyGraph.DisplayNode[] nodes =
                new DisplayTopologyGraph.DisplayNode[adjacentDisplaysPerId.size()];
                new DisplayTopologyGraph.DisplayNode[infoList.size()];
        for (int i = 0; i < nodes.length; i++) {
            int displayId = adjacentDisplaysPerId.keyAt(i);
            nodes[i] = new DisplayTopologyGraph.DisplayNode(displayId,
                    densityPerDisplay.get(displayId), adjacentDisplaysPerId.valueAt(i).toArray(
                            new DisplayTopologyGraph.AdjacentDisplay[0]));
            TreeNode treeNode = infoList.get(i).node;
            nodes[i] = new DisplayTopologyGraph.DisplayNode(
                    treeNode.mDisplayId, treeNode.mLogicalDensity,
                    adjacentDisplays[i].toArray(new DisplayTopologyGraph.AdjacentDisplay[0]));
        }
        return new DisplayTopologyGraph(mPrimaryDisplayId, nodes);
    }
+3 −3
Original line number Diff line number Diff line
@@ -949,7 +949,7 @@ class DisplayTopologyTest {
        densityPerDisplay.append(5, density5)

        topology = DisplayTopology(display1, primaryDisplayId)
        val graph = topology.getGraph(densityPerDisplay)!!
        val graph = topology.getGraph()
        val nodes = graph.displayNodes

        assertThat(graph.primaryDisplayId).isEqualTo(primaryDisplayId)
@@ -1031,7 +1031,7 @@ class DisplayTopologyTest {
        densityPerDisplay.append(5, density5)

        topology = DisplayTopology(display1, primaryDisplayId)
        val graph = topology.getGraph(densityPerDisplay)!!
        val graph = topology.getGraph()
        val nodes = graph.displayNodes

        assertThat(graph.primaryDisplayId).isEqualTo(primaryDisplayId)
@@ -1114,7 +1114,7 @@ class DisplayTopologyTest {
        densityPerDisplay.append(3, density3)

        topology = DisplayTopology(display1, primaryDisplayId)
        val graph = topology.getGraph(densityPerDisplay)!!
        val graph = topology.getGraph()
        val nodes = graph.displayNodes

        assertThat(graph.primaryDisplayId).isEqualTo(primaryDisplayId)
+1 −13
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import android.hardware.display.DisplayTopologyGraph;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.Display;
import android.view.DisplayInfo;

@@ -55,11 +54,6 @@ class DisplayTopologyCoordinator {
    @GuardedBy("mSyncRoot")
    private DisplayTopology mTopology;

    // Map from logical display ID to logical display density. Should always be consistent with
    // mTopology.
    @GuardedBy("mSyncRoot")
    private final SparseIntArray mDensities = new SparseIntArray();

    @GuardedBy("mSyncRoot")
    private final Map<String, Integer> mUniqueIdToDisplayIdMapping = new HashMap<>();

@@ -114,7 +108,6 @@ class DisplayTopologyCoordinator {
        }
        synchronized (mSyncRoot) {
            addDisplayIdMappingLocked(info);
            mDensities.put(info.displayId, info.logicalDensityDpi);
            mTopology.addDisplay(
                    info.displayId, info.logicalWidth, info.logicalHeight, info.logicalDensityDpi);
            Slog.i(TAG, "Display " + info.displayId + " added, new topology: " + mTopology);
@@ -132,9 +125,6 @@ class DisplayTopologyCoordinator {
            return;
        }
        synchronized (mSyncRoot) {
            if (mDensities.indexOfKey(info.displayId) >= 0) {
                mDensities.put(info.displayId, info.logicalDensityDpi);
            }
            if (mTopology.updateDisplay(info.displayId, info.logicalWidth, info.logicalHeight,
                    info.logicalDensityDpi)) {
                sendTopologyUpdateLocked();
@@ -148,7 +138,6 @@ class DisplayTopologyCoordinator {
     */
    void onDisplayRemoved(int displayId) {
        synchronized (mSyncRoot) {
            mDensities.delete(displayId);
            if (mTopology.removeDisplay(displayId)) {
                Slog.i(TAG, "Display " + displayId + " removed, new topology: " + mTopology);
                removeDisplayIdMappingLocked(displayId);
@@ -280,9 +269,8 @@ class DisplayTopologyCoordinator {
    @GuardedBy("mSyncRoot")
    private void sendTopologyUpdateLocked() {
        DisplayTopology copy = mTopology.copy();
        SparseIntArray densities = mDensities.clone();
        mTopologyChangeExecutor.execute(() -> mOnTopologyChangedCallback.accept(
                new Pair<>(copy, copy.getGraph(densities))));
                new Pair<>(copy, copy.getGraph())));
    }

    @VisibleForTesting
+1 −2
Original line number Diff line number Diff line
@@ -137,7 +137,6 @@ import android.provider.Settings.SettingNotFoundException;
import android.test.mock.MockContentResolver;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.Spline;
import android.view.ContentRecordingSession;
import android.view.Display;
@@ -3886,7 +3885,7 @@ public class DisplayManagerServiceTest {
        DisplayTopology topology = mock(DisplayTopology.class);
        when(topology.copy()).thenReturn(topology);
        DisplayTopologyGraph graph = mock(DisplayTopologyGraph.class);
        when(topology.getGraph(any(SparseIntArray.class))).thenReturn(graph);
        when(topology.getGraph()).thenReturn(graph);
        displayManagerBinderService.setDisplayTopology(topology);
        waitForIdleHandler(displayManager.getDisplayHandler());

+11 −31
Original line number Diff line number Diff line
@@ -19,13 +19,11 @@ package com.android.server.display
import android.hardware.display.DisplayTopology
import android.hardware.display.DisplayTopologyGraph
import android.util.SparseArray
import android.util.SparseIntArray
import android.view.Display
import android.view.DisplayInfo
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.any
import org.mockito.kotlin.clearInvocations
@@ -73,7 +71,7 @@ class DisplayTopologyCoordinatorTest {
        }
        whenever(mockIsExtendedDisplayAllowed()).thenReturn(true)
        whenever(mockTopology.copy()).thenReturn(mockTopologyCopy)
        whenever(mockTopologyCopy.getGraph(any())).thenReturn(mockTopologyGraph)
        whenever(mockTopologyCopy.getGraph()).thenReturn(mockTopologyGraph)
        coordinator = DisplayTopologyCoordinator(injector, mockIsExtendedDisplayAllowed,
            mockTopologyChangedCallback, topologyChangeExecutor, DisplayManagerService.SyncRoot(),
            mockTopologySavedCallback)
@@ -88,14 +86,7 @@ class DisplayTopologyCoordinatorTest {
                    displayInfo.logicalHeight, displayInfo.logicalDensityDpi)
        }

        val captor = ArgumentCaptor.forClass(SparseIntArray::class.java)
        verify(mockTopologyCopy, times(displayInfos.size)).getGraph(captor.capture())
        val densities = captor.value
        assertThat(densities.size()).isEqualTo(displayInfos.size)
        for (displayInfo in displayInfos) {
            assertThat(densities.get(displayInfo.displayId))
                .isEqualTo(displayInfo.logicalDensityDpi)
        }
        verify(mockTopologyCopy, times(displayInfos.size)).getGraph()

        verify(mockTopologyChangedCallback, times(displayInfos.size)).invoke(
            android.util.Pair(
@@ -223,12 +214,7 @@ class DisplayTopologyCoordinatorTest {
        verify(mockTopology).updateDisplay(displayInfos[0].displayId, displayInfos[0].logicalWidth,
                displayInfos[0].logicalHeight, displayInfos[0].logicalDensityDpi)

        val captor = ArgumentCaptor.forClass(SparseIntArray::class.java)
        verify(mockTopologyCopy).getGraph(captor.capture())
        val densities = captor.value
        assertThat(densities.size()).isEqualTo(displayInfos.size)
        assertThat(densities.get(displayInfos[0].displayId))
            .isEqualTo(displayInfos[0].logicalDensityDpi)
        verify(mockTopologyCopy).getGraph()

        verify(mockTopologyChangedCallback).invoke(
            android.util.Pair(
@@ -251,7 +237,7 @@ class DisplayTopologyCoordinatorTest {
        info.displayId = 100
        coordinator.onDisplayChanged(info)

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

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

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

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

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

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

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

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

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

@@ -309,7 +295,7 @@ class DisplayTopologyCoordinatorTest {
        }

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

@@ -336,13 +322,7 @@ class DisplayTopologyCoordinatorTest {
            coordinator.onDisplayRemoved(displayInfo.displayId)
        }

        val captor = ArgumentCaptor.forClass(SparseIntArray::class.java)
        verify(mockTopologyCopy, times(displaysToRemove.size)).getGraph(captor.capture())
        val densities = captor.value
        assertThat(densities.size()).isEqualTo(displayInfos.size - displaysToRemove.size)
        for (displayInfo in displaysToRemove) {
            assertThat(densities.get(displayInfo.displayId)).isEqualTo(0)
        }
        verify(mockTopologyCopy, times(displaysToRemove.size)).getGraph()

        verify(mockTopologyChangedCallback, times(displaysToRemove.size)).invoke(
            android.util.Pair(
@@ -372,7 +352,7 @@ class DisplayTopologyCoordinatorTest {
        val topologyCopy = mock<DisplayTopology>()
        val topologyGraph = mock<DisplayTopologyGraph>()
        whenever(topology.copy()).thenReturn(topologyCopy)
        whenever(topologyCopy.getGraph(any())).thenReturn(topologyGraph)
        whenever(topologyCopy.getGraph()).thenReturn(topologyGraph)
        whenever(mockTopologyStore.saveTopology(topology)).thenReturn(true)

        coordinator.topology = topology