Loading core/java/android/view/Display.java +70 −25 Original line number Diff line number Diff line Loading @@ -2429,6 +2429,18 @@ public final class Display { */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public static final class Mode implements Parcelable { /** * @hide */ public static final int FLAG_ARR_RENDER_RATE = 1 << 0; /** @hide */ @IntDef(flag = true, prefix = {"FLAG_"}, value = { FLAG_ARR_RENDER_RATE, }) @Retention(RetentionPolicy.SOURCE) public @interface ModeFlags {} /** * @hide */ Loading @@ -2440,6 +2452,9 @@ public final class Display { public static final int INVALID_MODE_ID = -1; private final int mModeId; private final int mParentModeId; @ModeFlags private final int mFlags; private final int mWidth; private final int mHeight; private final float mPeakRefreshRate; Loading @@ -2449,7 +2464,6 @@ public final class Display { @NonNull @HdrCapabilities.HdrType private final int[] mSupportedHdrTypes; private final boolean mIsSynthetic; /** * @hide Loading Loading @@ -2483,22 +2497,23 @@ public final class Display { */ public Mode(int modeId, int width, int height, float refreshRate, float vsyncRate, float[] alternativeRefreshRates, @HdrCapabilities.HdrType int[] supportedHdrTypes) { this(modeId, width, height, refreshRate, vsyncRate, false, alternativeRefreshRates, supportedHdrTypes); this(modeId, INVALID_MODE_ID, 0, width, height, refreshRate, vsyncRate, alternativeRefreshRates, supportedHdrTypes); } /** * @hide */ public Mode(int modeId, int width, int height, float refreshRate, float vsyncRate, boolean isSynthetic, float[] alternativeRefreshRates, @HdrCapabilities.HdrType int[] supportedHdrTypes) { public Mode(int modeId, int parentModeId, @ModeFlags int flags, int width, int height, float refreshRate, float vsyncRate, float[] alternativeRefreshRates, @HdrCapabilities.HdrType int[] supportedHdrTypes) { mModeId = modeId; mParentModeId = parentModeId; mFlags = flags; mWidth = width; mHeight = height; mPeakRefreshRate = refreshRate; mVsyncRate = vsyncRate; mIsSynthetic = isSynthetic; mAlternativeRefreshRates = Arrays.copyOf(alternativeRefreshRates, alternativeRefreshRates.length); Arrays.sort(mAlternativeRefreshRates); Loading @@ -2513,6 +2528,24 @@ public final class Display { return mModeId; } /** * Returns parent mode's id if the mode is derived from other Display.Mode. * Returns INVALID_MODE_ID in other cases. * @hide */ public int getParentModeId() { return mParentModeId; } /** * Returns flags associated with this mode. * @hide */ @ModeFlags public int getFlags() { return mFlags; } /** * Returns the physical width of the display in pixels when configured in this mode's * resolution. Loading Loading @@ -2565,14 +2598,14 @@ public final class Display { } /** * Returns true if mode is synthetic and does not have corresponding * SurfaceControl.DisplayMode * Returns true if switching to this mode does not actually switch display mode on * SurfaceFlinger level * @hide */ @SuppressWarnings("UnflaggedApi") // For testing only @TestApi public boolean isSynthetic() { return mIsSynthetic; return mParentModeId != INVALID_MODE_ID || (mFlags & FLAG_ARR_RENDER_RATE) != 0; } /** Loading Loading @@ -2694,6 +2727,8 @@ public final class Display { public int hashCode() { int hash = 1; hash = hash * 17 + mModeId; hash = hash * 17 + mParentModeId; hash = hash * 17 + mFlags; hash = hash * 17 + mWidth; hash = hash * 17 + mHeight; hash = hash * 17 + Float.floatToIntBits(mPeakRefreshRate); Loading @@ -2707,11 +2742,12 @@ public final class Display { public String toString() { return new StringBuilder("{") .append("id=").append(mModeId) .append(", parentModeId=").append(mParentModeId) .append(", flags=").append(flagsToString(mFlags)) .append(", width=").append(mWidth) .append(", height=").append(mHeight) .append(", fps=").append(mPeakRefreshRate) .append(", vsync=").append(mVsyncRate) .append(", synthetic=").append(mIsSynthetic) .append(", alternativeRefreshRates=") .append(Arrays.toString(mAlternativeRefreshRates)) .append(", supportedHdrTypes=") Loading @@ -2720,31 +2756,40 @@ public final class Display { .toString(); } private static String flagsToString(@ModeFlags int flags) { StringBuilder msg = new StringBuilder(); if ((flags & FLAG_ARR_RENDER_RATE) != 0) { msg.append(", FLAG_ARR_RENDER_RATE"); } return msg.toString(); } @Override public int describeContents() { return 0; } private Mode(Parcel in) { this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.readFloat(), in.readBoolean(), in.createFloatArray(), in.createIntArray()); this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.readFloat(), in.createFloatArray(), in.createIntArray()); } @Override public void writeToParcel(Parcel out, int parcelableFlags) { out.writeInt(mModeId); out.writeInt(mParentModeId); out.writeInt(mFlags); out.writeInt(mWidth); out.writeInt(mHeight); out.writeFloat(mPeakRefreshRate); out.writeFloat(mVsyncRate); out.writeBoolean(mIsSynthetic); out.writeFloatArray(mAlternativeRefreshRates); out.writeIntArray(mSupportedHdrTypes); } @SuppressWarnings("hiding") public static final @android.annotation.NonNull Parcelable.Creator<Mode> CREATOR = new Parcelable.Creator<Mode>() { public static final @android.annotation.NonNull Parcelable.Creator<Mode> CREATOR = new Parcelable.Creator<>() { @Override public Mode createFromParcel(Parcel in) { return new Mode(in); Loading services/core/java/com/android/server/display/DisplayAdapter.java +0 −17 Original line number Diff line number Diff line Loading @@ -24,7 +24,6 @@ import android.view.SurfaceControl; import com.android.server.display.feature.DisplayManagerFlags; import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicInteger; /** * A display adapter makes zero or more display devices available to the system Loading @@ -48,11 +47,6 @@ abstract class DisplayAdapter { public static final int DISPLAY_DEVICE_EVENT_CHANGED = 2; public static final int DISPLAY_DEVICE_EVENT_REMOVED = 3; /** * Used to generate globally unique display mode ids. */ private static final AtomicInteger NEXT_DISPLAY_MODE_ID = new AtomicInteger(1); // 0 = no mode. // Called with SyncRoot lock held. DisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, Listener listener, String name, DisplayManagerFlags featureFlags) { Loading Loading @@ -128,17 +122,6 @@ abstract class DisplayAdapter { mHandler.post(() -> mListener.onTraversalRequested()); } public static Display.Mode createMode(int width, int height, float refreshRate) { return createMode(width, height, refreshRate, refreshRate, new float[0], new int[0]); } public static Display.Mode createMode(int width, int height, float refreshRate, float vsyncRate, float[] alternativeRefreshRates, @Display.HdrCapabilities.HdrType int[] supportedHdrTypes) { return new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate, vsyncRate, /* isSynthetic= */ false, alternativeRefreshRates, supportedHdrTypes); } static int getPowerModeForState(int state) { switch (state) { case Display.STATE_OFF: Loading services/core/java/com/android/server/display/DisplayModeFactory.java 0 → 100644 +141 −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 static com.android.server.display.DisplayDeviceConfig.DEFAULT_LOW_REFRESH_RATE; import android.annotation.SuppressLint; import android.view.Display; import android.view.SurfaceControl; import com.android.server.display.LocalDisplayAdapter.DisplayModeRecord; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class DisplayModeFactory { /** * Used to generate globally unique display mode ids. */ private static final AtomicInteger NEXT_DISPLAY_MODE_ID = new AtomicInteger(1); // 0 = no mode. private static final float FLOAT_TOLERANCE = 0.01f; private static final float SYNTHETIC_MODE_REFRESH_RATE = DEFAULT_LOW_REFRESH_RATE; private static final float SYNTHETIC_MODE_HIGH_BOUNDARY = SYNTHETIC_MODE_REFRESH_RATE + FLOAT_TOLERANCE; static Display.Mode createMode(int width, int height, float refreshRate) { return new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), Display.Mode.INVALID_MODE_ID, 0, width, height, refreshRate, refreshRate, new float[0], new int[0] ); } @SuppressLint("WrongConstant") static Display.Mode createMode(SurfaceControl.DisplayMode mode, float[] alternativeRefreshRates, boolean hasArrSupport, boolean syntheticModesV2Enabled) { int flags = 0; if (syntheticModesV2Enabled && hasArrSupport && mode.peakRefreshRate <= SYNTHETIC_MODE_HIGH_BOUNDARY) { flags |= Display.Mode.FLAG_ARR_RENDER_RATE; } return new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), Display.Mode.INVALID_MODE_ID, flags, mode.width, mode.height, mode.peakRefreshRate, mode.vsyncRate, alternativeRefreshRates, mode.supportedHdrTypes ); } @SuppressWarnings("MixedMutabilityReturnType") static List<DisplayModeRecord> createArrSyntheticModes(List<DisplayModeRecord> records, boolean hasArrSupport, boolean syntheticModesV2Enabled) { if (!syntheticModesV2Enabled) { return Collections.emptyList(); } if (!hasArrSupport) { return Collections.emptyList(); } List<Display.Mode> modesToSkipForArrSyntheticMode = new ArrayList<>(); for (DisplayModeRecord record: records) { // already have < 60Hz mode, don't need to add synthetic if ((record.mMode.getFlags() & Display.Mode.FLAG_ARR_RENDER_RATE) != 0) { modesToSkipForArrSyntheticMode.add(record.mMode); } } List<Display.Mode> modesForArrSyntheticMode = new ArrayList<>(); for (DisplayModeRecord record: records) { if (!is60HzAchievable(record.mMode)) { continue; } // already have < 60Hz mode, don't need to add synthetic if ((record.mMode.getFlags() & Display.Mode.FLAG_ARR_RENDER_RATE) != 0) { continue; } // already added OR should be skipped boolean skipAdding = hasMatichingForArr(modesForArrSyntheticMode, record.mMode) || hasMatichingForArr(modesToSkipForArrSyntheticMode, record.mMode); if (!skipAdding) { modesForArrSyntheticMode.add(record.mMode); } } List<DisplayModeRecord> syntheticModes = new ArrayList<>(); for (Display.Mode mode : modesForArrSyntheticMode) { syntheticModes.add(new DisplayModeRecord( new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), mode.getModeId(), Display.Mode.FLAG_ARR_RENDER_RATE, mode.getPhysicalWidth(), mode.getPhysicalHeight(), SYNTHETIC_MODE_REFRESH_RATE, SYNTHETIC_MODE_REFRESH_RATE, new float[0], mode.getSupportedHdrTypes() ))); } return syntheticModes; } private static boolean hasMatichingForArr(List<Display.Mode> modes, Display.Mode modeToMatch) { for (Display.Mode mode : modes) { if (matchingForSyntheticArr(modeToMatch, mode)) { return true; } } return false; } private static boolean is60HzAchievable(Display.Mode mode) { float divisor = mode.getVsyncRate() / SYNTHETIC_MODE_REFRESH_RATE; return Math.abs(divisor - Math.round(divisor)) < FLOAT_TOLERANCE; } private static boolean matchingForSyntheticArr(Display.Mode mode1, Display.Mode mode2) { return mode1.getPhysicalWidth() == mode2.getPhysicalWidth() && mode1.getPhysicalHeight() == mode2.getPhysicalHeight() && Arrays.equals(mode1.getSupportedHdrTypes(), mode2.getSupportedHdrTypes()); } } services/core/java/com/android/server/display/LocalDisplayAdapter.java +32 −15 Original line number Diff line number Diff line Loading @@ -293,9 +293,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { public boolean updateDisplayPropertiesLocked(SurfaceControl.StaticDisplayInfo staticInfo, SurfaceControl.DynamicDisplayInfo dynamicInfo, SurfaceControl.DesiredDisplayModeSpecs modeSpecs) { boolean changed = updateDisplayModesLocked( dynamicInfo.supportedDisplayModes, dynamicInfo.preferredBootDisplayMode, dynamicInfo.activeDisplayModeId, dynamicInfo.renderFrameRate, modeSpecs); boolean changed = updateDisplayModesLocked(dynamicInfo, modeSpecs); changed |= updateStaticInfo(staticInfo); changed |= updateColorModesLocked(dynamicInfo.supportedColorModes, dynamicInfo.activeColorMode); Loading @@ -312,10 +311,15 @@ final class LocalDisplayAdapter extends DisplayAdapter { return changed; } public boolean updateDisplayModesLocked( SurfaceControl.DisplayMode[] displayModes, int preferredSfDisplayModeId, int activeSfDisplayModeId, float renderFrameRate, private boolean updateDisplayModesLocked(SurfaceControl.DynamicDisplayInfo dynamicInfo, SurfaceControl.DesiredDisplayModeSpecs modeSpecs) { SurfaceControl.DisplayMode[] displayModes = dynamicInfo.supportedDisplayModes; int preferredSfDisplayModeId = dynamicInfo.preferredBootDisplayMode; int activeSfDisplayModeId = dynamicInfo.activeDisplayModeId; float renderFrameRate = dynamicInfo.renderFrameRate; boolean hasArrSupport = dynamicInfo.hasArrSupport; boolean syntheticModesV2Enabled = getFeatureFlags().isSyntheticModesV2Enabled(); mSfDisplayModes = Arrays.copyOf(displayModes, displayModes.length); mActiveSfDisplayMode = getModeById(displayModes, activeSfDisplayModeId); mAppVsyncOffsetNanos = mActiveSfDisplayMode.appVsyncOffsetNanos; Loading Loading @@ -365,7 +369,9 @@ final class LocalDisplayAdapter extends DisplayAdapter { for (int j = 0; j < alternativeRates.length; j++) { alternativeRates[j] = alternativeRefreshRates.get(j); } record = new DisplayModeRecord(mode, alternativeRates); Display.Mode displayMode = DisplayModeFactory.createMode( mode, alternativeRates, hasArrSupport, syntheticModesV2Enabled); record = new DisplayModeRecord(displayMode); modesAdded = true; } records.add(record); Loading Loading @@ -438,13 +444,18 @@ final class LocalDisplayAdapter extends DisplayAdapter { sendTraversalRequestLocked(); } } List<DisplayModeRecord> syntheticModes = DisplayModeFactory .createArrSyntheticModes(records, hasArrSupport, syntheticModesV2Enabled); boolean recordsChanged = records.size() != mSupportedModes.size() || modesAdded; records.addAll(syntheticModes); boolean recordsChanged = records.size() != mSupportedModes.size() || modesAdded || !syntheticModes.isEmpty(); // If the records haven't changed then we're done here. if (!recordsChanged) { return activeModeChanged || preferredModeChanged || renderFrameRateChanged; } // Otherwise IDs changed and we need to update default/userPreferred/active mode IDs mSupportedModes.clear(); for (DisplayModeRecord record : records) { mSupportedModes.put(record.mMode.getModeId(), record); Loading Loading @@ -1151,7 +1162,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { mSurfaceControlProxy.clearBootDisplayMode(getDisplayTokenLocked()); } else { int preferredSfDisplayModeId = findSfDisplayModeIdLocked( mUserPreferredMode.getModeId(), mDefaultModeGroup); mUserPreferredMode, mDefaultModeGroup); mSurfaceControlProxy.setBootDisplayMode(getDisplayTokenLocked(), preferredSfDisplayModeId); } Loading Loading @@ -1405,6 +1416,14 @@ final class LocalDisplayAdapter extends DisplayAdapter { pw.println("mNitsToEvenDimmerStrength=" + mNitsToEvenDimmerStrength); } private int findSfDisplayModeIdLocked(Display.Mode mode, int modeGroup) { int modeId = mode.getModeId(); if (mode.getParentModeId() != INVALID_MODE_ID) { modeId = mode.getParentModeId(); } return findSfDisplayModeIdLocked(modeId, modeGroup); } private int findSfDisplayModeIdLocked(int displayModeId, int modeGroup) { int matchingSfDisplayModeId = INVALID_MODE_ID; DisplayModeRecord record = mSupportedModes.get(displayModeId); Loading Loading @@ -1550,13 +1569,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { /** * Keeps track of a display mode. */ private static final class DisplayModeRecord { static final class DisplayModeRecord { public final Display.Mode mMode; DisplayModeRecord(SurfaceControl.DisplayMode mode, float[] alternativeRefreshRates) { mMode = createMode(mode.width, mode.height, mode.peakRefreshRate, mode.vsyncRate, alternativeRefreshRates, mode.supportedHdrTypes); DisplayModeRecord(Display.Mode mode) { mMode = mode; } /** Loading services/core/java/com/android/server/display/LogicalDisplay.java +9 −5 Original line number Diff line number Diff line Loading @@ -231,14 +231,16 @@ final class LogicalDisplay { private final boolean mSyncedResolutionSwitchEnabled; private final boolean mSyntheticModesV2Enabled; private boolean mCanHostTasks; LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) { this(displayId, layerStack, primaryDisplayDevice, false); this(displayId, layerStack, primaryDisplayDevice, false, true); } LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice, boolean isSyncedResolutionSwitchEnabled) { boolean isSyncedResolutionSwitchEnabled, boolean syntheticModesV2Enabled) { mDisplayId = displayId; mLayerStack = layerStack; mPrimaryDisplayDevice = primaryDisplayDevice; Loading @@ -250,6 +252,7 @@ final class LogicalDisplay { mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID; mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId; mSyncedResolutionSwitchEnabled = isSyncedResolutionSwitchEnabled; mSyntheticModesV2Enabled = syntheticModesV2Enabled; // No need to initialize mCanHostTasks here; it's handled in // DisplayManagerService#setupLogicalDisplay(). Loading Loading @@ -535,9 +538,10 @@ final class LogicalDisplay { mBaseDisplayInfo.userPreferredModeId = deviceInfo.userPreferredModeId; mBaseDisplayInfo.supportedModes = Arrays.copyOf( deviceInfo.supportedModes, deviceInfo.supportedModes.length); mBaseDisplayInfo.appsSupportedModes = syntheticModeManager.createAppSupportedModes( config, mBaseDisplayInfo.supportedModes, mBaseDisplayInfo.hasArrSupport ); mBaseDisplayInfo.appsSupportedModes = mSyntheticModesV2Enabled ? Arrays.copyOf(deviceInfo.supportedModes, deviceInfo.supportedModes.length) : syntheticModeManager.createAppSupportedModes(config, mBaseDisplayInfo.supportedModes, mBaseDisplayInfo.hasArrSupport); mBaseDisplayInfo.colorMode = deviceInfo.colorMode; mBaseDisplayInfo.supportedColorModes = Arrays.copyOf( deviceInfo.supportedColorModes, Loading Loading
core/java/android/view/Display.java +70 −25 Original line number Diff line number Diff line Loading @@ -2429,6 +2429,18 @@ public final class Display { */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public static final class Mode implements Parcelable { /** * @hide */ public static final int FLAG_ARR_RENDER_RATE = 1 << 0; /** @hide */ @IntDef(flag = true, prefix = {"FLAG_"}, value = { FLAG_ARR_RENDER_RATE, }) @Retention(RetentionPolicy.SOURCE) public @interface ModeFlags {} /** * @hide */ Loading @@ -2440,6 +2452,9 @@ public final class Display { public static final int INVALID_MODE_ID = -1; private final int mModeId; private final int mParentModeId; @ModeFlags private final int mFlags; private final int mWidth; private final int mHeight; private final float mPeakRefreshRate; Loading @@ -2449,7 +2464,6 @@ public final class Display { @NonNull @HdrCapabilities.HdrType private final int[] mSupportedHdrTypes; private final boolean mIsSynthetic; /** * @hide Loading Loading @@ -2483,22 +2497,23 @@ public final class Display { */ public Mode(int modeId, int width, int height, float refreshRate, float vsyncRate, float[] alternativeRefreshRates, @HdrCapabilities.HdrType int[] supportedHdrTypes) { this(modeId, width, height, refreshRate, vsyncRate, false, alternativeRefreshRates, supportedHdrTypes); this(modeId, INVALID_MODE_ID, 0, width, height, refreshRate, vsyncRate, alternativeRefreshRates, supportedHdrTypes); } /** * @hide */ public Mode(int modeId, int width, int height, float refreshRate, float vsyncRate, boolean isSynthetic, float[] alternativeRefreshRates, @HdrCapabilities.HdrType int[] supportedHdrTypes) { public Mode(int modeId, int parentModeId, @ModeFlags int flags, int width, int height, float refreshRate, float vsyncRate, float[] alternativeRefreshRates, @HdrCapabilities.HdrType int[] supportedHdrTypes) { mModeId = modeId; mParentModeId = parentModeId; mFlags = flags; mWidth = width; mHeight = height; mPeakRefreshRate = refreshRate; mVsyncRate = vsyncRate; mIsSynthetic = isSynthetic; mAlternativeRefreshRates = Arrays.copyOf(alternativeRefreshRates, alternativeRefreshRates.length); Arrays.sort(mAlternativeRefreshRates); Loading @@ -2513,6 +2528,24 @@ public final class Display { return mModeId; } /** * Returns parent mode's id if the mode is derived from other Display.Mode. * Returns INVALID_MODE_ID in other cases. * @hide */ public int getParentModeId() { return mParentModeId; } /** * Returns flags associated with this mode. * @hide */ @ModeFlags public int getFlags() { return mFlags; } /** * Returns the physical width of the display in pixels when configured in this mode's * resolution. Loading Loading @@ -2565,14 +2598,14 @@ public final class Display { } /** * Returns true if mode is synthetic and does not have corresponding * SurfaceControl.DisplayMode * Returns true if switching to this mode does not actually switch display mode on * SurfaceFlinger level * @hide */ @SuppressWarnings("UnflaggedApi") // For testing only @TestApi public boolean isSynthetic() { return mIsSynthetic; return mParentModeId != INVALID_MODE_ID || (mFlags & FLAG_ARR_RENDER_RATE) != 0; } /** Loading Loading @@ -2694,6 +2727,8 @@ public final class Display { public int hashCode() { int hash = 1; hash = hash * 17 + mModeId; hash = hash * 17 + mParentModeId; hash = hash * 17 + mFlags; hash = hash * 17 + mWidth; hash = hash * 17 + mHeight; hash = hash * 17 + Float.floatToIntBits(mPeakRefreshRate); Loading @@ -2707,11 +2742,12 @@ public final class Display { public String toString() { return new StringBuilder("{") .append("id=").append(mModeId) .append(", parentModeId=").append(mParentModeId) .append(", flags=").append(flagsToString(mFlags)) .append(", width=").append(mWidth) .append(", height=").append(mHeight) .append(", fps=").append(mPeakRefreshRate) .append(", vsync=").append(mVsyncRate) .append(", synthetic=").append(mIsSynthetic) .append(", alternativeRefreshRates=") .append(Arrays.toString(mAlternativeRefreshRates)) .append(", supportedHdrTypes=") Loading @@ -2720,31 +2756,40 @@ public final class Display { .toString(); } private static String flagsToString(@ModeFlags int flags) { StringBuilder msg = new StringBuilder(); if ((flags & FLAG_ARR_RENDER_RATE) != 0) { msg.append(", FLAG_ARR_RENDER_RATE"); } return msg.toString(); } @Override public int describeContents() { return 0; } private Mode(Parcel in) { this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.readFloat(), in.readBoolean(), in.createFloatArray(), in.createIntArray()); this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.readFloat(), in.createFloatArray(), in.createIntArray()); } @Override public void writeToParcel(Parcel out, int parcelableFlags) { out.writeInt(mModeId); out.writeInt(mParentModeId); out.writeInt(mFlags); out.writeInt(mWidth); out.writeInt(mHeight); out.writeFloat(mPeakRefreshRate); out.writeFloat(mVsyncRate); out.writeBoolean(mIsSynthetic); out.writeFloatArray(mAlternativeRefreshRates); out.writeIntArray(mSupportedHdrTypes); } @SuppressWarnings("hiding") public static final @android.annotation.NonNull Parcelable.Creator<Mode> CREATOR = new Parcelable.Creator<Mode>() { public static final @android.annotation.NonNull Parcelable.Creator<Mode> CREATOR = new Parcelable.Creator<>() { @Override public Mode createFromParcel(Parcel in) { return new Mode(in); Loading
services/core/java/com/android/server/display/DisplayAdapter.java +0 −17 Original line number Diff line number Diff line Loading @@ -24,7 +24,6 @@ import android.view.SurfaceControl; import com.android.server.display.feature.DisplayManagerFlags; import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicInteger; /** * A display adapter makes zero or more display devices available to the system Loading @@ -48,11 +47,6 @@ abstract class DisplayAdapter { public static final int DISPLAY_DEVICE_EVENT_CHANGED = 2; public static final int DISPLAY_DEVICE_EVENT_REMOVED = 3; /** * Used to generate globally unique display mode ids. */ private static final AtomicInteger NEXT_DISPLAY_MODE_ID = new AtomicInteger(1); // 0 = no mode. // Called with SyncRoot lock held. DisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, Listener listener, String name, DisplayManagerFlags featureFlags) { Loading Loading @@ -128,17 +122,6 @@ abstract class DisplayAdapter { mHandler.post(() -> mListener.onTraversalRequested()); } public static Display.Mode createMode(int width, int height, float refreshRate) { return createMode(width, height, refreshRate, refreshRate, new float[0], new int[0]); } public static Display.Mode createMode(int width, int height, float refreshRate, float vsyncRate, float[] alternativeRefreshRates, @Display.HdrCapabilities.HdrType int[] supportedHdrTypes) { return new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate, vsyncRate, /* isSynthetic= */ false, alternativeRefreshRates, supportedHdrTypes); } static int getPowerModeForState(int state) { switch (state) { case Display.STATE_OFF: Loading
services/core/java/com/android/server/display/DisplayModeFactory.java 0 → 100644 +141 −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 static com.android.server.display.DisplayDeviceConfig.DEFAULT_LOW_REFRESH_RATE; import android.annotation.SuppressLint; import android.view.Display; import android.view.SurfaceControl; import com.android.server.display.LocalDisplayAdapter.DisplayModeRecord; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class DisplayModeFactory { /** * Used to generate globally unique display mode ids. */ private static final AtomicInteger NEXT_DISPLAY_MODE_ID = new AtomicInteger(1); // 0 = no mode. private static final float FLOAT_TOLERANCE = 0.01f; private static final float SYNTHETIC_MODE_REFRESH_RATE = DEFAULT_LOW_REFRESH_RATE; private static final float SYNTHETIC_MODE_HIGH_BOUNDARY = SYNTHETIC_MODE_REFRESH_RATE + FLOAT_TOLERANCE; static Display.Mode createMode(int width, int height, float refreshRate) { return new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), Display.Mode.INVALID_MODE_ID, 0, width, height, refreshRate, refreshRate, new float[0], new int[0] ); } @SuppressLint("WrongConstant") static Display.Mode createMode(SurfaceControl.DisplayMode mode, float[] alternativeRefreshRates, boolean hasArrSupport, boolean syntheticModesV2Enabled) { int flags = 0; if (syntheticModesV2Enabled && hasArrSupport && mode.peakRefreshRate <= SYNTHETIC_MODE_HIGH_BOUNDARY) { flags |= Display.Mode.FLAG_ARR_RENDER_RATE; } return new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), Display.Mode.INVALID_MODE_ID, flags, mode.width, mode.height, mode.peakRefreshRate, mode.vsyncRate, alternativeRefreshRates, mode.supportedHdrTypes ); } @SuppressWarnings("MixedMutabilityReturnType") static List<DisplayModeRecord> createArrSyntheticModes(List<DisplayModeRecord> records, boolean hasArrSupport, boolean syntheticModesV2Enabled) { if (!syntheticModesV2Enabled) { return Collections.emptyList(); } if (!hasArrSupport) { return Collections.emptyList(); } List<Display.Mode> modesToSkipForArrSyntheticMode = new ArrayList<>(); for (DisplayModeRecord record: records) { // already have < 60Hz mode, don't need to add synthetic if ((record.mMode.getFlags() & Display.Mode.FLAG_ARR_RENDER_RATE) != 0) { modesToSkipForArrSyntheticMode.add(record.mMode); } } List<Display.Mode> modesForArrSyntheticMode = new ArrayList<>(); for (DisplayModeRecord record: records) { if (!is60HzAchievable(record.mMode)) { continue; } // already have < 60Hz mode, don't need to add synthetic if ((record.mMode.getFlags() & Display.Mode.FLAG_ARR_RENDER_RATE) != 0) { continue; } // already added OR should be skipped boolean skipAdding = hasMatichingForArr(modesForArrSyntheticMode, record.mMode) || hasMatichingForArr(modesToSkipForArrSyntheticMode, record.mMode); if (!skipAdding) { modesForArrSyntheticMode.add(record.mMode); } } List<DisplayModeRecord> syntheticModes = new ArrayList<>(); for (Display.Mode mode : modesForArrSyntheticMode) { syntheticModes.add(new DisplayModeRecord( new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), mode.getModeId(), Display.Mode.FLAG_ARR_RENDER_RATE, mode.getPhysicalWidth(), mode.getPhysicalHeight(), SYNTHETIC_MODE_REFRESH_RATE, SYNTHETIC_MODE_REFRESH_RATE, new float[0], mode.getSupportedHdrTypes() ))); } return syntheticModes; } private static boolean hasMatichingForArr(List<Display.Mode> modes, Display.Mode modeToMatch) { for (Display.Mode mode : modes) { if (matchingForSyntheticArr(modeToMatch, mode)) { return true; } } return false; } private static boolean is60HzAchievable(Display.Mode mode) { float divisor = mode.getVsyncRate() / SYNTHETIC_MODE_REFRESH_RATE; return Math.abs(divisor - Math.round(divisor)) < FLOAT_TOLERANCE; } private static boolean matchingForSyntheticArr(Display.Mode mode1, Display.Mode mode2) { return mode1.getPhysicalWidth() == mode2.getPhysicalWidth() && mode1.getPhysicalHeight() == mode2.getPhysicalHeight() && Arrays.equals(mode1.getSupportedHdrTypes(), mode2.getSupportedHdrTypes()); } }
services/core/java/com/android/server/display/LocalDisplayAdapter.java +32 −15 Original line number Diff line number Diff line Loading @@ -293,9 +293,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { public boolean updateDisplayPropertiesLocked(SurfaceControl.StaticDisplayInfo staticInfo, SurfaceControl.DynamicDisplayInfo dynamicInfo, SurfaceControl.DesiredDisplayModeSpecs modeSpecs) { boolean changed = updateDisplayModesLocked( dynamicInfo.supportedDisplayModes, dynamicInfo.preferredBootDisplayMode, dynamicInfo.activeDisplayModeId, dynamicInfo.renderFrameRate, modeSpecs); boolean changed = updateDisplayModesLocked(dynamicInfo, modeSpecs); changed |= updateStaticInfo(staticInfo); changed |= updateColorModesLocked(dynamicInfo.supportedColorModes, dynamicInfo.activeColorMode); Loading @@ -312,10 +311,15 @@ final class LocalDisplayAdapter extends DisplayAdapter { return changed; } public boolean updateDisplayModesLocked( SurfaceControl.DisplayMode[] displayModes, int preferredSfDisplayModeId, int activeSfDisplayModeId, float renderFrameRate, private boolean updateDisplayModesLocked(SurfaceControl.DynamicDisplayInfo dynamicInfo, SurfaceControl.DesiredDisplayModeSpecs modeSpecs) { SurfaceControl.DisplayMode[] displayModes = dynamicInfo.supportedDisplayModes; int preferredSfDisplayModeId = dynamicInfo.preferredBootDisplayMode; int activeSfDisplayModeId = dynamicInfo.activeDisplayModeId; float renderFrameRate = dynamicInfo.renderFrameRate; boolean hasArrSupport = dynamicInfo.hasArrSupport; boolean syntheticModesV2Enabled = getFeatureFlags().isSyntheticModesV2Enabled(); mSfDisplayModes = Arrays.copyOf(displayModes, displayModes.length); mActiveSfDisplayMode = getModeById(displayModes, activeSfDisplayModeId); mAppVsyncOffsetNanos = mActiveSfDisplayMode.appVsyncOffsetNanos; Loading Loading @@ -365,7 +369,9 @@ final class LocalDisplayAdapter extends DisplayAdapter { for (int j = 0; j < alternativeRates.length; j++) { alternativeRates[j] = alternativeRefreshRates.get(j); } record = new DisplayModeRecord(mode, alternativeRates); Display.Mode displayMode = DisplayModeFactory.createMode( mode, alternativeRates, hasArrSupport, syntheticModesV2Enabled); record = new DisplayModeRecord(displayMode); modesAdded = true; } records.add(record); Loading Loading @@ -438,13 +444,18 @@ final class LocalDisplayAdapter extends DisplayAdapter { sendTraversalRequestLocked(); } } List<DisplayModeRecord> syntheticModes = DisplayModeFactory .createArrSyntheticModes(records, hasArrSupport, syntheticModesV2Enabled); boolean recordsChanged = records.size() != mSupportedModes.size() || modesAdded; records.addAll(syntheticModes); boolean recordsChanged = records.size() != mSupportedModes.size() || modesAdded || !syntheticModes.isEmpty(); // If the records haven't changed then we're done here. if (!recordsChanged) { return activeModeChanged || preferredModeChanged || renderFrameRateChanged; } // Otherwise IDs changed and we need to update default/userPreferred/active mode IDs mSupportedModes.clear(); for (DisplayModeRecord record : records) { mSupportedModes.put(record.mMode.getModeId(), record); Loading Loading @@ -1151,7 +1162,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { mSurfaceControlProxy.clearBootDisplayMode(getDisplayTokenLocked()); } else { int preferredSfDisplayModeId = findSfDisplayModeIdLocked( mUserPreferredMode.getModeId(), mDefaultModeGroup); mUserPreferredMode, mDefaultModeGroup); mSurfaceControlProxy.setBootDisplayMode(getDisplayTokenLocked(), preferredSfDisplayModeId); } Loading Loading @@ -1405,6 +1416,14 @@ final class LocalDisplayAdapter extends DisplayAdapter { pw.println("mNitsToEvenDimmerStrength=" + mNitsToEvenDimmerStrength); } private int findSfDisplayModeIdLocked(Display.Mode mode, int modeGroup) { int modeId = mode.getModeId(); if (mode.getParentModeId() != INVALID_MODE_ID) { modeId = mode.getParentModeId(); } return findSfDisplayModeIdLocked(modeId, modeGroup); } private int findSfDisplayModeIdLocked(int displayModeId, int modeGroup) { int matchingSfDisplayModeId = INVALID_MODE_ID; DisplayModeRecord record = mSupportedModes.get(displayModeId); Loading Loading @@ -1550,13 +1569,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { /** * Keeps track of a display mode. */ private static final class DisplayModeRecord { static final class DisplayModeRecord { public final Display.Mode mMode; DisplayModeRecord(SurfaceControl.DisplayMode mode, float[] alternativeRefreshRates) { mMode = createMode(mode.width, mode.height, mode.peakRefreshRate, mode.vsyncRate, alternativeRefreshRates, mode.supportedHdrTypes); DisplayModeRecord(Display.Mode mode) { mMode = mode; } /** Loading
services/core/java/com/android/server/display/LogicalDisplay.java +9 −5 Original line number Diff line number Diff line Loading @@ -231,14 +231,16 @@ final class LogicalDisplay { private final boolean mSyncedResolutionSwitchEnabled; private final boolean mSyntheticModesV2Enabled; private boolean mCanHostTasks; LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) { this(displayId, layerStack, primaryDisplayDevice, false); this(displayId, layerStack, primaryDisplayDevice, false, true); } LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice, boolean isSyncedResolutionSwitchEnabled) { boolean isSyncedResolutionSwitchEnabled, boolean syntheticModesV2Enabled) { mDisplayId = displayId; mLayerStack = layerStack; mPrimaryDisplayDevice = primaryDisplayDevice; Loading @@ -250,6 +252,7 @@ final class LogicalDisplay { mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID; mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId; mSyncedResolutionSwitchEnabled = isSyncedResolutionSwitchEnabled; mSyntheticModesV2Enabled = syntheticModesV2Enabled; // No need to initialize mCanHostTasks here; it's handled in // DisplayManagerService#setupLogicalDisplay(). Loading Loading @@ -535,9 +538,10 @@ final class LogicalDisplay { mBaseDisplayInfo.userPreferredModeId = deviceInfo.userPreferredModeId; mBaseDisplayInfo.supportedModes = Arrays.copyOf( deviceInfo.supportedModes, deviceInfo.supportedModes.length); mBaseDisplayInfo.appsSupportedModes = syntheticModeManager.createAppSupportedModes( config, mBaseDisplayInfo.supportedModes, mBaseDisplayInfo.hasArrSupport ); mBaseDisplayInfo.appsSupportedModes = mSyntheticModesV2Enabled ? Arrays.copyOf(deviceInfo.supportedModes, deviceInfo.supportedModes.length) : syntheticModeManager.createAppSupportedModes(config, mBaseDisplayInfo.supportedModes, mBaseDisplayInfo.hasArrSupport); mBaseDisplayInfo.colorMode = deviceInfo.colorMode; mBaseDisplayInfo.supportedColorModes = Arrays.copyOf( deviceInfo.supportedColorModes, Loading