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

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

Merge "Get topology copy, set topology normalize" into main

parents ed7d191a 060d3226
Loading
Loading
Loading
Loading
+159 −139
Original line number Original line Diff line number Diff line
@@ -28,6 +28,7 @@ import android.graphics.RectF;
import android.os.Parcel;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Parcelable;
import android.util.IndentingPrintWriter;
import android.util.IndentingPrintWriter;
import android.util.MathUtils;
import android.util.Pair;
import android.util.Pair;
import android.util.Slog;
import android.util.Slog;
import android.view.Display;
import android.view.Display;
@@ -283,6 +284,146 @@ public final class DisplayTopology implements Parcelable {
        normalize();
        normalize();
    }
    }


    /**
     * Clamp offsets and remove any overlaps between displays.
     */
    public void normalize() {
        if (mRoot == null) {
            return;
        }
        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);

        // 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) {
                return 0;
            }

            int compareDepths = Integer.compare(depths.get(d1), depths.get(d2));
            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));
        };
        List<TreeNode> displays = new ArrayList<>(bounds.keySet());
        displays.sort(comparator);

        for (int i = 1; i < displays.size(); i++) {
            TreeNode targetDisplay = displays.get(i);
            TreeNode 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);

                if (!RectF.intersects(sourceBounds, targetBounds)) {
                    continue;
                }

                // Find the offset by which to move the display. Pick the smaller one among the x
                // and y axes.
                float offsetX = targetBounds.left >= 0
                        ? sourceBounds.right - targetBounds.left
                        : sourceBounds.left - targetBounds.right;
                float offsetY = targetBounds.top >= 0
                        ? sourceBounds.bottom - targetBounds.top
                        : sourceBounds.top - targetBounds.bottom;
                if (Math.abs(offsetX) <= Math.abs(offsetY)) {
                    targetBounds.left += offsetX;
                    targetBounds.right += offsetX;
                    // We need to also update the offset in the tree
                    if (targetDisplay.mPosition == POSITION_TOP
                            || targetDisplay.mPosition == POSITION_BOTTOM) {
                        targetDisplay.mOffset += offsetX;
                    }
                    offsetY = 0;
                } else {
                    targetBounds.top += offsetY;
                    targetBounds.bottom += offsetY;
                    // We need to also update the offset in the tree
                    if (targetDisplay.mPosition == POSITION_LEFT
                            || targetDisplay.mPosition == POSITION_RIGHT) {
                        targetDisplay.mOffset += offsetY;
                    }
                    offsetX = 0;
                }

                lastIntersectingSourceDisplay = sourceDisplay;
                lastOffsetX = offsetX;
                lastOffsetY = offsetY;
            }

            // Now re-parent the target display to the last intersecting source display if it no
            // longer touches its parent.
            if (lastIntersectingSourceDisplay == null) {
                // There was no overlap.
                continue;
            }
            TreeNode parent = parents.get(targetDisplay);
            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
                // already know that the display is adjacent to its parent.
                continue;
            }

            RectF childBounds = bounds.get(targetDisplay);
            RectF parentBounds = bounds.get(parent);
            // Check that the edges are on the same line
            boolean areTouching = switch (targetDisplay.mPosition) {
                case POSITION_LEFT -> floatEquals(parentBounds.left, childBounds.right);
                case POSITION_RIGHT -> floatEquals(parentBounds.right, childBounds.left);
                case POSITION_TOP -> floatEquals(parentBounds.top, childBounds.bottom);
                case POSITION_BOTTOM -> floatEquals(parentBounds.bottom, childBounds.top);
                default -> throw new IllegalStateException(
                        "Unexpected value: " + targetDisplay.mPosition);
            };
            // Check that the offset is within bounds
            areTouching &= switch (targetDisplay.mPosition) {
                case POSITION_LEFT, POSITION_RIGHT ->
                        childBounds.bottom + EPSILON >= parentBounds.top
                                && childBounds.top <= parentBounds.bottom + EPSILON;
                case POSITION_TOP, POSITION_BOTTOM ->
                        childBounds.right + EPSILON >= parentBounds.left
                                && childBounds.left <= parentBounds.right + EPSILON;
                default -> throw new IllegalStateException(
                        "Unexpected value: " + targetDisplay.mPosition);
            };

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

                if (lastOffsetX != 0) {
                    targetDisplay.mPosition = lastOffsetX > 0 ? POSITION_RIGHT : POSITION_LEFT;
                    targetDisplay.mOffset =
                            childBounds.top - lastIntersectingSourceDisplayBounds.top;
                } else if (lastOffsetY != 0) {
                    targetDisplay.mPosition = lastOffsetY > 0 ? POSITION_BOTTOM : POSITION_TOP;
                    targetDisplay.mOffset =
                            childBounds.left - lastIntersectingSourceDisplayBounds.left;
                }
            }
        }
    }

    /**
    /**
     * @return A deep copy of the topology that will not be modified by the system.
     * @return A deep copy of the topology that will not be modified by the system.
     */
     */
@@ -441,145 +582,6 @@ public final class DisplayTopology implements Parcelable {
        }
        }
    }
    }


    /**
     * Update the topology to remove any overlaps between displays.
     */
    @VisibleForTesting
    public void normalize() {
        if (mRoot == null) {
            return;
        }
        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);

        // 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) {
                return 0;
            }

            int compareDepths = Integer.compare(depths.get(d1), depths.get(d2));
            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));
        };
        List<TreeNode> displays = new ArrayList<>(bounds.keySet());
        displays.sort(comparator);

        for (int i = 1; i < displays.size(); i++) {
            TreeNode targetDisplay = displays.get(i);
            TreeNode 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);

                if (!RectF.intersects(sourceBounds, targetBounds)) {
                    continue;
                }

                // Find the offset by which to move the display. Pick the smaller one among the x
                // and y axes.
                float offsetX = targetBounds.left >= 0
                        ? sourceBounds.right - targetBounds.left
                        : sourceBounds.left - targetBounds.right;
                float offsetY = targetBounds.top >= 0
                        ? sourceBounds.bottom - targetBounds.top
                        : sourceBounds.top - targetBounds.bottom;
                if (Math.abs(offsetX) <= Math.abs(offsetY)) {
                    targetBounds.left += offsetX;
                    targetBounds.right += offsetX;
                    // We need to also update the offset in the tree
                    if (targetDisplay.mPosition == POSITION_TOP
                            || targetDisplay.mPosition == POSITION_BOTTOM) {
                        targetDisplay.mOffset += offsetX;
                    }
                    offsetY = 0;
                } else {
                    targetBounds.top += offsetY;
                    targetBounds.bottom += offsetY;
                    // We need to also update the offset in the tree
                    if (targetDisplay.mPosition == POSITION_LEFT
                            || targetDisplay.mPosition == POSITION_RIGHT) {
                        targetDisplay.mOffset += offsetY;
                    }
                    offsetX = 0;
                }

                lastIntersectingSourceDisplay = sourceDisplay;
                lastOffsetX = offsetX;
                lastOffsetY = offsetY;
            }

            // Now re-parent the target display to the last intersecting source display if it no
            // longer touches its parent.
            if (lastIntersectingSourceDisplay == null) {
                // There was no overlap.
                continue;
            }
            TreeNode parent = parents.get(targetDisplay);
            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
                // already know that the display is adjacent to its parent.
                continue;
            }

            RectF childBounds = bounds.get(targetDisplay);
            RectF parentBounds = bounds.get(parent);
            // Check that the edges are on the same line
            boolean areTouching = switch (targetDisplay.mPosition) {
                case POSITION_LEFT -> floatEquals(parentBounds.left, childBounds.right);
                case POSITION_RIGHT -> floatEquals(parentBounds.right, childBounds.left);
                case POSITION_TOP -> floatEquals(parentBounds.top, childBounds.bottom);
                case POSITION_BOTTOM -> floatEquals(parentBounds.bottom, childBounds.top);
                default -> throw new IllegalStateException(
                        "Unexpected value: " + targetDisplay.mPosition);
            };
            // Check that the offset is within bounds
            areTouching &= switch (targetDisplay.mPosition) {
                case POSITION_LEFT, POSITION_RIGHT ->
                        childBounds.bottom + EPSILON >= parentBounds.top
                                && childBounds.top <= parentBounds.bottom + EPSILON;
                case POSITION_TOP, POSITION_BOTTOM ->
                        childBounds.right + EPSILON >= parentBounds.left
                                && childBounds.left <= parentBounds.right + EPSILON;
                default -> throw new IllegalStateException(
                        "Unexpected value: " + targetDisplay.mPosition);
            };

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

                if (lastOffsetX != 0) {
                    targetDisplay.mPosition = lastOffsetX > 0 ? POSITION_RIGHT : POSITION_LEFT;
                    targetDisplay.mOffset =
                            childBounds.top - lastIntersectingSourceDisplayBounds.top;
                } else if (lastOffsetY != 0) {
                    targetDisplay.mPosition = lastOffsetY > 0 ? POSITION_BOTTOM : POSITION_TOP;
                    targetDisplay.mOffset =
                            childBounds.left - lastIntersectingSourceDisplayBounds.left;
                }
            }
        }
    }

    /**
    /**
     * Tests whether two brightness float values are within a small enough tolerance
     * Tests whether two brightness float values are within a small enough tolerance
     * of each other.
     * of each other.
@@ -605,6 +607,24 @@ public final class DisplayTopology implements Parcelable {
        return found;
        return found;
    }
    }


    /**
     * Ensure that the offsets of all displays within the given tree are within bounds.
     * @param display The starting node
     */
    private void clampOffsets(TreeNode display) {
        if (display == null) {
            return;
        }
        for (TreeNode child : display.mChildren) {
            if (child.mPosition == POSITION_LEFT || child.mPosition == POSITION_RIGHT) {
                child.mOffset = MathUtils.constrain(child.mOffset, -child.mHeight, display.mHeight);
            } else if (child.mPosition == POSITION_TOP || child.mPosition == POSITION_BOTTOM) {
                child.mOffset = MathUtils.constrain(child.mOffset, -child.mWidth, display.mWidth);
            }
            clampOffsets(child);
        }
    }

    public static final class TreeNode implements Parcelable {
    public static final class TreeNode implements Parcelable {
        public static final int POSITION_LEFT = 0;
        public static final int POSITION_LEFT = 0;
        public static final int POSITION_TOP = 1;
        public static final int POSITION_TOP = 1;
+106 −183

File changed.

Preview size limit exceeded, changes collapsed.

+2 −1
Original line number Original line Diff line number Diff line
@@ -100,13 +100,14 @@ class DisplayTopologyCoordinator {
     */
     */
    DisplayTopology getTopology() {
    DisplayTopology getTopology() {
        synchronized (mSyncRoot) {
        synchronized (mSyncRoot) {
            return mTopology;
            return mTopology.copy();
        }
        }
    }
    }


    void setTopology(DisplayTopology topology) {
    void setTopology(DisplayTopology topology) {
        synchronized (mSyncRoot) {
        synchronized (mSyncRoot) {
            mTopology = topology;
            mTopology = topology;
            mTopology.normalize();
            sendTopologyUpdateLocked();
            sendTopologyUpdateLocked();
        }
        }
    }
    }
+18 −0
Original line number Original line Diff line number Diff line
@@ -20,6 +20,7 @@ import android.hardware.display.DisplayTopology
import android.util.DisplayMetrics
import android.util.DisplayMetrics
import android.view.Display
import android.view.Display
import android.view.DisplayInfo
import android.view.DisplayInfo
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Before
import org.junit.Test
import org.junit.Test
import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyFloat
@@ -87,4 +88,21 @@ class DisplayTopologyCoordinatorTest {
        verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
        verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
        verify(mockTopologyChangedCallback, never()).invoke(any())
        verify(mockTopologyChangedCallback, never()).invoke(any())
    }
    }

    @Test
    fun getTopology_copy() {
        assertThat(coordinator.topology).isEqualTo(mockTopologyCopy)
    }

    @Test
    fun setTopology_normalize() {
        val topology = mock<DisplayTopology>()
        val topologyCopy = mock<DisplayTopology>()
        whenever(topology.copy()).thenReturn(topologyCopy)

        coordinator.topology = topology

        verify(topology).normalize()
        verify(mockTopologyChangedCallback).invoke(topologyCopy)
    }
}
}
 No newline at end of file