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

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

Merge "Send topology graph to input manager" into main

parents cd6619a6 4a592a5d
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -750,7 +750,7 @@ public final class DisplayTopology implements Parcelable {
                new SparseArray<>();
        for (int id : displayIds) {
            if (densityPerDisplay.get(id) == 0) {
                Slog.w(TAG, "Cannot construct graph, no density for display " + id);
                Slog.e(TAG, "Cannot construct graph, no density for display " + id);
                return null;
            }
            adjacentDisplaysPerId.append(id, new ArrayList<>(Math.min(10, displayIds.size())));
+10 −1
Original line number Diff line number Diff line
@@ -107,6 +107,7 @@ import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayGroupListener;
import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
import android.hardware.display.DisplayTopology;
import android.hardware.display.DisplayTopologyGraph;
import android.hardware.display.DisplayViewport;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
@@ -292,6 +293,7 @@ public final class DisplayManagerService extends SystemService {
    private final DisplayModeDirector mDisplayModeDirector;
    private final ExternalDisplayPolicy mExternalDisplayPolicy;
    private WindowManagerInternal mWindowManagerInternal;
    @Nullable
    private InputManagerInternal mInputManagerInternal;
    private ActivityManagerInternal mActivityManagerInternal;
    private final UidImportanceListener mUidImportanceListener = new UidImportanceListener();
@@ -680,8 +682,15 @@ public final class DisplayManagerService extends SystemService {
        mExternalDisplayPolicy = new ExternalDisplayPolicy(new ExternalDisplayPolicyInjector());
        if (mFlags.isDisplayTopologyEnabled()) {
            final var backupManager = new BackupManager(mContext);
            Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> topologyChangedCallback =
                    update -> {
                        if (mInputManagerInternal != null) {
                            mInputManagerInternal.setDisplayTopology(update.second);
                        }
                        deliverTopologyUpdate(update.first);
                    };
            mDisplayTopologyCoordinator = new DisplayTopologyCoordinator(
                    this::isExtendedDisplayEnabled, this::deliverTopologyUpdate,
                    this::isExtendedDisplayEnabled, topologyChangedCallback,
                    new HandlerExecutor(mHandler), mSyncRoot, backupManager::dataChanged);
        } else {
            mDisplayTopologyCoordinator = null;
+23 −5
Original line number Diff line number Diff line
@@ -19,8 +19,11 @@ package com.android.server.display;
import static android.hardware.display.DisplayTopology.pxToDp;

import android.hardware.display.DisplayTopology;
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;

@@ -53,6 +56,12 @@ 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<>();

@@ -69,13 +78,13 @@ class DisplayTopologyCoordinator {
     * Should be invoked from the corresponding executor.
     * A copy of the topology should be sent that will not be modified by the system.
     */
    private final Consumer<DisplayTopology> mOnTopologyChangedCallback;
    private final Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> mOnTopologyChangedCallback;
    private final Executor mTopologyChangeExecutor;
    private final DisplayManagerService.SyncRoot mSyncRoot;
    private final Runnable mTopologySavedCallback;

    DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayEnabled,
            Consumer<DisplayTopology> onTopologyChangedCallback,
            Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> onTopologyChangedCallback,
            Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot,
            Runnable topologySavedCallback) {
        this(new Injector(), isExtendedDisplayEnabled, onTopologyChangedCallback,
@@ -84,7 +93,7 @@ class DisplayTopologyCoordinator {

    @VisibleForTesting
    DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayEnabled,
            Consumer<DisplayTopology> onTopologyChangedCallback,
            Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> onTopologyChangedCallback,
            Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot,
            Runnable topologySavedCallback) {
        mTopology = injector.getTopology();
@@ -107,7 +116,7 @@ class DisplayTopologyCoordinator {
        }
        synchronized (mSyncRoot) {
            addDisplayIdMappingLocked(info);

            mDensities.put(info.displayId, info.logicalDensityDpi);
            mTopology.addDisplay(info.displayId, getWidth(info), getHeight(info));
            restoreTopologyLocked();
            sendTopologyUpdateLocked();
@@ -119,7 +128,13 @@ class DisplayTopologyCoordinator {
     * @param info The new display info
     */
    void onDisplayChanged(DisplayInfo info) {
        if (!isDisplayAllowedInTopology(info)) {
            return;
        }
        synchronized (mSyncRoot) {
            if (mDensities.indexOfKey(info.displayId) >= 0) {
                mDensities.put(info.displayId, info.logicalDensityDpi);
            }
            if (mTopology.updateDisplay(info.displayId, getWidth(info), getHeight(info))) {
                sendTopologyUpdateLocked();
            }
@@ -132,6 +147,7 @@ class DisplayTopologyCoordinator {
     */
    void onDisplayRemoved(int displayId) {
        synchronized (mSyncRoot) {
            mDensities.delete(displayId);
            if (mTopology.removeDisplay(displayId)) {
                removeDisplayIdMappingLocked(displayId);
                restoreTopologyLocked();
@@ -256,7 +272,9 @@ class DisplayTopologyCoordinator {
    @GuardedBy("mSyncRoot")
    private void sendTopologyUpdateLocked() {
        DisplayTopology copy = mTopology.copy();
        mTopologyChangeExecutor.execute(() -> mOnTopologyChangedCallback.accept(copy));
        SparseIntArray densities = mDensities.clone();
        mTopologyChangeExecutor.execute(() -> mOnTopologyChangedCallback.accept(
                new Pair<>(copy, copy.getGraph(densities))));
    }

    @VisibleForTesting
+11 −1
Original line number Diff line number Diff line
@@ -107,6 +107,7 @@ import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayOffloader;
import android.hardware.display.DisplayTopology;
import android.hardware.display.DisplayTopologyGraph;
import android.hardware.display.DisplayViewport;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
@@ -136,6 +137,7 @@ 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;
@@ -3875,8 +3877,16 @@ public class DisplayManagerServiceTest {
                displayManager.new BinderService();
        registerDefaultDisplays(displayManager);
        initDisplayPowerController(localService);
        displayManager.windowManagerAndInputReady();

        DisplayTopology topology = mock(DisplayTopology.class);
        when(topology.copy()).thenReturn(topology);
        DisplayTopologyGraph graph = mock(DisplayTopologyGraph.class);
        when(topology.getGraph(any(SparseIntArray.class))).thenReturn(graph);
        displayManagerBinderService.setDisplayTopology(topology);
        waitForIdleHandler(displayManager.getDisplayHandler());

        displayManagerBinderService.setDisplayTopology(new DisplayTopology());
        verify(mMockInputManagerInternal).setDisplayTopology(graph);
    }

    @Test
+138 −29
Original line number Diff line number Diff line
@@ -18,39 +18,51 @@ package com.android.server.display

import android.hardware.display.DisplayTopology
import android.hardware.display.DisplayTopology.pxToDp
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.anyFloat
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.any
import org.mockito.kotlin.clearInvocations
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

class DisplayTopologyCoordinatorTest {
    private lateinit var coordinator: DisplayTopologyCoordinator
    private val displayInfo = DisplayInfo()
    private lateinit var displayInfos: List<DisplayInfo>
    private val topologyChangeExecutor = Runnable::run

    private val mockTopologyStore = mock<DisplayTopologyStore>()
    private val mockTopology = mock<DisplayTopology>()
    private val mockTopologyCopy = mock<DisplayTopology>()
    private val mockTopologyGraph = mock<DisplayTopologyGraph>()
    private val mockIsExtendedDisplayEnabled = mock<() -> Boolean>()
    private val mockTopologySavedCallback = mock<() -> Unit>()
    private val mockTopologyChangedCallback = mock<(DisplayTopology) -> Unit>()
    private val mockTopologyChangedCallback =
        mock<(android.util.Pair<DisplayTopology, DisplayTopologyGraph>) -> Unit>()

    @Before
    fun setUp() {
        displayInfo.displayId = Display.DEFAULT_DISPLAY
        displayInfo.logicalWidth = 300
        displayInfo.logicalHeight = 200
        displayInfo.logicalDensityDpi = 100
        displayInfos = (1..10).map { i ->
            val info = DisplayInfo()
            info.displayId = i
            info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP
            info.logicalWidth = i * 300
            info.logicalHeight = i * 200
            info.logicalDensityDpi = i * 100
            return@map info
        }

        val injector = object : DisplayTopologyCoordinator.Injector() {
            override fun getTopology() = mockTopology
@@ -62,6 +74,7 @@ class DisplayTopologyCoordinatorTest {
        }
        whenever(mockIsExtendedDisplayEnabled()).thenReturn(true)
        whenever(mockTopology.copy()).thenReturn(mockTopologyCopy)
        whenever(mockTopologyCopy.getGraph(any())).thenReturn(mockTopologyGraph)
        coordinator = DisplayTopologyCoordinator(injector, mockIsExtendedDisplayEnabled,
            mockTopologyChangedCallback, topologyChangeExecutor, DisplayManagerService.SyncRoot(),
            mockTopologySavedCallback)
@@ -69,20 +82,45 @@ class DisplayTopologyCoordinatorTest {

    @Test
    fun addDisplay() {
        displayInfos.forEachIndexed { i, displayInfo ->
            coordinator.onDisplayAdded(displayInfo)

            val widthDp = pxToDp(displayInfo.logicalWidth.toFloat(), displayInfo.logicalDensityDpi)
        val heightDp = pxToDp(displayInfo.logicalHeight.toFloat(), displayInfo.logicalDensityDpi)
            val heightDp =
                pxToDp(displayInfo.logicalHeight.toFloat(), displayInfo.logicalDensityDpi)
            verify(mockTopology).addDisplay(displayInfo.displayId, widthDp, heightDp)
        verify(mockTopologyChangedCallback).invoke(mockTopologyCopy)
        verify(mockTopologyStore).restoreTopology(mockTopology)
        }

        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(mockTopologyChangedCallback, times(displayInfos.size)).invoke(
            android.util.Pair(
                mockTopologyCopy,
                mockTopologyGraph
            )
        )
        verify(mockTopologyStore, times(displayInfos.size)).restoreTopology(mockTopology)

        // Clear invocations for other tests that call this method
        clearInvocations(mockTopologyCopy)
        clearInvocations(mockTopologyChangedCallback)
        clearInvocations(mockTopologyStore)
    }

    @Test
    fun addDisplay_extendedDisplaysDisabled() {
        whenever(mockIsExtendedDisplayEnabled()).thenReturn(false)

        for (displayInfo in displayInfos) {
            coordinator.onDisplayAdded(displayInfo)
        }

        verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
        verify(mockTopologyChangedCallback, never()).invoke(any())
@@ -91,9 +129,9 @@ class DisplayTopologyCoordinatorTest {

    @Test
    fun addDisplay_notInDefaultDisplayGroup() {
        displayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1
        displayInfos[0].displayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1

        coordinator.onDisplayAdded(displayInfo)
        coordinator.onDisplayAdded(displayInfos[0])

        verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
        verify(mockTopologyChangedCallback, never()).invoke(any())
@@ -102,39 +140,102 @@ class DisplayTopologyCoordinatorTest {

    @Test
    fun updateDisplay() {
        whenever(mockTopology.updateDisplay(eq(Display.DEFAULT_DISPLAY), anyFloat(), anyFloat()))
        whenever(mockTopology.updateDisplay(eq(displayInfos[0].displayId), anyFloat(), anyFloat()))
            .thenReturn(true)
        addDisplay()

        coordinator.onDisplayChanged(displayInfo)
        displayInfos[0].logicalDensityDpi += 10
        coordinator.onDisplayChanged(displayInfos[0])

        verify(mockTopologyChangedCallback).invoke(mockTopologyCopy)
        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(mockTopologyChangedCallback).invoke(
            android.util.Pair(
                mockTopologyCopy,
                mockTopologyGraph
            )
        )
    }

    @Test
    fun updateDisplay_notChanged() {
        whenever(mockTopology.updateDisplay(eq(Display.DEFAULT_DISPLAY), anyFloat(), anyFloat()))
            .thenReturn(false)
        addDisplay()

        for (displayInfo in displayInfos) {
            coordinator.onDisplayChanged(displayInfo)
        }

        // Try to update a display that does not exist
        val info = DisplayInfo()
        info.displayId = 100
        coordinator.onDisplayChanged(info)

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

    @Test
    fun updateDisplay_extendedDisplaysDisabled() {
        whenever(mockIsExtendedDisplayEnabled()).thenReturn(false)

        for (displayInfo in displayInfos) {
            coordinator.onDisplayAdded(displayInfo)
        }

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

    @Test
    fun updateDisplay_notInDefaultDisplayGroup() {
        displayInfos[0].displayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1

        coordinator.onDisplayAdded(displayInfos[0])

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

    @Test
    fun removeDisplay() {
        whenever(mockTopology.removeDisplay(Display.DEFAULT_DISPLAY)).thenReturn(true)
        addDisplay()

        val displaysToRemove = listOf(0, 2, 3).map { displayInfos[it] }
        for (displayInfo in displaysToRemove) {
            whenever(mockTopology.removeDisplay(displayInfo.displayId)).thenReturn(true)

            coordinator.onDisplayRemoved(displayInfo.displayId)
        }

        coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY)
        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(mockTopologyChangedCallback).invoke(mockTopologyCopy)
        verify(mockTopologyStore).restoreTopology(mockTopology)
        verify(mockTopologyChangedCallback, times(displaysToRemove.size)).invoke(
            android.util.Pair(
                mockTopologyCopy,
                mockTopologyGraph
            )
        )
        verify(mockTopologyStore, times(displaysToRemove.size)).restoreTopology(mockTopology)
    }

    @Test
    fun removeDisplay_notChanged() {
        whenever(mockTopology.removeDisplay(Display.DEFAULT_DISPLAY)).thenReturn(false)

        coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY)
        for (displayInfo in displayInfos) {
            coordinator.onDisplayRemoved(displayInfo.displayId)
        }

        verify(mockTopologyChangedCallback, never()).invoke(any())
        verify(mockTopologyStore, never()).restoreTopology(any())
@@ -149,12 +250,20 @@ class DisplayTopologyCoordinatorTest {
    fun setTopology_normalize() {
        val topology = mock<DisplayTopology>()
        val topologyCopy = mock<DisplayTopology>()
        val topologyGraph = mock<DisplayTopologyGraph>()
        whenever(topology.copy()).thenReturn(topologyCopy)
        whenever(topologyCopy.getGraph(any())).thenReturn(topologyGraph)
        whenever(mockTopologyStore.saveTopology(topology)).thenReturn(true)

        coordinator.topology = topology

        verify(topology).normalize()
        verify(mockTopologyChangedCallback).invoke(topologyCopy)
        verify(mockTopologyChangedCallback).invoke(
            android.util.Pair(
                topologyCopy,
                topologyGraph
            )
        )
        verify(mockTopologyStore).saveTopology(topology)
        verify(mockTopologySavedCallback).invoke()
    }