Loading core/java/android/hardware/display/DisplayManagerInternal.java +6 −0 Original line number Diff line number Diff line Loading @@ -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 Loading services/core/java/com/android/server/display/DisplayManagerService.java +34 −3 Original line number Diff line number Diff line Loading @@ -72,6 +72,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; Loading Loading @@ -678,9 +679,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; } Loading Loading @@ -759,6 +761,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(); Loading Loading @@ -798,6 +805,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); } } } Loading Loading @@ -875,6 +887,8 @@ public final class DisplayManagerService extends SystemService { mSmallAreaDetectionController = (mFlags.isSmallAreaDetectionEnabled()) ? SmallAreaDetectionController.create(mContext) : null; scheduleTopologiesReload(mCurrentUserId, /*isUserSwitching=*/ false); } @VisibleForTesting Loading Loading @@ -927,6 +941,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) { Loading Loading @@ -5976,6 +5999,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 Loading services/core/java/com/android/server/display/DisplayTopologyCoordinator.java +116 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading @@ -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; } /** Loading @@ -80,7 +106,10 @@ class DisplayTopologyCoordinator { return; } synchronized (mSyncRoot) { addDisplayIdMappingLocked(info); mTopology.addDisplay(info.displayId, getWidth(info), getHeight(info)); restoreTopologyLocked(); sendTopologyUpdateLocked(); } } Loading @@ -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. */ Loading @@ -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(); } } Loading @@ -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 Loading @@ -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(); Loading @@ -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; } }); } } } services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +57 −7 Original line number Diff line number Diff line Loading @@ -31,6 +31,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; Loading Loading @@ -413,6 +414,9 @@ public class DisplayManagerServiceTest { .setStrictness(Strictness.LENIENT) .spyStatic(SystemProperties.class) .build(); private int mUniqueIdCount = 0; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); Loading Loading @@ -3864,6 +3868,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 = Loading Loading @@ -3892,6 +3897,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(); Loading @@ -3900,19 +3906,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(); Loading @@ -3925,9 +3934,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(); } Loading Loading @@ -4605,6 +4618,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(); Loading Loading @@ -4677,6 +4691,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; Loading services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt +18 −2 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -76,6 +86,7 @@ class DisplayTopologyCoordinatorTest { verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat()) verify(mockTopologyChangedCallback, never()).invoke(any()) verify(mockTopologyStore, never()).restoreTopology(any()) } @Test Loading @@ -86,6 +97,7 @@ class DisplayTopologyCoordinatorTest { verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat()) verify(mockTopologyChangedCallback, never()).invoke(any()) verify(mockTopologyStore, never()).restoreTopology(any()) } @Test Loading Loading @@ -115,6 +127,7 @@ class DisplayTopologyCoordinatorTest { coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY) verify(mockTopologyChangedCallback).invoke(mockTopologyCopy) verify(mockTopologyStore).restoreTopology(mockTopology) } @Test Loading @@ -124,6 +137,7 @@ class DisplayTopologyCoordinatorTest { coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY) verify(mockTopologyChangedCallback, never()).invoke(any()) verify(mockTopologyStore, never()).restoreTopology(any()) } @Test Loading @@ -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 Loading
core/java/android/hardware/display/DisplayManagerInternal.java +6 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
services/core/java/com/android/server/display/DisplayManagerService.java +34 −3 Original line number Diff line number Diff line Loading @@ -72,6 +72,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; Loading Loading @@ -678,9 +679,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; } Loading Loading @@ -759,6 +761,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(); Loading Loading @@ -798,6 +805,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); } } } Loading Loading @@ -875,6 +887,8 @@ public final class DisplayManagerService extends SystemService { mSmallAreaDetectionController = (mFlags.isSmallAreaDetectionEnabled()) ? SmallAreaDetectionController.create(mContext) : null; scheduleTopologiesReload(mCurrentUserId, /*isUserSwitching=*/ false); } @VisibleForTesting Loading Loading @@ -927,6 +941,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) { Loading Loading @@ -5976,6 +5999,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 Loading
services/core/java/com/android/server/display/DisplayTopologyCoordinator.java +116 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading @@ -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; } /** Loading @@ -80,7 +106,10 @@ class DisplayTopologyCoordinator { return; } synchronized (mSyncRoot) { addDisplayIdMappingLocked(info); mTopology.addDisplay(info.displayId, getWidth(info), getHeight(info)); restoreTopologyLocked(); sendTopologyUpdateLocked(); } } Loading @@ -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. */ Loading @@ -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(); } } Loading @@ -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 Loading @@ -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(); Loading @@ -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; } }); } } }
services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +57 −7 Original line number Diff line number Diff line Loading @@ -31,6 +31,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; Loading Loading @@ -413,6 +414,9 @@ public class DisplayManagerServiceTest { .setStrictness(Strictness.LENIENT) .spyStatic(SystemProperties.class) .build(); private int mUniqueIdCount = 0; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); Loading Loading @@ -3864,6 +3868,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 = Loading Loading @@ -3892,6 +3897,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(); Loading @@ -3900,19 +3906,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(); Loading @@ -3925,9 +3934,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(); } Loading Loading @@ -4605,6 +4618,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(); Loading Loading @@ -4677,6 +4691,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; Loading
services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt +18 −2 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -76,6 +86,7 @@ class DisplayTopologyCoordinatorTest { verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat()) verify(mockTopologyChangedCallback, never()).invoke(any()) verify(mockTopologyStore, never()).restoreTopology(any()) } @Test Loading @@ -86,6 +97,7 @@ class DisplayTopologyCoordinatorTest { verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat()) verify(mockTopologyChangedCallback, never()).invoke(any()) verify(mockTopologyStore, never()).restoreTopology(any()) } @Test Loading Loading @@ -115,6 +127,7 @@ class DisplayTopologyCoordinatorTest { coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY) verify(mockTopologyChangedCallback).invoke(mockTopologyCopy) verify(mockTopologyStore).restoreTopology(mockTopology) } @Test Loading @@ -124,6 +137,7 @@ class DisplayTopologyCoordinatorTest { coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY) verify(mockTopologyChangedCallback, never()).invoke(any()) verify(mockTopologyStore, never()).restoreTopology(any()) } @Test Loading @@ -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