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

Commit b2bbb9f5 authored by Fiona Campbell's avatar Fiona Campbell
Browse files

Add connected displays to appropriate power group

- Sort displays into appropriate power (display) groups
- Determine this based on desktop content mode.
- Ensure that, if a group with this type of display already exists, the
  display is added to that group.

Bug: 402406603
Flag: com.android.server.display.feature.flags.separate_timeouts
Test: adb shell dumpsys power | grep group.*user -A15 -i
Test: adb shell dumpsys display | grep "Display Groups" -A15
Test: confirming that I ran CD smoke tests

Change-Id: I8663659de156f9ab2b8e9049fd212aae6e278195
parent 0b4cbfba
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -32,6 +32,8 @@ public class DisplayGroup {
    private final List<LogicalDisplay> mDisplays = new ArrayList<>();
    private final int mGroupId;

    private String mGroupName;

    private int mChangeCount;

    DisplayGroup(int groupId) {
@@ -43,6 +45,14 @@ public class DisplayGroup {
        return mGroupId;
    }

    public String getGroupName() {
        return mGroupName;
    }

    public void setGroupName(String groupName) {
        mGroupName = groupName;
    }

    /**
     * Adds the provided {@code display} to the Group
     *
@@ -107,6 +117,7 @@ public class DisplayGroup {
            LogicalDisplay logicalDisplay = mDisplays.get(i);
            ipw.println("Display " + logicalDisplay.getDisplayIdLocked() + " "
                    + logicalDisplay.getPrimaryDisplayDeviceLocked());
            ipw.println("Group name: " + getGroupName());
        }
    }
}
+165 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.display;

import android.content.Context;
import android.util.SparseArray;
import android.view.Display;

import com.android.server.wm.DesktopModeHelper;

/**
 * DisplayGroupAllocator helps select which display groups new displays should be added to,
 * when they are added to the system. It takes into account attributes of the display, as well
 * as which display groups already exist.
 */
class DisplayGroupAllocator {
    private static final String TAG = "DisplayGroupAllocator";

    private boolean mCanDeviceEnterDesktopMode;
    private boolean mCanDefaultDisplayEnterDesktopMode;
    private Context mContext;
    private final Injector mInjector;
    private int mReason;

    public static final String GROUP_TYPE_PRIMARY = "";
    public static final String GROUP_TYPE_SECONDARY = "secondary_mode";
    public static final int REASON_NON_DESKTOP = 0;
    public static final int REASON_PROJECTED = 1;
    public static final int REASON_EXTENDED = 2;
    public static final int REASON_FALLBACK = 3;


    DisplayGroupAllocator(Context context) {
        this(context, new Injector());
    }

    DisplayGroupAllocator(Context context, Injector injector) {
        mInjector = injector;
        mContext = context;
    }

    public void initLater(Context context) {
        mContext = context;
        mCanDeviceEnterDesktopMode = mInjector.canEnterDesktopMode(mContext);
        mCanDefaultDisplayEnterDesktopMode = mInjector.canInternalDisplayHostDesktops(mContext);
    }

    /**
     * Decides which group type is needed for a given display content mode.
     * Locked on DisplayManager mSyncRoot.
     * Sets mReason, for logging purposes.
     */
    public String decideRequiredGroupTypeLocked(LogicalDisplay display, int type) {
        mReason = getContentModeForDisplayLocked(display, type);
        return switch (mReason) {
            case REASON_NON_DESKTOP, REASON_EXTENDED, REASON_FALLBACK -> GROUP_TYPE_PRIMARY;
            case REASON_PROJECTED -> GROUP_TYPE_SECONDARY;
            default -> GROUP_TYPE_PRIMARY;
        };
    }


    /**
     * Returns the first suitable group for a display to be sorted into, given the required group
     * type.
     * @param requiredGroupType which content mode group this display should be used in
     * @param displayGroups array of existing display groups.
     * @return Id of group that display should be sorted into
     */
    public static int calculateGroupId(String requiredGroupType,
            SparseArray<DisplayGroup> displayGroups) {
        // go through display groups, find one group with the correct tag.
        final int size = displayGroups.size();
        int calculatedGroup = Display.INVALID_DISPLAY_GROUP;
        for (int i = 0; i < size; i++) {
            final DisplayGroup displayGroup = displayGroups.valueAt(i);
            if (displayGroup.getGroupName().equals(requiredGroupType)) {
                calculatedGroup = displayGroups.keyAt(i);
                break;
            }
        }
        return calculatedGroup;
    }

    /**
     * Calculates whether this display supports desktop mode
     */
    public boolean isDesktopModeSupportedOnDisplayLocked(LogicalDisplay display, int type) {
        if (!mCanDeviceEnterDesktopMode) {
            return false;
        }

        if (type == Display.TYPE_INTERNAL) {
            return mCanDefaultDisplayEnterDesktopMode;
        }

        if (type == Display.TYPE_EXTERNAL || type == Display.TYPE_OVERLAY) {
            return mInjector.canDisplayHostTasksLocked(display);
        }
        return false;
    }

    /**
     * Decides on the content mode that the display should be using.
     */
    public int getContentModeForDisplayLocked(LogicalDisplay display, int type) {
        // conditions for non desktop mode;
        // desktop mode not supported on device or this display
        // or display is internal, and should be in a different group to the
        // projected and extended mode displays.
        if (!mCanDeviceEnterDesktopMode || type == Display.TYPE_INTERNAL) {
            return REASON_NON_DESKTOP;
        }

        // conditions for projected mode;
        // desktop mode supported on device, external display, but not internal
        if (mCanDeviceEnterDesktopMode
                && isDesktopModeSupportedOnDisplayLocked(display, type)
                && !mCanDefaultDisplayEnterDesktopMode) {
            return REASON_PROJECTED;
        }

        // conditions for extended mode;
        // desktop mode supported on both displays
        if (mCanDeviceEnterDesktopMode
                && isDesktopModeSupportedOnDisplayLocked(display, type)
                && mCanDefaultDisplayEnterDesktopMode) {
            return REASON_EXTENDED;
        }
        return REASON_FALLBACK;
    }

    public int getReason() {
        return mReason;
    }

    static class Injector {

        private boolean canEnterDesktopMode(Context context) {
            return DesktopModeHelper.canEnterDesktopMode(context);
        }

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

        boolean canDisplayHostTasksLocked(LogicalDisplay display) {
            return display.canHostTasksLocked();
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -1129,7 +1129,7 @@ final class LogicalDisplay {
    /**
     * Gets the name of display group to which the display is assigned.
     */
    public String getDisplayGroupNameLocked() {
    public String getLayoutGroupNameLocked() {
        return mDisplayGroupName;
    }

+38 −10
Original line number Diff line number Diff line
@@ -22,6 +22,9 @@ import static android.hardware.devicestate.DeviceState.PROPERTY_POWER_CONFIGURAT
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static android.view.Display.DEFAULT_DISPLAY;

import static com.android.server.display.DisplayGroupAllocator.GROUP_TYPE_PRIMARY;
import static com.android.server.display.DisplayGroupAllocator.calculateGroupId;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -216,6 +219,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
    private final DisplayManagerFlags mFlags;
    private final SyntheticModeManager mSyntheticModeManager;
    private final FeatureFlags mDeviceStateManagerFlags;
    private final Context mContext;
    private final DisplayGroupAllocator mDisplayGroupAllocator;

    LogicalDisplayMapper(@NonNull Context context, FoldSettingProvider foldSettingProvider,
            FoldGracePeriodProvider foldGracePeriodProvider,
@@ -226,7 +231,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
                handler,
                new DeviceStateToLayoutMap((isDefault) -> isDefault ? DEFAULT_DISPLAY
                        : sNextNonDefaultDisplayId++, flags), flags,
                new SyntheticModeManager(flags));
                new SyntheticModeManager(flags), new DisplayGroupAllocator(context));
    }

    LogicalDisplayMapper(@NonNull Context context, FoldSettingProvider foldSettingProvider,
@@ -234,8 +239,10 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
            @NonNull DisplayDeviceRepository repo,
            @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
            @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap,
            DisplayManagerFlags flags, SyntheticModeManager syntheticModeManager) {
            DisplayManagerFlags flags, SyntheticModeManager syntheticModeManager,
            DisplayGroupAllocator displayGroupAllocator) {
        mSyncRoot = syncRoot;
        mContext = context;
        mPowerManager = context.getSystemService(PowerManager.class);
        mInteractive = mPowerManager.isInteractive();
        mHandler = new LogicalDisplayMapperHandler(handler.getLooper());
@@ -256,6 +263,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
        mFlags = flags;
        mSyntheticModeManager = syntheticModeManager;
        mDeviceStateManagerFlags = new FeatureFlagsImpl();
        mDisplayGroupAllocator = displayGroupAllocator;
    }

    @Override
@@ -586,6 +594,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
            if (!mDeviceStateToBeAppliedAfterBoot.equals(INVALID_DEVICE_STATE)) {
                setDeviceStateLocked(mDeviceStateToBeAppliedAfterBoot);
            }
            mDisplayGroupAllocator.initLater(mContext);
        }
    }

@@ -1071,12 +1080,25 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
        final DisplayGroup oldGroup = getDisplayGroupLocked(groupId);

        // groupName directly from LogicalDisplay (not from DisplayInfo)
        final String groupName = display.getDisplayGroupNameLocked();
        String groupName = display.getLayoutGroupNameLocked();
        // DisplayDeviceInfo is safe to use, it is updated earlier
        final DisplayDeviceInfo displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();

        int decidedGroupId = Display.INVALID_DISPLAY_GROUP;

        // Choose a display group based on the content mode type of the display.
        String requiredGroupType = GROUP_TYPE_PRIMARY;

        if (mFlags.isSeparateTimeoutsEnabled() && TextUtils.isEmpty(groupName)) {
            requiredGroupType = mDisplayGroupAllocator.decideRequiredGroupTypeLocked(
                    display, displayDeviceInfo.type);
            decidedGroupId = calculateGroupId(requiredGroupType, mDisplayGroups);
            groupName = requiredGroupType;
        }

        // Get the new display group if a change is needed, if display group name is empty and
        // {@code DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP} is not set, the display is assigned
        // to the default display group.
        // {@code DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP} is not set, and required group type
        // has not been decided, the display is assigned to the default display group.
        final boolean needsOwnDisplayGroup =
                (displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0
                        || !TextUtils.isEmpty(groupName);
@@ -1088,17 +1110,19 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
                deviceDisplayGroupId != null && groupId == deviceDisplayGroupId;
        if (groupId == Display.INVALID_DISPLAY_GROUP
                || hasOwnDisplayGroup != needsOwnDisplayGroup
                || hasDeviceDisplayGroup != needsDeviceDisplayGroup) {
                || hasDeviceDisplayGroup != needsDeviceDisplayGroup
                || decidedGroupId != Display.INVALID_DISPLAY_GROUP) {
            groupId =
                    assignDisplayGroupIdLocked(needsOwnDisplayGroup,
                            display.getDisplayGroupNameLocked(), needsDeviceDisplayGroup,
                            linkedDeviceUniqueId);
                            display.getLayoutGroupNameLocked(), needsDeviceDisplayGroup,
                            linkedDeviceUniqueId, decidedGroupId);
        }

        // Create a new group if needed
        DisplayGroup newGroup = getDisplayGroupLocked(groupId);
        if (newGroup == null) {
            newGroup = new DisplayGroup(groupId);
            newGroup.setGroupName(groupName);
            mDisplayGroups.append(groupId, newGroup);
        }
        if (oldGroup != newGroup) {
@@ -1109,7 +1133,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
            display.updateDisplayGroupIdLocked(groupId);
            Slog.i(TAG, "Setting new display group " + groupId + " for display "
                    + displayId + ", from previous group: "
                    + (oldGroup != null ? oldGroup.getGroupId() : "null"));
                    + (oldGroup != null ? oldGroup.getGroupId() : "null")
                    + ", for reason: " + mDisplayGroupAllocator.getReason());
        }
    }

@@ -1308,7 +1333,10 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
    }

    private int assignDisplayGroupIdLocked(boolean isOwnDisplayGroup, String displayGroupName,
            boolean isDeviceDisplayGroup, Integer linkedDeviceUniqueId) {
            boolean isDeviceDisplayGroup, Integer linkedDeviceUniqueId, int decidedGroupId) {
        if (decidedGroupId != Display.INVALID_DISPLAY_GROUP) {
            return decidedGroupId;
        }
        if (isDeviceDisplayGroup && linkedDeviceUniqueId != null) {
            int deviceDisplayGroupId = mDeviceDisplayGroupIds.get(linkedDeviceUniqueId);
            // A value of 0 indicates that no device display group was found.
+3 −3
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import com.android.window.flags.Flags;
 * Constants for desktop mode feature
 */
public final class DesktopModeHelper {

    /**
     * Flag to indicate whether to restrict desktop mode to supported devices.
     */
@@ -66,8 +67,7 @@ public final class DesktopModeHelper {
    /**
     * Return {@code true} if the current device can hosts desktop sessions on its internal display.
     */
    @VisibleForTesting
    private static boolean canInternalDisplayHostDesktops(@NonNull Context context) {
    public static boolean canInternalDisplayHostDesktops(@NonNull Context context) {
        return context.getResources().getBoolean(R.bool.config_canInternalDisplayHostDesktops);
    }

@@ -98,7 +98,7 @@ public final class DesktopModeHelper {
    /**
     * Return {@code true} if desktop mode can be entered on the current device.
     */
    static boolean canEnterDesktopMode(@NonNull Context context) {
    public static boolean canEnterDesktopMode(@NonNull Context context) {
        return (isDeviceEligibleForDesktopMode(context)
                && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue())
                || isDesktopModeEnabledByDevOption(context);
Loading