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

Commit 51502aac authored by Piotr Wilczyński's avatar Piotr Wilczyński Committed by Android (Google) Code Review
Browse files

Merge "Update topology when display removed" into main

parents 19719a16 ec7d1c26
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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());
    }
+81 −19
Original line number Diff line number Diff line
@@ -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 {
            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);
                mPrimaryDisplayId = Display.INVALID_DISPLAY;
            }
            Slog.i(TAG,  "Primary display with ID " + displayId
                    + " removed, new primary display: " + mPrimaryDisplayId);
        } else {
            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 {

+10 −0
Original line number Diff line number Diff line
@@ -65,6 +65,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.
+130 −3
Original line number Diff line number Diff line
@@ -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