diff --git a/core/api/test-current.txt b/core/api/test-current.txt index b98ee11b014e7..efd96b91e1348 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1180,12 +1180,15 @@ package android.hardware.display { public final class DisplayManager { method public boolean areUserDisabledHdrTypesAllowed(); + method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void clearUserPreferredDisplayMode(); method @NonNull public int[] getUserDisabledHdrTypes(); + method @Nullable public android.view.Display.Mode getUserPreferredDisplayMode(); method public boolean isMinimalPostProcessingRequested(int); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAreUserDisabledHdrTypesAllowed(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE) public void setRefreshRateSwitchingType(int); method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public void setShouldAlwaysRespectAppRequestedMode(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setUserDisabledHdrTypes(@NonNull int[]); + method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setUserPreferredDisplayMode(@NonNull android.view.Display.Mode); method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public boolean shouldAlwaysRespectAppRequestedMode(); field public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2; // 0x2 field public static final int SWITCHING_TYPE_NONE = 0; // 0x0 @@ -2168,6 +2171,9 @@ package android.provider { field public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices"; field public static final String SHOW_FIRST_CRASH_DIALOG = "show_first_crash_dialog"; field public static final String USER_DISABLED_HDR_FORMATS = "user_disabled_hdr_formats"; + field public static final String USER_PREFERRED_REFRESH_RATE = "user_preferred_refresh_rate"; + field public static final String USER_PREFERRED_RESOLUTION_HEIGHT = "user_preferred_resolution_height"; + field public static final String USER_PREFERRED_RESOLUTION_WIDTH = "user_preferred_resolution_width"; field public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package"; } @@ -2735,6 +2741,7 @@ package android.view { } public final class Display { + method @NonNull public android.view.Display.Mode getDefaultMode(); method @NonNull public int[] getReportedHdrTypes(); method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut(); method public int getType(); @@ -2748,6 +2755,11 @@ package android.view { field public static final int TYPE_WIFI = 3; // 0x3 } + public static final class Display.Mode implements android.os.Parcelable { + ctor public Display.Mode(int, int, float); + method public boolean matches(int, int, float); + } + public class FocusFinder { method public static void sort(android.view.View[], int, int, android.view.ViewGroup, boolean); } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 73961ff373dd1..209ee40e42647 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -1112,6 +1112,44 @@ public final class DisplayManager { return mGlobal.getMinimumBrightnessCurve(); } + /** + * Sets the default display mode, according to the refresh rate and the resolution chosen by the + * user. + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) + public void setUserPreferredDisplayMode(@NonNull Display.Mode mode) { + // Create a new object containing default values for the unused fields like mode ID and + // alternative refresh rates. + Display.Mode preferredMode = new Display.Mode(mode.getPhysicalWidth(), + mode.getPhysicalHeight(), mode.getRefreshRate()); + mGlobal.setUserPreferredDisplayMode(preferredMode); + } + + /** + * Removes the user preferred display mode. + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) + public void clearUserPreferredDisplayMode() { + mGlobal.setUserPreferredDisplayMode(null); + } + + /** + * Returns the user preferred display mode. + * + * @hide + */ + @TestApi + @Nullable + public Display.Mode getUserPreferredDisplayMode() { + return mGlobal.getUserPreferredDisplayMode(); + } + /** * When enabled the app requested mode is always selected regardless of user settings and * policies for low brightness, low battery, etc. diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 1fec3c9ff50a7..75155bbab0c88 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -876,6 +876,29 @@ public final class DisplayManagerGlobal { } } + /** + * Sets the default display mode, according to the refresh rate and the resolution chosen by the + * user. + */ + public void setUserPreferredDisplayMode(Display.Mode mode) { + try { + mDm.setUserPreferredDisplayMode(mode); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Returns the user preferred display mode. + */ + public Display.Mode getUserPreferredDisplayMode() { + try { + return mDm.getUserPreferredDisplayMode(); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + /** * When enabled the app requested display resolution and refresh rate is always selected * in DisplayModeDirector regardless of user settings and policies for low brightness, low diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index 116214625e268..b3be9da7c9770 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -27,6 +27,7 @@ import android.hardware.display.VirtualDisplayConfig; import android.hardware.display.WifiDisplay; import android.hardware.display.WifiDisplayStatus; import android.media.projection.IMediaProjection; +import android.view.Display.Mode; import android.view.DisplayInfo; import android.view.Surface; @@ -162,6 +163,11 @@ interface IDisplayManager { // based on hardware capability. int getPreferredWideGamutColorSpaceId(); + // Sets the user preferred display mode. + // Requires WRITE_SECURE_SETTINGS permission. + void setUserPreferredDisplayMode(in Mode mode); + Mode getUserPreferredDisplayMode(); + // When enabled the app requested display resolution and refresh rate is always selected // in DisplayModeDirector regardless of user settings and policies for low brightness, low // battery etc. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 98730c2a9e400..7002a380f1d89 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -14603,6 +14603,38 @@ public final class Settings { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static final int HEADS_UP_ON = 1; + /** + * The refresh rate chosen by the user. + * + * @hide + */ + @TestApi + @Readable + @SuppressLint("NoSettingsProvider") + public static final String USER_PREFERRED_REFRESH_RATE = "user_preferred_refresh_rate"; + + /** + * The resolution height chosen by the user. + * + * @hide + */ + @TestApi + @Readable + @SuppressLint("NoSettingsProvider") + public static final String USER_PREFERRED_RESOLUTION_HEIGHT = + "user_preferred_resolution_height"; + + /** + * The resolution width chosen by the user. + * + * @hide + */ + @TestApi + @Readable + @SuppressLint("NoSettingsProvider") + public static final String USER_PREFERRED_RESOLUTION_WIDTH = + "user_preferred_resolution_width"; + /** * The name of the device */ diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index e7ff978266a2a..8259a9d981255 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -999,6 +999,18 @@ public final class Display { } } + /** + * Returns the default mode of the display. + * @hide + */ + @TestApi + public @NonNull Mode getDefaultMode() { + synchronized (mLock) { + updateDisplayInfoLocked(); + return mDisplayInfo.getDefaultMode(); + } + } + /** * Gets the supported modes of this display. */ @@ -1699,6 +1711,11 @@ public final class Display { */ public static final Mode[] EMPTY_ARRAY = new Mode[0]; + /** + * @hide + */ + public static final int INVALID_MODE_ID = -1; + private final int mModeId; private final int mWidth; private final int mHeight; @@ -1706,6 +1723,14 @@ public final class Display { @NonNull private final float[] mAlternativeRefreshRates; + /** + * @hide + */ + @TestApi + public Mode(int width, int height, float refreshRate) { + this(INVALID_MODE_ID, width, height, refreshRate, new float[0]); + } + /** * @hide */ @@ -1804,6 +1829,7 @@ public final class Display { * * @hide */ + @TestApi public boolean matches(int width, int height, float refreshRate) { return mWidth == width && mHeight == height && diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 9972eba7ea207..9e5d12223501b 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -1820,11 +1820,6 @@ public final class SurfaceControl implements Parcelable { * @hide */ public static final class DisplayMode { - /** - * Invalid display config id. - */ - public static final int INVALID_DISPLAY_MODE_ID = -1; - public int id; public int width; public int height; diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java index 80b7e1052d466..6f42d59cca189 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java @@ -81,6 +81,9 @@ public class GlobalSettings { Settings.Global.POWER_BUTTON_LONG_PRESS, Settings.Global.AUTOMATIC_POWER_SAVE_MODE, Settings.Global.ADVANCED_BATTERY_USAGE_AMOUNT, - Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS + Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS, + Settings.Global.USER_PREFERRED_REFRESH_RATE, + Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT, + Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index 0a75eb81734c5..93f900d9bda03 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -21,6 +21,7 @@ import static android.provider.settings.validators.SettingsValidators.ANY_INTEGE import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.NONE_NEGATIVE_LONG_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_FLOAT_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.PACKAGE_NAME_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.PERCENTAGE_INTEGER_VALIDATOR; @@ -321,6 +322,9 @@ public class GlobalSettingsValidators { VALIDATORS.put(Global.Wearable.COMBINED_LOCATION_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.WRIST_ORIENTATION_MODE, new DiscreteValueValidator(new String[] {"0", "1", "2", "3"})); + VALIDATORS.put(Global.USER_PREFERRED_REFRESH_RATE, NON_NEGATIVE_FLOAT_VALIDATOR); + VALIDATORS.put(Global.USER_PREFERRED_RESOLUTION_HEIGHT, ANY_INTEGER_VALIDATOR); + VALIDATORS.put(Global.USER_PREFERRED_RESOLUTION_WIDTH, ANY_INTEGER_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java index 223cc51a5390b..49012b0159c28 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java @@ -74,6 +74,20 @@ public class SettingsValidators { } }; + public static final Validator NON_NEGATIVE_FLOAT_VALIDATOR = new Validator() { + @Override + public boolean validate(@Nullable String value) { + if (value == null) { + return true; + } + try { + return Float.parseFloat(value) >= 0.0f; + } catch (NumberFormatException e) { + return false; + } + } + }; + public static final Validator URI_VALIDATOR = new Validator() { @Override public boolean validate(@Nullable String value) { diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 806bcc29f3057..def9685b1c1f0 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -205,6 +205,13 @@ abstract class DisplayDevice { public void setDesiredDisplayModeSpecsLocked( DisplayModeDirector.DesiredDisplayModeSpecs displayModeSpecs) {} + /** + * Sets the user preferred display mode. Removes the user preferred display mode and sets + * default display mode as the mode chosen by HAL, if 'mode' is null + * Returns true if the mode set by user is supported by the display. + */ + public void setUserPreferredDisplayModeLocked(Display.Mode mode) { } + /** * Sets the requested color mode. */ diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 73bcea6de115f..c964e37cbce4f 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -130,6 +130,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicLong; @@ -213,6 +214,9 @@ public final class DisplayManagerService extends SystemService { private int[] mUserDisabledHdrTypes = {}; private boolean mAreUserDisabledHdrTypesAllowed = true; + // Display mode chosen by user. + private Display.Mode mUserPreferredMode; + // The synchronization root for the display manager. // This lock guards most of the display manager's state. // NOTE: This is synchronized on while holding WindowManagerService.mWindowMap so never call @@ -581,6 +585,7 @@ public final class DisplayManagerService extends SystemService { updateSettingsLocked(); updateUserDisabledHdrTypesFromSettingsLocked(); + updateUserPreferredDisplayModeSettingsLocked(); } mDisplayModeDirector.setDesiredDisplayModeSpecsListener( @@ -795,9 +800,8 @@ public final class DisplayManagerService extends SystemService { mUserDisabledHdrTypes[i] = Integer.parseInt(userDisabledHdrTypeStrings[i]); } } catch (NumberFormatException e) { - Slog.e(TAG, - "Failed to parse USER_DISABLED_HDR_FORMATS. " - + "Clearing the setting.", e); + Slog.e(TAG, "Failed to parse USER_DISABLED_HDR_FORMATS. " + + "Clearing the setting.", e); clearUserDisabledHdrTypesLocked(); } } else { @@ -813,6 +817,17 @@ public final class DisplayManagerService extends SystemService { } } + private void updateUserPreferredDisplayModeSettingsLocked() { + final float refreshRate = Settings.Global.getFloat(mContext.getContentResolver(), + Settings.Global.USER_PREFERRED_REFRESH_RATE, 0.0f); + final int height = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT, -1); + final int width = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, -1); + Display.Mode mode = new Display.Mode(height, width, refreshRate); + mUserPreferredMode = isResolutionAndRefreshRateValid(mode) ? mode : null; + } + private DisplayInfo getDisplayInfoForFrameRateOverride(DisplayEventReceiver.FrameRateOverride[] frameRateOverrides, DisplayInfo info, int callingUid) { float frameRateHz = 0; @@ -1259,6 +1274,9 @@ public final class DisplayManagerService extends SystemService { recordStableDisplayStatsIfNeededLocked(display); recordTopInsetLocked(display); } + if (mUserPreferredMode != null) { + device.setUserPreferredDisplayModeLocked(mUserPreferredMode); + } addDisplayPowerControllerLocked(display); mDisplayStates.append(displayId, Display.STATE_UNKNOWN); @@ -1429,6 +1447,39 @@ public final class DisplayManagerService extends SystemService { return mWideColorSpace.getId(); } + void setUserPreferredDisplayModeInternal(Display.Mode mode) { + synchronized (mSyncRoot) { + if (Objects.equals(mUserPreferredMode, mode)) { + return; + } + + if (mode != null && !isResolutionAndRefreshRateValid(mode)) { + throw new IllegalArgumentException("width, height and refresh rate of mode should " + + "be greater than 0"); + } + mUserPreferredMode = mode; + + final int resolutionHeight = mode == null ? -1 : mode.getPhysicalHeight(); + final int resolutionWidth = mode == null ? -1 : mode.getPhysicalWidth(); + final float refreshRate = mode == null ? 0.0f : mode.getRefreshRate(); + Settings.Global.putFloat(mContext.getContentResolver(), + Settings.Global.USER_PREFERRED_REFRESH_RATE, refreshRate); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT, resolutionHeight); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, resolutionWidth); + mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> { + device.setUserPreferredDisplayModeLocked(mode); + }); + } + } + + private Display.Mode getUserPreferredDisplayModeInternal() { + synchronized (mSyncRoot) { + return mUserPreferredMode; + } + } + void setShouldAlwaysRespectAppRequestedModeInternal(boolean enabled) { mDisplayModeDirector.setShouldAlwaysRespectAppRequestedMode(enabled); } @@ -2019,6 +2070,10 @@ public final class DisplayManagerService extends SystemService { pw.println(" mStableDisplaySize=" + mStableDisplaySize); pw.println(" mMinimumBrightnessCurve=" + mMinimumBrightnessCurve); + if (mUserPreferredMode != null) { + pw.println(mUserPreferredMode.toString()); + } + pw.println(); if (!mAreUserDisabledHdrTypesAllowed) { pw.println(" mUserDisabledHdrTypes: size=" + mUserDisabledHdrTypes.length); @@ -2097,6 +2152,11 @@ public final class DisplayManagerService extends SystemService { return floatArray; } + private static boolean isResolutionAndRefreshRateValid(Display.Mode mode) { + return mode.getPhysicalWidth() > 0 && mode.getPhysicalHeight() > 0 + && mode.getRefreshRate() > 0.0f; + } + /** * This is the object that everything in the display manager locks on. * We make it an inner class within the {@link DisplayManagerService} to so that it is @@ -3083,6 +3143,29 @@ public final class DisplayManagerService extends SystemService { } } + @Override // Binder call + public void setUserPreferredDisplayMode(Display.Mode mode) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.WRITE_SECURE_SETTINGS, + "Permission required to set the user preferred display mode."); + final long token = Binder.clearCallingIdentity(); + try { + setUserPreferredDisplayModeInternal(mode); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call + public Display.Mode getUserPreferredDisplayMode() { + final long token = Binder.clearCallingIdentity(); + try { + return getUserPreferredDisplayModeInternal(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override // Binder call public void setShouldAlwaysRespectAppRequestedMode(boolean enabled) { mContext.enforceCallingOrSelfPermission( diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index be4ec717c2de4..fce3fd53efad7 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -97,8 +97,6 @@ public class DisplayModeDirector { // specific display. private static final int GLOBAL_ID = -1; - private static final int INVALID_DISPLAY_MODE_ID = -1; - private static final float FLOAT_TOLERANCE = RefreshRateRange.FLOAT_TOLERANCE; private final Object mLock = new Object(); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 67df5653c7119..dbe17b71789ab 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -16,6 +16,8 @@ package com.android.server.display; +import static android.view.Display.Mode.INVALID_MODE_ID; + import android.app.ActivityThread; import android.content.Context; import android.content.res.Resources; @@ -65,8 +67,6 @@ final class LocalDisplayAdapter extends DisplayAdapter { private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular"; - private static final int NO_DISPLAY_MODE_ID = 0; - private final LongSparseArray mDevices = new LongSparseArray<>(); private final Injector mInjector; @@ -191,9 +191,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { // This is only set in the runnable returned from requestDisplayStateLocked. private float mBrightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT; private float mSdrBrightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT; - private int mDefaultModeId; + private int mDefaultModeId = INVALID_MODE_ID; private int mDefaultModeGroup; - private int mActiveModeId; + private int mUserPreferredModeId = INVALID_MODE_ID; + private Display.Mode mUserPreferredMode; + private int mActiveModeId = INVALID_MODE_ID; private DisplayModeDirector.DesiredDisplayModeSpecs mDisplayModeSpecs = new DisplayModeDirector.DesiredDisplayModeSpecs(); private boolean mDisplayModeSpecsInvalid; @@ -323,7 +325,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { // Check whether SurfaceFlinger or the display device changed the active mode out from // under us. - if (mActiveModeId != NO_DISPLAY_MODE_ID + if (mActiveModeId != INVALID_MODE_ID && mActiveModeId != activeRecord.mMode.getModeId()) { Slog.d(TAG, "The active mode was changed from SurfaceFlinger or the display" + " device to " + activeRecord.mMode); @@ -334,12 +336,12 @@ final class LocalDisplayAdapter extends DisplayAdapter { // Check whether surface flinger spontaneously changed display config specs out from // under us. If so, schedule a traversal to reapply our display config specs. - if (mDisplayModeSpecs.baseModeId != NO_DISPLAY_MODE_ID) { + if (mDisplayModeSpecs.baseModeId != INVALID_MODE_ID) { int activeBaseMode = findMatchingModeIdLocked(modeSpecs.defaultMode); // If we can't map the defaultMode index to a mode, then the physical display // modes must have changed, and the code below for handling changes to the // list of available modes will take care of updating display mode specs. - if (activeBaseMode == NO_DISPLAY_MODE_ID + if (activeBaseMode == INVALID_MODE_ID || mDisplayModeSpecs.baseModeId != activeBaseMode || mDisplayModeSpecs.primaryRefreshRateRange.min != modeSpecs.primaryRefreshRateMin @@ -366,7 +368,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { } // For a new display, we need to initialize the default mode ID. - if (mDefaultModeId == NO_DISPLAY_MODE_ID) { + if (mDefaultModeId == INVALID_MODE_ID) { mDefaultModeId = activeRecord.mMode.getModeId(); mDefaultModeGroup = mActiveSfDisplayMode.group; } else if (modesAdded && activeModeChanged) { @@ -383,7 +385,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { // Determine whether the display mode specs' base mode is still there. if (mSupportedModes.indexOfKey(mDisplayModeSpecs.baseModeId) < 0) { - if (mDisplayModeSpecs.baseModeId != NO_DISPLAY_MODE_ID) { + if (mDisplayModeSpecs.baseModeId != INVALID_MODE_ID) { Slog.w(TAG, "DisplayModeSpecs base mode no longer available, using currently" + " active mode."); @@ -392,13 +394,17 @@ final class LocalDisplayAdapter extends DisplayAdapter { mDisplayModeSpecsInvalid = true; } + if (mUserPreferredMode != null) { + mUserPreferredModeId = findUserPreferredModeIdLocked(mUserPreferredMode); + } + // Determine whether the active mode is still there. if (mSupportedModes.indexOfKey(mActiveModeId) < 0) { - if (mActiveModeId != NO_DISPLAY_MODE_ID) { + if (mActiveModeId != INVALID_MODE_ID) { Slog.w(TAG, "Active display mode no longer available, reverting to default" + " mode."); } - mActiveModeId = mDefaultModeId; + mActiveModeId = getPreferredModeId(); } // Schedule traversals so that we apply pending changes. @@ -414,6 +420,12 @@ final class LocalDisplayAdapter extends DisplayAdapter { return mDisplayDeviceConfig; } + private int getPreferredModeId() { + return mUserPreferredModeId != INVALID_MODE_ID + ? mUserPreferredModeId + : mDefaultModeId; + } + private void loadDisplayDeviceConfig() { // Load display device config final Context context = getOverlayContext(); @@ -561,7 +573,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { mInfo.width = mActiveSfDisplayMode.width; mInfo.height = mActiveSfDisplayMode.height; mInfo.modeId = mActiveModeId; - mInfo.defaultModeId = mDefaultModeId; + mInfo.defaultModeId = getPreferredModeId(); mInfo.supportedModes = getDisplayModes(mSupportedModes); mInfo.colorMode = mActiveColorMode; mInfo.allmSupported = mAllmSupported; @@ -822,6 +834,17 @@ final class LocalDisplayAdapter extends DisplayAdapter { return null; } + @Override + public void setUserPreferredDisplayModeLocked(Display.Mode mode) { + final int oldModeId = getPreferredModeId(); + mUserPreferredMode = mode; + mUserPreferredModeId = findUserPreferredModeIdLocked(mode); + + if (oldModeId != getPreferredModeId()) { + updateDeviceInfoLocked(); + } + } + @Override public void setRequestedColorModeLocked(int colorMode) { requestColorModeLocked(colorMode); @@ -903,7 +926,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { } mActiveSfDisplayMode = getModeById(mSfDisplayModes, activeSfModeId); mActiveModeId = findMatchingModeIdLocked(activeSfModeId); - if (mActiveModeId == NO_DISPLAY_MODE_ID) { + if (mActiveModeId == INVALID_MODE_ID) { Slog.w(TAG, "In unknown mode after setting allowed modes" + ", activeModeId=" + activeSfModeId); } @@ -988,6 +1011,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { pw.println("mActiveModeId=" + mActiveModeId); pw.println("mActiveColorMode=" + mActiveColorMode); pw.println("mDefaultModeId=" + mDefaultModeId); + pw.println("mUserPreferredModeId=" + mUserPreferredModeId); pw.println("mState=" + Display.stateToString(mState)); pw.println("mBrightnessState=" + mBrightnessState); pw.println("mBacklightAdapter=" + mBacklightAdapter); @@ -1010,13 +1034,12 @@ final class LocalDisplayAdapter extends DisplayAdapter { } private int findDisplayModeIdLocked(int modeId, int modeGroup) { - int matchingModeId = SurfaceControl.DisplayMode.INVALID_DISPLAY_MODE_ID; + int matchingModeId = INVALID_MODE_ID; DisplayModeRecord record = mSupportedModes.get(modeId); if (record != null) { for (SurfaceControl.DisplayMode mode : mSfDisplayModes) { if (record.hasMatchingMode(mode)) { - if (matchingModeId - == SurfaceControl.DisplayMode.INVALID_DISPLAY_MODE_ID) { + if (matchingModeId == INVALID_MODE_ID) { matchingModeId = mode.id; } @@ -1030,11 +1053,25 @@ final class LocalDisplayAdapter extends DisplayAdapter { return matchingModeId; } + private int findUserPreferredModeIdLocked(Display.Mode userPreferredMode) { + if (userPreferredMode != null) { + for (int i = 0; i < mSupportedModes.size(); i++) { + Display.Mode supportedMode = mSupportedModes.valueAt(i).mMode; + if (userPreferredMode.matches(supportedMode.getPhysicalWidth(), + supportedMode.getPhysicalHeight(), + supportedMode.getRefreshRate())) { + return supportedMode.getModeId(); + } + } + } + return INVALID_MODE_ID; + } + private int findMatchingModeIdLocked(int sfModeId) { SurfaceControl.DisplayMode mode = getModeById(mSfDisplayModes, sfModeId); if (mode == null) { Slog.e(TAG, "Invalid display mode ID " + sfModeId); - return NO_DISPLAY_MODE_ID; + return INVALID_MODE_ID; } for (int i = 0; i < mSupportedModes.size(); i++) { DisplayModeRecord record = mSupportedModes.valueAt(i); @@ -1042,7 +1079,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { return record.mMode.getModeId(); } } - return NO_DISPLAY_MODE_ID; + return INVALID_MODE_ID; } private void updateDeviceInfoLocked() { diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index 28cdd6317e5a2..b5ad459d29f76 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -335,6 +335,62 @@ public class LocalDisplayAdapterTest { }); } + @Test + public void testAfterDisplayChange_DefaultDisplayModeIsUpdated() throws Exception { + SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f); + SurfaceControl.DisplayMode[] modes = + new SurfaceControl.DisplayMode[]{displayMode}; + FakeDisplay display = new FakeDisplay(PORT_A, modes, 0); + setUpDisplay(display); + updateAvailableDisplays(); + mAdapter.registerLocked(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + assertThat(mListener.addedDisplays.size()).isEqualTo(1); + assertThat(mListener.changedDisplays).isEmpty(); + + DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get( + 0).getDisplayDeviceInfoLocked(); + + assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length); + assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode); + + Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); + assertThat(matches(defaultMode, displayMode)).isTrue(); + + // Set the display mode to an unsupported mode + SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 1920, 1080, 120f); + mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked( + new Display.Mode(displayMode2.width, displayMode2.height, + displayMode2.refreshRate)); + updateAvailableDisplays(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); + assertThat(matches(defaultMode, displayMode2)).isFalse(); + + // Change the display + modes = new SurfaceControl.DisplayMode[]{displayMode, displayMode2}; + display.dynamicInfo.supportedDisplayModes = modes; + setUpDisplay(display); + mInjector.getTransmitter().sendHotplug(display, /* connected */ true); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + assertTrue(mListener.traversalRequested); + assertThat(mListener.addedDisplays.size()).isEqualTo(1); + assertThat(mListener.changedDisplays.size()).isEqualTo(1); + + DisplayDevice displayDevice = mListener.changedDisplays.get(0); + displayDevice.applyPendingDisplayDeviceInfoChangesLocked(); + displayDeviceInfo = mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(); + + assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length); + assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode); + assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2); + + defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); + assertThat(matches(defaultMode, displayMode2)).isTrue(); + } + @Test public void testAfterDisplayChange_DisplayModesAreUpdated() throws Exception { SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f); @@ -356,12 +412,10 @@ public class LocalDisplayAdapterTest { assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode); Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); - assertThat(defaultMode.matches(displayMode.width, displayMode.height, - displayMode.refreshRate)).isTrue(); + assertThat(matches(defaultMode, displayMode)).isTrue(); Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId); - assertThat(activeMode.matches(displayMode.width, displayMode.height, - displayMode.refreshRate)).isTrue(); + assertThat(matches(activeMode, displayMode)).isTrue(); // Change the display SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(1, 3840, 2160, 60f); @@ -385,12 +439,10 @@ public class LocalDisplayAdapterTest { assertModeIsSupported(displayDeviceInfo.supportedModes, addedDisplayInfo); activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId); - assertThat(activeMode.matches(addedDisplayInfo.width, addedDisplayInfo.height, - addedDisplayInfo.refreshRate)).isTrue(); + assertThat(matches(activeMode, addedDisplayInfo)).isTrue(); defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); - assertThat(defaultMode.matches(addedDisplayInfo.width, addedDisplayInfo.height, - addedDisplayInfo.refreshRate)).isTrue(); + assertThat(matches(defaultMode, addedDisplayInfo)).isTrue(); } @Test @@ -918,4 +970,9 @@ public class LocalDisplayAdapterTest { }); return mockArray; } + + private boolean matches(Display.Mode a, SurfaceControl.DisplayMode b) { + return a.getPhysicalWidth() == b.width && a.getPhysicalHeight() == b.height + && Float.floatToIntBits(a.getRefreshRate()) == Float.floatToIntBits(b.refreshRate); + } }