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

Commit 11fae1bc authored by Lingyu Feng's avatar Lingyu Feng Committed by Android (Google) Code Review
Browse files

Merge "Add "Include default display in topology" switch for projected mode" into main

parents 7e524320 d42d9769
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -158,6 +158,20 @@ public final class DisplayTopology implements Parcelable {
        return mPrimaryDisplayId;
    }

    /**
     * @hide
     */
    public boolean isEmpty() {
        return mRoot == null;
    }

    /**
     * @hide
     */
    public boolean hasMultipleDisplays() {
        return mRoot != null && mRoot.mChildren != null && !mRoot.mChildren.isEmpty();
    }

    /**
     * Add a display to the topology.
     * If this is the second display in the topology, it will be placed above the first display.
+71 −2
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.ROOT_UID;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
import static android.provider.Settings.Secure.INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY;
import static android.provider.Settings.Secure.MIRROR_BUILT_IN_DISPLAY;
import static android.provider.Settings.Secure.RESOLUTION_MODE_FULL;
import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH;
@@ -195,6 +196,7 @@ import com.android.server.display.utils.DebugUtils;
import com.android.server.display.utils.SensorUtils;
import com.android.server.input.InputManagerInternal;
import com.android.server.utils.FoldSettingProvider;
import com.android.server.wm.DesktopModeHelper;
import com.android.server.wm.SurfaceAnimationThread;
import com.android.server.wm.WindowManagerInternal;

@@ -550,6 +552,10 @@ public final class DisplayManagerService extends SystemService {

    private boolean mMirrorBuiltInDisplay;

    // Whether default display should be included in the display topology. Note that this should
    // only be used for the devices in projected mode.
    private boolean mIncludeDefaultDisplayInTopology;

    private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
@@ -691,8 +697,9 @@ public final class DisplayManagerService extends SystemService {
                        deliverTopologyUpdate(update.first);
                    };
            mDisplayTopologyCoordinator = new DisplayTopologyCoordinator(
                    this::isExtendedDisplayAllowed, topologyChangedCallback,
                    new HandlerExecutor(mHandler), mSyncRoot, backupManager::dataChanged);
                    this::isExtendedDisplayAllowed, this::shouldIncludeDefaultDisplayInTopology,
                    topologyChangedCallback, new HandlerExecutor(mHandler), mSyncRoot,
                    backupManager::dataChanged, mFlags);
        } else {
            mDisplayTopologyCoordinator = null;
        }
@@ -875,6 +882,12 @@ public final class DisplayManagerService extends SystemService {
            if (mFlags.isDisplayContentModeManagementEnabled()) {
                updateMirrorBuiltInDisplaySettingLocked(/*shouldSendDisplayChangeEvent=*/ false);
            }

            if (mFlags.isDefaultDisplayInTopologySwitchEnabled()) {
                mIncludeDefaultDisplayInTopology = mInjector.canInternalDisplayHostDesktops(
                        mContext) || (Settings.Secure.getIntForUser(mContext.getContentResolver(),
                        INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY, 0, UserHandle.USER_CURRENT) != 0);
            }
        }

        mDisplayModeDirector.setDesiredDisplayModeSpecsListener(
@@ -1231,6 +1244,14 @@ public final class DisplayManagerService extends SystemService {
                        Settings.Secure.getUriFor(
                                MIRROR_BUILT_IN_DISPLAY), false, this, UserHandle.USER_ALL);
            }

            if (mFlags.isDefaultDisplayInTopologySwitchEnabled()
                    && !mInjector.canInternalDisplayHostDesktops(mContext)) {
                mContext.getContentResolver().registerContentObserver(
                        Settings.Secure.getUriFor(
                                Settings.Secure.INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY),
                        false, this, UserHandle.USER_ALL);
            }
        }

        @Override
@@ -1253,6 +1274,15 @@ public final class DisplayManagerService extends SystemService {
                }
                return;
            }
            if (Settings.Secure.getUriFor(INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY).equals(uri)) {
                synchronized (mSyncRoot) {
                    if (mFlags.isDefaultDisplayInTopologySwitchEnabled()
                            && !mInjector.canInternalDisplayHostDesktops(mContext)) {
                        handleIncludeDefaultDisplayInTopologySettingChangeLocked();
                    }
                }
                return;
            }
        }
    }

@@ -1288,6 +1318,36 @@ public final class DisplayManagerService extends SystemService {
        return true;
    }

    private void handleIncludeDefaultDisplayInTopologySettingChangeLocked() {
        ContentResolver resolver = mContext.getContentResolver();
        final boolean includeDefaultDisplayInTopology = Settings.Secure.getIntForUser(resolver,
                INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY, 0, UserHandle.USER_CURRENT) != 0;

        if (mIncludeDefaultDisplayInTopology == includeDefaultDisplayInTopology) {
            return;
        }
        mIncludeDefaultDisplayInTopology = includeDefaultDisplayInTopology;

        // This switch is not available when the "Mirror built-in display" switch is enabled.
        if (shouldMirrorBuiltInDisplay()) {
            return;
        }

        if (mDisplayTopologyCoordinator != null) {
            if (mIncludeDefaultDisplayInTopology) {
                final DisplayInfo info = mLogicalDisplayMapper.getDisplayLocked(
                        Display.DEFAULT_DISPLAY).getDisplayInfoLocked();
                mDisplayTopologyCoordinator.onDisplayAdded(info);
            } else {
                // The default display can only be removed when there are multiple displays in the
                // topology to ensure the topology is not empty.
                if (mDisplayTopologyCoordinator.getTopology().hasMultipleDisplays()) {
                    mDisplayTopologyCoordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY);
                }
            }
        }
    }

    private void restoreResolutionFromBackup() {
        int savedMode = Settings.Secure.getIntForUser(mContext.getContentResolver(),
                Settings.Secure.SCREEN_RESOLUTION_MODE,
@@ -2443,6 +2503,11 @@ public final class DisplayManagerService extends SystemService {
        }
    }

    boolean shouldIncludeDefaultDisplayInTopology() {
        return mInjector.canInternalDisplayHostDesktops(mContext)
                || mIncludeDefaultDisplayInTopology;
    }

    @SuppressLint("AndroidFrameworkRequiresPermission")
    private void handleLogicalDisplayAddedLocked(LogicalDisplay display) {
        final int displayId = display.getDisplayIdLocked();
@@ -3994,6 +4059,10 @@ public final class DisplayManagerService extends SystemService {
                    blanker, logicalDisplay, brightnessTracker, brightnessSetting,
                    onBrightnessChangeRunnable, hbmMetadata, bootCompleted, flags);
        }

        boolean canInternalDisplayHostDesktops(Context context) {
            return DesktopModeHelper.canInternalDisplayHostDesktops(context);
        }
    }

    @VisibleForTesting
+56 −4
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.display;

import android.annotation.Nullable;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayTopology;
import android.hardware.display.DisplayTopologyGraph;
import android.util.Pair;
@@ -26,6 +28,8 @@ import android.view.DisplayInfo;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.display.feature.DisplayManagerFlags;

import java.io.PrintWriter;
import java.util.HashMap;
@@ -48,6 +52,8 @@ class DisplayTopologyCoordinator {
        return info.uniqueId;
    }

    private final Injector mInjector;

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

@@ -65,6 +71,13 @@ class DisplayTopologyCoordinator {
     */
    private final BooleanSupplier mIsExtendedDisplayAllowed;

    /**
     * Check if the default display should be included in the topology when there are other displays
     * present. If not, remove the default when another display is added, and add the default
     * display back to the topology when all other displays are removed.
     */
    private final BooleanSupplier mShouldIncludeDefaultDisplayInTopology;

    /**
     * Callback used to send topology updates.
     * Should be invoked from the corresponding executor.
@@ -74,28 +87,35 @@ class DisplayTopologyCoordinator {
    private final Executor mTopologyChangeExecutor;
    private final DisplayManagerService.SyncRoot mSyncRoot;
    private final Runnable mTopologySavedCallback;
    private final DisplayManagerFlags mFlags;

    DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayAllowed,
            BooleanSupplier shouldIncludeDefaultDisplayInTopology,
            Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> onTopologyChangedCallback,
            Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot,
            Runnable topologySavedCallback) {
        this(new Injector(), isExtendedDisplayAllowed, onTopologyChangedCallback,
                topologyChangeExecutor, syncRoot, topologySavedCallback);
            Runnable topologySavedCallback, DisplayManagerFlags flags) {
        this(new Injector(), isExtendedDisplayAllowed, shouldIncludeDefaultDisplayInTopology,
                onTopologyChangedCallback, topologyChangeExecutor, syncRoot, topologySavedCallback,
                flags);
    }

    @VisibleForTesting
    DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayAllowed,
            BooleanSupplier shouldIncludeDefaultDisplayInTopology,
            Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> onTopologyChangedCallback,
            Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot,
            Runnable topologySavedCallback) {
            Runnable topologySavedCallback, DisplayManagerFlags flags) {
        mInjector = injector;
        mTopology = injector.getTopology();
        mIsExtendedDisplayAllowed = isExtendedDisplayAllowed;
        mShouldIncludeDefaultDisplayInTopology = shouldIncludeDefaultDisplayInTopology;
        mOnTopologyChangedCallback = onTopologyChangedCallback;
        mTopologyChangeExecutor = topologyChangeExecutor;
        mSyncRoot = syncRoot;
        mTopologyStore = injector.createTopologyStore(
                mDisplayIdToUniqueIdMapping, mUniqueIdToDisplayIdMapping);
        mTopologySavedCallback = topologySavedCallback;
        mFlags = flags;
    }

    /**
@@ -114,6 +134,16 @@ class DisplayTopologyCoordinator {
            restoreTopologyLocked();
            sendTopologyUpdateLocked();
        }

        if (mFlags.isDefaultDisplayInTopologySwitchEnabled()) {
            // If the default display should not be included in the topology, then when a
            // non-default display is added, remove the default display from the topology.
            if (info.displayId != Display.DEFAULT_DISPLAY
                    && !mShouldIncludeDefaultDisplayInTopology.getAsBoolean()
                    && mTopology.hasMultipleDisplays()) {
                onDisplayRemoved(Display.DEFAULT_DISPLAY);
            }
        }
    }

    /**
@@ -145,6 +175,16 @@ class DisplayTopologyCoordinator {
                sendTopologyUpdateLocked();
            }
        }

        // If the default display should not be included in the topology, then when all non-default
        // displays are removed, add the default display back to the topology.
        if (mFlags.isDefaultDisplayInTopologySwitchEnabled()) {
            if (displayId != Display.DEFAULT_DISPLAY
                    && !mShouldIncludeDefaultDisplayInTopology.getAsBoolean()
                    && mTopology.isEmpty()) {
                onDisplayAdded(mInjector.getDisplayInfo(Display.DEFAULT_DISPLAY));
            }
        }
    }

    /**
@@ -225,6 +265,9 @@ class DisplayTopologyCoordinator {
    }

    private boolean isDisplayAllowedInTopology(DisplayInfo info, boolean shouldLog) {
        if (info == null) {
            return false;
        }
        if (info.type != Display.TYPE_INTERNAL && info.type != Display.TYPE_EXTERNAL
                && info.type != Display.TYPE_OVERLAY) {
            if (shouldLog) {
@@ -275,6 +318,9 @@ class DisplayTopologyCoordinator {

    @VisibleForTesting
    static class Injector {
        private final DisplayManagerInternal mDisplayManagerInternal
                = LocalServices.getService(DisplayManagerInternal.class);

        DisplayTopology getTopology() {
            return new DisplayTopology();
        }
@@ -294,5 +340,11 @@ class DisplayTopologyCoordinator {
                }
            });
        }

        @Nullable
        DisplayInfo getDisplayInfo(int displayId) {
            return mDisplayManagerInternal == null ? null
                    : mDisplayManagerInternal.getDisplayInfo(displayId);
        }
    }
}
+139 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUST
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.INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY;
import static android.provider.Settings.Secure.MIRROR_BUILT_IN_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
@@ -64,6 +65,7 @@ import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
@@ -197,6 +199,7 @@ import org.mockito.MockitoAnnotations;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;

import java.lang.reflect.Field;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
@@ -380,6 +383,11 @@ public class DisplayManagerServiceTest {
        boolean getHdrOutputConversionSupport() {
            return true;
        }

        @Override
        boolean canInternalDisplayHostDesktops(Context context) {
            return false;
        }
    }

    private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
@@ -406,6 +414,7 @@ public class DisplayManagerServiceTest {
    @Mock DisplayManagerInternal mMockDisplayManagerInternal;
    @Mock ActivityManagerInternal mMockActivityManagerInternal;
    @Mock DisplayAdapter mMockDisplayAdapter;
    @Mock DisplayTopologyCoordinator mMockDisplayTopologyCoordinator;

    @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
    @Mock DisplayManagerFlags mMockFlags;
@@ -4343,6 +4352,126 @@ public class DisplayManagerServiceTest {
                .getDefaultDozeBrightness(Display.DEFAULT_DISPLAY));
    }

    @Test
    public void testIncludeDefaultDisplayInTopologySwitch_flagDisabled() {
        when(mMockFlags.isDefaultDisplayInTopologySwitchEnabled()).thenReturn(false);
        Settings.Secure.putInt(mContext.getContentResolver(), INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY,
                0);

        DisplayManagerService displayManager = new DisplayManagerService(mContext,
                mBasicInjector);
        setFieldValue(displayManager, "mDisplayTopologyCoordinator",
                mMockDisplayTopologyCoordinator);
        displayManager.systemReady(/* safeMode= */ false);
        assertThat(displayManager.shouldIncludeDefaultDisplayInTopology()).isFalse();

        Settings.Secure.putInt(mContext.getContentResolver(), INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY,
                1);
        final ContentObserver observer = displayManager.getSettingsObserver();
        observer.onChange(false, Settings.Secure.getUriFor(INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY));
        assertThat(displayManager.shouldIncludeDefaultDisplayInTopology()).isFalse();
        verify(mMockDisplayTopologyCoordinator, never()).onDisplayAdded(any());
    }

    @Test
    public void testIncludeDefaultDisplayInTopologySwitch_internalDisplayCanHostDesktops() {
        when(mMockFlags.isDefaultDisplayInTopologySwitchEnabled()).thenReturn(true);
        Settings.Secure.putInt(mContext.getContentResolver(), INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY,
                0);

        DisplayManagerService displayManager = new DisplayManagerService(
                mContext, new BasicInjector() {
            @Override
            boolean canInternalDisplayHostDesktops(Context context) {
                return true;
            }
        });

        displayManager.systemReady(/* safeMode= */ false);
        assertThat(displayManager.shouldIncludeDefaultDisplayInTopology()).isTrue();

        Settings.Secure.putInt(mContext.getContentResolver(), INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY,
                1);
        final ContentObserver observer = displayManager.getSettingsObserver();
        observer.onChange(false, Settings.Secure.getUriFor(INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY));
        assertThat(displayManager.shouldIncludeDefaultDisplayInTopology()).isTrue();
        verify(mMockDisplayTopologyCoordinator, never()).onDisplayAdded(any());
    }

    @Test
    public void testIncludeDefaultDisplayInTopologySwitch_addDefaultDisplayWhenEnableSwitch() {
        when(mMockFlags.isDefaultDisplayInTopologySwitchEnabled()).thenReturn(true);
        Settings.Secure.putInt(mContext.getContentResolver(), MIRROR_BUILT_IN_DISPLAY, 0);
        Settings.Secure.putInt(mContext.getContentResolver(), INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY,
                0);

        DisplayManagerService displayManager = new DisplayManagerService(mContext,
                mBasicInjector);
        setFieldValue(displayManager, "mDisplayTopologyCoordinator",
                mMockDisplayTopologyCoordinator);
        displayManager.systemReady(/* safeMode= */ false);
        assertThat(displayManager.shouldIncludeDefaultDisplayInTopology()).isFalse();

        createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
        DisplayInfo defaultDisplayInfo = displayManager.getLogicalDisplayMapper().
                getDisplayLocked(Display.DEFAULT_DISPLAY).getDisplayInfoLocked();
        clearInvocations(mMockDisplayTopologyCoordinator);

        Settings.Secure.putInt(mContext.getContentResolver(), INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY,
                1);
        final ContentObserver observer = displayManager.getSettingsObserver();
        observer.onChange(false, Settings.Secure.getUriFor(INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY));
        assertThat(displayManager.shouldIncludeDefaultDisplayInTopology()).isTrue();
        verify(mMockDisplayTopologyCoordinator).onDisplayAdded(defaultDisplayInfo);
    }

    @Test
    public void testIncludeDefaultDisplayInTopologySwitch_removeDefaultDisplayWhenDisableSwitch() {
        when(mMockFlags.isDefaultDisplayInTopologySwitchEnabled()).thenReturn(true);
        Settings.Secure.putInt(mContext.getContentResolver(), MIRROR_BUILT_IN_DISPLAY, 0);
        Settings.Secure.putInt(mContext.getContentResolver(), INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY,
                1);

        DisplayManagerService displayManager = new DisplayManagerService(mContext,
                mBasicInjector);
        setFieldValue(displayManager, "mDisplayTopologyCoordinator",
                mMockDisplayTopologyCoordinator);
        displayManager.systemReady(/* safeMode= */ false);
        assertThat(displayManager.shouldIncludeDefaultDisplayInTopology()).isTrue();

        DisplayTopology mockTopology = mock(DisplayTopology.class);
        doReturn(true).when(mockTopology).hasMultipleDisplays();
        doReturn(mockTopology).when(mMockDisplayTopologyCoordinator).getTopology();

        Settings.Secure.putInt(mContext.getContentResolver(), INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY,
                0);
        final ContentObserver observer = displayManager.getSettingsObserver();
        observer.onChange(false, Settings.Secure.getUriFor(INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY));
        assertThat(displayManager.shouldIncludeDefaultDisplayInTopology()).isFalse();
        verify(mMockDisplayTopologyCoordinator).onDisplayRemoved(Display.DEFAULT_DISPLAY);
    }

    @Test
    public void testIncludeDefaultDisplayInTopologySwitch_mirrorBuiltInDisplay() {
        when(mMockFlags.isDisplayContentModeManagementEnabled()).thenReturn(true);
        when(mMockFlags.isDefaultDisplayInTopologySwitchEnabled()).thenReturn(true);
        Settings.Secure.putInt(mContext.getContentResolver(), MIRROR_BUILT_IN_DISPLAY, 1);
        Settings.Secure.putInt(mContext.getContentResolver(), INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY,
                0);

        DisplayManagerService displayManager = new DisplayManagerService(mContext,
                mBasicInjector);
        setFieldValue(displayManager, "mDisplayTopologyCoordinator",
                mMockDisplayTopologyCoordinator);
        displayManager.systemReady(/* safeMode= */ false);

        Settings.Secure.putInt(mContext.getContentResolver(), INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY,
                1);
        final ContentObserver observer = displayManager.getSettingsObserver();
        observer.onChange(false, Settings.Secure.getUriFor(INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY));
        verify(mMockDisplayTopologyCoordinator, never()).onDisplayAdded(any());
    }

    private void initDisplayPowerController(DisplayManagerInternal localService) {
        localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() {
            @Override
@@ -4748,6 +4877,16 @@ public class DisplayManagerServiceTest {
        return topology;
    }

    private void setFieldValue(Object o, String fieldName, Object value) {
        try {
            final Field field = o.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(o, value);
        } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }

    private static class FakeDisplayManagerCallback extends IDisplayManagerCallback.Stub
            implements DisplayManagerInternal.DisplayGroupListener {
        int mDisplayId;
+81 −2

File changed.

Preview size limit exceeded, changes collapsed.