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

Commit 2b418ad3 authored by Oleg Blinnikov's avatar Oleg Blinnikov
Browse files

Call save&restore topology when needed

Change-Id: I6c34a030a360094f3758034167bb1a9a4af8a24d
Bug: 364907408
Test: atest DisplayTopologyCoordinatorTest DisplayManagerServiceTest
Flag: com.android.server.display.feature.flags.display_topology
parent 3b3d48e7
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -475,6 +475,12 @@ public abstract class DisplayManagerInternal {
     */
    public abstract boolean isDisplayReadyForMirroring(int displayId);

    /**
     * Called by {@link  com.android.server.display.DisplayBackupHelper} when backup files were
     * restored and are ready to be reloaded.
     */
    public abstract void reloadTopologies(int userId);


    /**
     * Used by the window manager to override the per-display screen brightness based on the
+34 −3
Original line number Diff line number Diff line
@@ -69,6 +69,7 @@ import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.backup.BackupManager;
import android.app.compat.CompatChanges;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.VirtualDeviceManager;
@@ -673,9 +674,10 @@ public final class DisplayManagerService extends SystemService {
                mExternalDisplayStatsService);
        mExternalDisplayPolicy = new ExternalDisplayPolicy(new ExternalDisplayPolicyInjector());
        if (mFlags.isDisplayTopologyEnabled()) {
            mDisplayTopologyCoordinator =
                    new DisplayTopologyCoordinator(this::isExtendedDisplayEnabled,
                            this::deliverTopologyUpdate, new HandlerExecutor(mHandler), mSyncRoot);
            final var backupManager = new BackupManager(mContext);
            mDisplayTopologyCoordinator = new DisplayTopologyCoordinator(
                    this::isExtendedDisplayEnabled, this::deliverTopologyUpdate,
                    new HandlerExecutor(mHandler), mSyncRoot, backupManager::dataChanged);
        } else {
            mDisplayTopologyCoordinator = null;
        }
@@ -754,6 +756,11 @@ public final class DisplayManagerService extends SystemService {
        }
    }

    @Override
    public void onUserUnlocked(@NonNull final TargetUser to) {
        scheduleTopologiesReload(to.getUserIdentifier(), /*isUserSwitching=*/ true);
    }

    @Override
    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
        final int newUserId = to.getUserIdentifier();
@@ -793,6 +800,11 @@ public final class DisplayManagerService extends SystemService {
            if (mFlags.isDisplayContentModeManagementEnabled()) {
                updateMirrorBuiltInDisplaySettingLocked();
            }

            final UserManager userManager = getUserManager();
            if (null != userManager && userManager.isUserUnlockingOrUnlocked(mCurrentUserId)) {
                scheduleTopologiesReload(mCurrentUserId, /*isUserSwitching=*/ true);
            }
        }
    }

@@ -870,6 +882,8 @@ public final class DisplayManagerService extends SystemService {

        mSmallAreaDetectionController = (mFlags.isSmallAreaDetectionEnabled())
                ? SmallAreaDetectionController.create(mContext) : null;

        scheduleTopologiesReload(mCurrentUserId, /*isUserSwitching=*/ false);
    }

    @VisibleForTesting
@@ -922,6 +936,15 @@ public final class DisplayManagerService extends SystemService {
        return mDisplayNotificationManager;
    }

    private void scheduleTopologiesReload(final int userId, final boolean isUserSwitching) {
        if (mDisplayTopologyCoordinator != null) {
            // Need background thread due to xml files read operations not allowed on Display thread
            BackgroundThread.getHandler().post(() ->
                    mDisplayTopologyCoordinator.reloadTopologies(
                            userId, isUserSwitching));
        }
    }

    private void loadStableDisplayValuesLocked() {
        final Point size = mPersistentDataStore.getStableDisplaySize();
        if (size.x > 0 && size.y > 0) {
@@ -5901,6 +5924,14 @@ public final class DisplayManagerService extends SystemService {
        public boolean isDisplayReadyForMirroring(int displayId) {
            return mExternalDisplayPolicy.isDisplayReadyForMirroring(displayId);
        }

        @Override
        public void reloadTopologies(final int userId) {
            // Reload topologies only if the userId matches the current user id.
            if (userId == mCurrentUserId) {
                scheduleTopologiesReload(mCurrentUserId, /*isUserSwitching=*/ false);
            }
        }
    }

    class DesiredDisplayModeSpecsObserver
+116 −4
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.server.display;
import static android.hardware.display.DisplayTopology.pxToDp;

import android.hardware.display.DisplayTopology;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;

@@ -26,6 +28,8 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
@@ -35,9 +39,25 @@ import java.util.function.Consumer;
 * persisting the topology.
 */
class DisplayTopologyCoordinator {
    private static final String TAG = "DisplayTopologyCoordinator";

    private static String getUniqueId(DisplayInfo info) {
        if (info.displayId == Display.DEFAULT_DISPLAY && info.type == Display.TYPE_INTERNAL) {
            return "internal";
        }
        return info.uniqueId;
    }

    // Persistent data store for display topologies.
    private final DisplayTopologyStore mTopologyStore;

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

    @GuardedBy("mSyncRoot")
    private final SparseArray<String> mDisplayIdToUniqueIdMapping = new SparseArray<>();

    /**
     * Check if extended displays are enabled. If not, a topology is not needed.
@@ -52,23 +72,29 @@ class DisplayTopologyCoordinator {
    private final Consumer<DisplayTopology> mOnTopologyChangedCallback;
    private final Executor mTopologyChangeExecutor;
    private final DisplayManagerService.SyncRoot mSyncRoot;
    private final Runnable mTopologySavedCallback;

    DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayEnabled,
            Consumer<DisplayTopology> onTopologyChangedCallback,
            Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot) {
            Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot,
            Runnable topologySavedCallback) {
        this(new Injector(), isExtendedDisplayEnabled, onTopologyChangedCallback,
                topologyChangeExecutor, syncRoot);
                topologyChangeExecutor, syncRoot, topologySavedCallback);
    }

    @VisibleForTesting
    DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayEnabled,
            Consumer<DisplayTopology> onTopologyChangedCallback,
            Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot) {
            Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot,
            Runnable topologySavedCallback) {
        mTopology = injector.getTopology();
        mIsExtendedDisplayEnabled = isExtendedDisplayEnabled;
        mOnTopologyChangedCallback = onTopologyChangedCallback;
        mTopologyChangeExecutor = topologyChangeExecutor;
        mSyncRoot = syncRoot;
        mTopologyStore = injector.createTopologyStore(
                mDisplayIdToUniqueIdMapping, mUniqueIdToDisplayIdMapping);
        mTopologySavedCallback = topologySavedCallback;
    }

    /**
@@ -80,7 +106,10 @@ class DisplayTopologyCoordinator {
            return;
        }
        synchronized (mSyncRoot) {
            addDisplayIdMappingLocked(info);

            mTopology.addDisplay(info.displayId, getWidth(info), getHeight(info));
            restoreTopologyLocked();
            sendTopologyUpdateLocked();
        }
    }
@@ -104,11 +133,39 @@ class DisplayTopologyCoordinator {
    void onDisplayRemoved(int displayId) {
        synchronized (mSyncRoot) {
            if (mTopology.removeDisplay(displayId)) {
                removeDisplayIdMappingLocked(displayId);
                restoreTopologyLocked();
                sendTopologyUpdateLocked();
            }
        }
    }

    /**
     * Loads all topologies from the persistent topology store for the given userId.
     * @param userId the user id, same as returned from
     *              {@link android.app.ActivityManagerInternal#getCurrentUserId()}.
     * @param isUserSwitching whether the id of the user is currently switching.
     */
    void reloadTopologies(int userId, boolean isUserSwitching) {
        boolean isTopologySaved = false;
        synchronized (mSyncRoot) {
            mTopologyStore.reloadTopologies(userId);
            boolean isTopologyRestored = restoreTopologyLocked();
            if (isTopologyRestored) {
                sendTopologyUpdateLocked();
            }
            if (isUserSwitching && !isTopologyRestored) {
                // During user switch, if topology is not restored - last user topology is the
                // good initial guess. Save this topology for consistent use in the future.
                isTopologySaved = mTopologyStore.saveTopology(mTopology);
            }
        }

        if (isTopologySaved) {
            mTopologySavedCallback.run();
        }
    }

    /**
     * @return A deep copy of the topology.
     */
@@ -119,10 +176,16 @@ class DisplayTopologyCoordinator {
    }

    void setTopology(DisplayTopology topology) {
        final boolean isTopologySaved;
        synchronized (mSyncRoot) {
            topology.normalize();
            mTopology = topology;
            mTopology.normalize();
            sendTopologyUpdateLocked();
            isTopologySaved = mTopologyStore.saveTopology(topology);
        }

        if (isTopologySaved) {
            mTopologySavedCallback.run();
        }
    }

@@ -136,6 +199,24 @@ class DisplayTopologyCoordinator {
        }
    }

    @GuardedBy("mSyncRoot")
    private void removeDisplayIdMappingLocked(final int displayId) {
        final String uniqueId = mDisplayIdToUniqueIdMapping.get(displayId);
        if (null == uniqueId) {
            Slog.e(TAG, "Can't find uniqueId for displayId=" + displayId);
            return;
        }
        mDisplayIdToUniqueIdMapping.remove(displayId);
        mUniqueIdToDisplayIdMapping.remove(uniqueId);
    }

    @GuardedBy("mSyncRoot")
    private void addDisplayIdMappingLocked(DisplayInfo info) {
        final String uniqueId = getUniqueId(info);
        mUniqueIdToDisplayIdMapping.put(uniqueId, info.displayId);
        mDisplayIdToUniqueIdMapping.put(info.displayId, uniqueId);
    }

    /**
     * @param info The display info
     * @return The width of the display in dp
@@ -157,6 +238,21 @@ class DisplayTopologyCoordinator {
                && info.displayGroupId == Display.DEFAULT_DISPLAY_GROUP;
    }

    /**
     * Restores {@link #mTopology} from {@link #mTopologyStore}, saves it in {@link #mTopology}.
     * @return true if the topology was restored, false otherwise.
     */
    @GuardedBy("mSyncRoot")
    private boolean restoreTopologyLocked() {
        var restoredTopology = mTopologyStore.restoreTopology(mTopology);
        if (restoredTopology == null) {
            return false;
        }
        mTopology = restoredTopology;
        mTopology.normalize();
        return true;
    }

    @GuardedBy("mSyncRoot")
    private void sendTopologyUpdateLocked() {
        DisplayTopology copy = mTopology.copy();
@@ -168,5 +264,21 @@ class DisplayTopologyCoordinator {
        DisplayTopology getTopology() {
            return new DisplayTopology();
        }

        DisplayTopologyStore createTopologyStore(
                SparseArray<String> displayIdToUniqueIdMapping,
                Map<String, Integer> uniqueIdToDisplayIdMapping) {
            return new DisplayTopologyXmlStore(new DisplayTopologyXmlStore.Injector() {
                @Override
                public SparseArray<String> getDisplayIdToUniqueIdMapping() {
                    return displayIdToUniqueIdMapping;
                }

                @Override
                public Map<String, Integer> getUniqueIdToDisplayIdMapping() {
                    return uniqueIdToDisplayIdMapping;
                }
            });
        }
    }
}
+57 −7
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_C
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
import static android.hardware.display.DisplayManagerGlobal.INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED;
import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_SYSTEM;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
import static android.provider.Settings.Secure.MIRROR_BUILT_IN_DISPLAY;
@@ -412,6 +413,9 @@ public class DisplayManagerServiceTest {
                    .setStrictness(Strictness.LENIENT)
                    .spyStatic(SystemProperties.class)
                    .build();

    private int mUniqueIdCount = 0;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
@@ -3735,6 +3739,7 @@ public class DisplayManagerServiceTest {
    public void testSetDisplayTopology() {
        manageDisplaysPermission(/* granted= */ true);
        when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
        when(mMockFlags.isDisplayContentModeManagementEnabled()).thenReturn(true);
        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
        DisplayManagerInternal localService = displayManager.new LocalService();
        DisplayManagerService.BinderService displayManagerBinderService =
@@ -3763,6 +3768,7 @@ public class DisplayManagerServiceTest {
    public void testShouldNotifyTopologyChanged() {
        manageDisplaysPermission(/* granted= */ true);
        when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
        when(mMockFlags.isDisplayContentModeManagementEnabled()).thenReturn(true);
        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
        DisplayManagerService.BinderService displayManagerBinderService =
                displayManager.new BinderService();
@@ -3771,19 +3777,22 @@ public class DisplayManagerServiceTest {

        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
        displayManagerBinderService.registerCallbackWithEventMask(callback,
                DisplayManagerGlobal.INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED);
                INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED);
        waitForIdleHandler(handler);

        displayManagerBinderService.setDisplayTopology(new DisplayTopology());
        waitForIdleHandler(handler);

        assertThat(callback.receivedEvents()).containsExactly(TOPOLOGY_CHANGED_EVENT);
        var topology = initDisplayTopology(displayManager, displayManagerBinderService, callback,
                handler, /*shouldEmitTopologyChangeEvent=*/ true);
        callback.clear();
        callback.expectsEvent(TOPOLOGY_CHANGED_EVENT);
        displayManagerBinderService.setDisplayTopology(topology);
        callback.waitForExpectedEvent();
    }

    @Test
    public void testShouldNotNotifyTopologyChanged_WhenClientIsNotSubscribed() {
        manageDisplaysPermission(/* granted= */ true);
        when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
        when(mMockFlags.isDisplayContentModeManagementEnabled()).thenReturn(true);
        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
        DisplayManagerService.BinderService displayManagerBinderService =
                displayManager.new BinderService();
@@ -3796,9 +3805,13 @@ public class DisplayManagerServiceTest {
                STANDARD_DISPLAY_EVENTS);
        waitForIdleHandler(handler);

        displayManagerBinderService.setDisplayTopology(new DisplayTopology());
        var topology = initDisplayTopology(displayManager, displayManagerBinderService, callback,
                handler, /*shouldEmitTopologyChangeEvent=*/ false);
        callback.clear();
        callback.expectsEvent(TOPOLOGY_CHANGED_EVENT); // should not happen
        displayManagerBinderService.setDisplayTopology(topology);
        callback.waitForNonExpectedEvent(); // checks that event did not happen
        waitForIdleHandler(handler);

        assertThat(callback.receivedEvents()).isEmpty();
    }

@@ -4476,6 +4489,7 @@ public class DisplayManagerServiceTest {
                            new float[0], new int[0]);
        }
        displayDeviceInfo.name = "" + displayType;
        displayDeviceInfo.uniqueId = "uniqueId" + mUniqueIdCount++;
        displayDeviceInfo.modeId = 1;
        displayDeviceInfo.type = displayType;
        displayDeviceInfo.renderFrameRate = displayDeviceInfo.supportedModes[0].getRefreshRate();
@@ -4548,6 +4562,42 @@ public class DisplayManagerServiceTest {
        }
    }

    private DisplayTopology initDisplayTopology(DisplayManagerService displayManager,
            DisplayManagerService.BinderService displayManagerBinderService,
            FakeDisplayManagerCallback callback,
            Handler handler, boolean shouldEmitTopologyChangeEvent) {
        Settings.Global.putInt(mContext.getContentResolver(),
                DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 1);
        callback.expectsEvent(TOPOLOGY_CHANGED_EVENT);
        FakeDisplayDevice displayDevice0 =
                createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
        int displayId0 = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
                displayDevice0);
        if (shouldEmitTopologyChangeEvent) {
            callback.waitForExpectedEvent();
        } else {
            callback.waitForNonExpectedEvent();
        }
        callback.clear();

        callback.expectsEvent(TOPOLOGY_CHANGED_EVENT);
        FakeDisplayDevice displayDevice1 = createFakeDisplayDevice(displayManager,
                new float[]{60f}, Display.TYPE_OVERLAY);
        int displayId1 = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
                displayDevice1);
        waitForIdleHandler(handler);
        if (shouldEmitTopologyChangeEvent) {
            callback.waitForExpectedEvent();
        } else {
            callback.waitForNonExpectedEvent();
        }

        var topology = new DisplayTopology();
        topology.addDisplay(displayId0, 2048, 800);
        topology.addDisplay(displayId1, 1920, 1080);
        return topology;
    }

    private static class FakeDisplayManagerCallback extends IDisplayManagerCallback.Stub
            implements DisplayManagerInternal.DisplayGroupListener {
        int mDisplayId;
+18 −2
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.display

import android.hardware.display.DisplayTopology
import android.hardware.display.DisplayTopology.pxToDp
import android.util.SparseArray
import android.view.Display
import android.view.DisplayInfo
import com.google.common.truth.Truth.assertThat
@@ -37,9 +38,11 @@ class DisplayTopologyCoordinatorTest {
    private val displayInfo = DisplayInfo()
    private val topologyChangeExecutor = Runnable::run

    private val mockTopologyStore = mock<DisplayTopologyStore>()
    private val mockTopology = mock<DisplayTopology>()
    private val mockTopologyCopy = mock<DisplayTopology>()
    private val mockIsExtendedDisplayEnabled = mock<() -> Boolean>()
    private val mockTopologySavedCallback = mock<() -> Unit>()
    private val mockTopologyChangedCallback = mock<(DisplayTopology) -> Unit>()

    @Before
@@ -51,11 +54,17 @@ class DisplayTopologyCoordinatorTest {

        val injector = object : DisplayTopologyCoordinator.Injector() {
            override fun getTopology() = mockTopology
            override fun createTopologyStore(
                displayIdToUniqueId: SparseArray<String>,
                uniqueIdToDisplayId: MutableMap<String, Int>
            ) =
                mockTopologyStore
        }
        whenever(mockIsExtendedDisplayEnabled()).thenReturn(true)
        whenever(mockTopology.copy()).thenReturn(mockTopologyCopy)
        coordinator = DisplayTopologyCoordinator(injector, mockIsExtendedDisplayEnabled,
            mockTopologyChangedCallback, topologyChangeExecutor, DisplayManagerService.SyncRoot())
            mockTopologyChangedCallback, topologyChangeExecutor, DisplayManagerService.SyncRoot(),
            mockTopologySavedCallback)
    }

    @Test
@@ -66,6 +75,7 @@ class DisplayTopologyCoordinatorTest {
        val heightDp = pxToDp(displayInfo.logicalHeight.toFloat(), displayInfo.logicalDensityDpi)
        verify(mockTopology).addDisplay(displayInfo.displayId, widthDp, heightDp)
        verify(mockTopologyChangedCallback).invoke(mockTopologyCopy)
        verify(mockTopologyStore).restoreTopology(mockTopology)
    }

    @Test
@@ -76,6 +86,7 @@ class DisplayTopologyCoordinatorTest {

        verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
        verify(mockTopologyChangedCallback, never()).invoke(any())
        verify(mockTopologyStore, never()).restoreTopology(any())
    }

    @Test
@@ -86,6 +97,7 @@ class DisplayTopologyCoordinatorTest {

        verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
        verify(mockTopologyChangedCallback, never()).invoke(any())
        verify(mockTopologyStore, never()).restoreTopology(any())
    }

    @Test
@@ -115,6 +127,7 @@ class DisplayTopologyCoordinatorTest {
        coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY)

        verify(mockTopologyChangedCallback).invoke(mockTopologyCopy)
        verify(mockTopologyStore).restoreTopology(mockTopology)
    }

    @Test
@@ -124,6 +137,7 @@ class DisplayTopologyCoordinatorTest {
        coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY)

        verify(mockTopologyChangedCallback, never()).invoke(any())
        verify(mockTopologyStore, never()).restoreTopology(any())
    }

    @Test
@@ -136,10 +150,12 @@ class DisplayTopologyCoordinatorTest {
        val topology = mock<DisplayTopology>()
        val topologyCopy = mock<DisplayTopology>()
        whenever(topology.copy()).thenReturn(topologyCopy)

        whenever(mockTopologyStore.saveTopology(topology)).thenReturn(true)
        coordinator.topology = topology

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