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

Commit 82907c57 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Topology get and set API" into main

parents 85091178 7853e4a5
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.hardware.display;

import static android.Manifest.permission.MANAGE_DISPLAYS;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.HdrCapabilities.HdrType;
import static android.view.Display.INVALID_DISPLAY;
@@ -1763,6 +1764,29 @@ public final class DisplayManager {
        return mDisplayIdToMirror;
    }

    /**
     * @return The current display topology that represents the relative positions of extended
     * displays.
     *
     * @hide
     */
    @RequiresPermission(MANAGE_DISPLAYS)
    @Nullable
    public DisplayTopology getDisplayTopology() {
        return mGlobal.getDisplayTopology();
    }

    /**
     * Set the relative positions between extended displays (display topology).
     * @param topology The display topology to be set
     *
     * @hide
     */
    @RequiresPermission(MANAGE_DISPLAYS)
    public void setDisplayTopology(DisplayTopology topology) {
        mGlobal.setDisplayTopology(topology);
    }

    /**
     * Listens for changes in available display devices.
     */
+26 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.hardware.display;


import static android.hardware.display.DisplayManager.EventFlag;
import static android.Manifest.permission.MANAGE_DISPLAYS;
import static android.view.Display.HdrCapabilities.HdrType;

import android.Manifest;
@@ -1285,6 +1286,31 @@ public final class DisplayManagerGlobal {
        }
    }

    /**
     * @see DisplayManager#getDisplayTopology
     */
    @RequiresPermission(MANAGE_DISPLAYS)
    @Nullable
    public DisplayTopology getDisplayTopology() {
        try {
            return mDm.getDisplayTopology();
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

    /**
     * @see DisplayManager#setDisplayTopology
     */
    @RequiresPermission(MANAGE_DISPLAYS)
    public void setDisplayTopology(DisplayTopology topology) {
        try {
            mDm.setDisplayTopology(topology);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

    private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
        @Override
        public void onDisplayEvent(int displayId, @DisplayEvent int event) {
+19 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.hardware.display;

parcelable DisplayTopology;
+189 −40
Original line number Diff line number Diff line
@@ -14,25 +14,34 @@
 * limitations under the License.
 */

package com.android.server.display;
package android.hardware.display;

import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_BOTTOM;
import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_LEFT;
import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_TOP;
import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_RIGHT;
import static android.hardware.display.DisplayTopology.TreeNode.POSITION_BOTTOM;
import static android.hardware.display.DisplayTopology.TreeNode.POSITION_LEFT;
import static android.hardware.display.DisplayTopology.TreeNode.POSITION_RIGHT;
import static android.hardware.display.DisplayTopology.TreeNode.POSITION_TOP;

import android.annotation.IntDef;
import android.annotation.Nullable;
import android.graphics.RectF;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.Slog;
import android.view.Display;

import androidx.annotation.NonNull;

import com.android.internal.annotations.VisibleForTesting;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@@ -42,24 +51,59 @@ import java.util.Queue;
/**
 * Represents the relative placement of extended displays.
 * Does not support concurrent calls, so a lock should be held when calling into this class.
 *
 * @hide
 */
class DisplayTopology {
public final class DisplayTopology implements Parcelable {
    private static final String TAG = "DisplayTopology";
    private static final float EPSILON = 0.0001f;

    @android.annotation.NonNull
    public static final Creator<DisplayTopology> CREATOR =
            new Creator<>() {
                @Override
                public DisplayTopology createFromParcel(Parcel source) {
                    return new DisplayTopology(source);
                }

                @Override
                public DisplayTopology[] newArray(int size) {
                    return new DisplayTopology[size];
                }
            };

    /**
     * The topology tree
     */
    @Nullable
    @VisibleForTesting
    TreeNode mRoot;
    private TreeNode mRoot;

    /**
     * The logical display ID of the primary display that will show certain UI elements.
     * This is not necessarily the same as the default display.
     */
    private int mPrimaryDisplayId = Display.INVALID_DISPLAY;

    public DisplayTopology() {}

    @VisibleForTesting
    int mPrimaryDisplayId = Display.INVALID_DISPLAY;
    public DisplayTopology(TreeNode root, int primaryDisplayId) {
        mRoot = root;
        mPrimaryDisplayId = primaryDisplayId;
    }

    public DisplayTopology(Parcel source) {
        this(source.readTypedObject(TreeNode.CREATOR), source.readInt());
    }

    @Nullable
    public TreeNode getRoot() {
        return mRoot;
    }

    public int getPrimaryDisplayId() {
        return mPrimaryDisplayId;
    }

    /**
     * Add a display to the topology.
@@ -69,7 +113,7 @@ class DisplayTopology {
     * @param width The width of the display
     * @param height The height of the display
     */
    void addDisplay(int displayId, float width, float height) {
    public void addDisplay(int displayId, float width, float height) {
        addDisplay(displayId, width, height, /* shouldLog= */ true);
    }

@@ -79,7 +123,7 @@ class DisplayTopology {
     * one by one.
     * @param displayId The logical display ID
     */
    void removeDisplay(int displayId) {
    public void removeDisplay(int displayId) {
        if (findDisplay(displayId, mRoot) == null) {
            return;
        }
@@ -106,11 +150,22 @@ class DisplayTopology {
        }
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeTypedObject(mRoot, flags);
        dest.writeInt(mPrimaryDisplayId);
    }

    /**
     * Print the object's state and debug information into the given stream.
     * @param pw The stream to dump information to.
     */
    void dump(PrintWriter pw) {
    public void dump(PrintWriter pw) {
        pw.println("DisplayTopology:");
        pw.println("--------------------");
        IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
@@ -126,13 +181,21 @@ class DisplayTopology {
        }
    }

    @Override
    public String toString() {
        StringWriter out = new StringWriter();
        PrintWriter writer = new PrintWriter(out);
        dump(writer);
        return out.toString();
    }

    private void addDisplay(int displayId, float width, float height, boolean shouldLog) {
        if (findDisplay(displayId, mRoot) != null) {
            throw new IllegalArgumentException(
                    "DisplayTopology: attempting to add a display that already exists");
        }
        if (mRoot == null) {
            mRoot = new TreeNode(displayId, width, height, /* position= */ null, /* offset= */ 0);
            mRoot = new TreeNode(displayId, width, height, /* position= */ 0, /* offset= */ 0);
            mPrimaryDisplayId = displayId;
            if (shouldLog) {
                Slog.i(TAG, "First display added: " + mRoot);
@@ -241,7 +304,7 @@ class DisplayTopology {
     * Update the topology to remove any overlaps between displays.
     */
    @VisibleForTesting
    void normalize() {
    public void normalize() {
        if (mRoot == null) {
            return;
        }
@@ -341,6 +404,8 @@ class DisplayTopology {
                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) {
@@ -350,6 +415,8 @@ class DisplayTopology {
                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) {
@@ -379,36 +446,56 @@ class DisplayTopology {
     * @param b second float to compare
     * @return whether the two values are within a small enough tolerance value
     */
    public static boolean floatEquals(float a, float b) {
        return a == b || Float.isNaN(a) && Float.isNaN(b) || Math.abs(a - b) < EPSILON;
    private static boolean floatEquals(float a, float b) {
        return a == b || (Float.isNaN(a) && Float.isNaN(b)) || Math.abs(a - b) < EPSILON;
    }

    @VisibleForTesting
    static class TreeNode {
    public static final class TreeNode implements Parcelable {
        public static final int POSITION_LEFT = 0;
        public static final int POSITION_TOP = 1;
        public static final int POSITION_RIGHT = 2;
        public static final int POSITION_BOTTOM = 3;

        @IntDef(prefix = { "POSITION_" }, value = {
                POSITION_LEFT, POSITION_TOP, POSITION_RIGHT, POSITION_BOTTOM
        })
        @Retention(RetentionPolicy.SOURCE)
        public @interface Position{}

        @android.annotation.NonNull
        public static final Creator<TreeNode> CREATOR =
                new Creator<>() {
                    @Override
                    public TreeNode createFromParcel(Parcel source) {
                        return new TreeNode(source);
                    }

                    @Override
                    public TreeNode[] newArray(int size) {
                        return new TreeNode[size];
                    }
                };

        /**
         * The logical display ID
         */
        @VisibleForTesting
        final int mDisplayId;
        private final int mDisplayId;

        /**
         * The width of the display in density-independent pixels (dp).
         */
        @VisibleForTesting
        float mWidth;
        private final float mWidth;

        /**
         * The height of the display in density-independent pixels (dp).
         */
        @VisibleForTesting
        float mHeight;
        private final float mHeight;

        /**
         * The position of this display relative to its parent.
         */
        @VisibleForTesting
        Position mPosition;
        @Position
        private int mPosition;

        /**
         * The distance from the top edge of the parent display to the top edge of this display (in
@@ -416,13 +503,13 @@ class DisplayTopology {
         * to the left edge of this display (in case of POSITION_TOP or POSITION_BOTTOM). The unit
         * used is density-independent pixels (dp).
         */
        @VisibleForTesting
        float mOffset;
        private float mOffset;

        @VisibleForTesting
        final List<TreeNode> mChildren = new ArrayList<>();
        private final List<TreeNode> mChildren = new ArrayList<>();

        TreeNode(int displayId, float width, float height, Position position, float offset) {
        @VisibleForTesting
        public TreeNode(int displayId, float width, float height, @Position int position,
                float offset) {
            mDisplayId = displayId;
            mWidth = width;
            mHeight = height;
@@ -430,11 +517,76 @@ class DisplayTopology {
            mOffset = offset;
        }

        public TreeNode(Parcel source) {
            this(source.readInt(), source.readFloat(), source.readFloat(), source.readInt(),
                    source.readFloat());
            source.readTypedList(mChildren, CREATOR);
        }

        public int getDisplayId() {
            return mDisplayId;
        }

        public float getWidth() {
            return mWidth;
        }

        public float getHeight() {
            return mHeight;
        }

        public int getPosition() {
            return mPosition;
        }

        public float getOffset() {
            return mOffset;
        }

        public List<TreeNode> getChildren() {
            return Collections.unmodifiableList(mChildren);
        }

        @Override
        public String toString() {
            return "Display {id=" + mDisplayId + ", width=" + mWidth + ", height=" + mHeight
                    + ", position=" + positionToString(mPosition) + ", offset=" + mOffset + "}";
        }

        /**
         * @param position The position
         * @return The string representation
         */
        public static String positionToString(@Position int position) {
            return switch (position) {
                case POSITION_LEFT -> "left";
                case POSITION_TOP -> "top";
                case POSITION_RIGHT -> "right";
                case POSITION_BOTTOM -> "bottom";
                default -> throw new IllegalStateException("Unexpected value: " + position);
            };
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(@NonNull Parcel dest, int flags) {
            dest.writeInt(mDisplayId);
            dest.writeFloat(mWidth);
            dest.writeFloat(mHeight);
            dest.writeInt(mPosition);
            dest.writeFloat(mOffset);
            dest.writeTypedList(mChildren);
        }

        /**
         * Print the object's state and debug information into the given stream.
         * @param ipw The stream to dump information to.
         */
        void dump(IndentingPrintWriter ipw) {
        public void dump(IndentingPrintWriter ipw) {
            ipw.println(this);
            ipw.increaseIndent();
            for (TreeNode child : mChildren) {
@@ -443,15 +595,12 @@ class DisplayTopology {
            ipw.decreaseIndent();
        }

        @Override
        public String toString() {
            return "Display {id=" + mDisplayId + ", width=" + mWidth + ", height=" + mHeight
                    + ", position=" + mPosition + ", offset=" + mOffset + "}";
        }

        /**
         * @param child The child to add
         */
        @VisibleForTesting
        enum Position {
            POSITION_LEFT, POSITION_TOP, POSITION_RIGHT, POSITION_BOTTOM
        public void addChild(TreeNode child) {
            mChildren.add(child);
        }
    }
}
+10 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.BrightnessInfo;
import android.hardware.display.Curve;
import android.hardware.graphics.common.DisplayDecorationSupport;
import android.hardware.display.DisplayTopology;
import android.hardware.display.HdrConversionMode;
import android.hardware.display.IDisplayManagerCallback;
import android.hardware.display.IVirtualDisplayCallback;
@@ -254,4 +255,13 @@ interface IDisplayManager {
    // Get the default doze brightness
    @EnforcePermission("CONTROL_DISPLAY_BRIGHTNESS")
    float getDefaultDozeBrightness(int displayId);

    // Get the display topology
    @EnforcePermission("MANAGE_DISPLAYS")
    @nullable
    DisplayTopology getDisplayTopology();

    // Set the display topology
    @EnforcePermission("MANAGE_DISPLAYS")
    void setDisplayTopology(in DisplayTopology topology);
}
Loading