diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 2653b2ac0b88f..fd94be9a29212 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -103,10 +103,6 @@ public class DisplayModeDirector { private static final int MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED = 7; private static final int MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED = 8; - // Special ID used to indicate that given vote is to be applied globally, rather than to a - // specific display. - private static final int GLOBAL_ID = -1; - private static final float FLOAT_TOLERANCE = RefreshRateRange.FLOAT_TOLERANCE; private final Object mLock = new Object(); @@ -129,10 +125,6 @@ public class DisplayModeDirector { @Nullable private DisplayDeviceConfig mDefaultDisplayDeviceConfig; - // A map from the display ID to the collection of votes and their priority. The latter takes - // the form of another map from the priority to the vote itself so that each priority is - // guaranteed to have exactly one vote, which is also easily and efficiently replaceable. - private SparseArray> mVotesByDisplay; // A map from the display ID to the supported modes on that display. private SparseArray mSupportedModesByDisplay; // A map from the display ID to the default mode of that display. @@ -146,6 +138,8 @@ public class DisplayModeDirector { private final boolean mSupportsFrameRateOverride; + private final VotesStorage mVotesStorage; + /** * The allowed refresh rate switching type. This is used by SurfaceFlinger. */ @@ -161,7 +155,6 @@ public class DisplayModeDirector { mContext = context; mHandler = new DisplayModeDirectorHandler(handler.getLooper()); mInjector = injector; - mVotesByDisplay = new SparseArray<>(); mSupportedModesByDisplay = new SparseArray<>(); mDefaultModeByDisplay = new SparseArray<>(); mAppRequestObserver = new AppRequestObserver(); @@ -171,15 +164,11 @@ public class DisplayModeDirector { mBrightnessObserver = new BrightnessObserver(context, handler, injector); mDefaultDisplayDeviceConfig = null; mUdfpsObserver = new UdfpsObserver(); - final BallotBox ballotBox = (displayId, priority, vote) -> { - synchronized (mLock) { - updateVoteLocked(displayId, priority, vote); - } - }; - mDisplayObserver = new DisplayObserver(context, handler, ballotBox); - mSensorObserver = new SensorObserver(context, ballotBox, injector); - mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, ballotBox); - mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler(), + mVotesStorage = new VotesStorage(this::notifyDesiredDisplayModeSpecsChangedLocked); + mDisplayObserver = new DisplayObserver(context, handler, mVotesStorage); + mSensorObserver = new SensorObserver(context, mVotesStorage, injector); + mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, mVotesStorage); + mHbmObserver = new HbmObserver(injector, mVotesStorage, BackgroundThread.getHandler(), mDeviceConfigDisplaySettings); mAlwaysRespectAppRequest = false; mSupportsFrameRateOverride = injector.supportsFrameRateOverride(); @@ -226,29 +215,7 @@ public class DisplayModeDirector { mLoggingEnabled = loggingEnabled; mBrightnessObserver.setLoggingEnabled(loggingEnabled); mSkinThermalStatusObserver.setLoggingEnabled(loggingEnabled); - } - - @NonNull - private SparseArray getVotesLocked(int displayId) { - SparseArray displayVotes = mVotesByDisplay.get(displayId); - final SparseArray votes; - if (displayVotes != null) { - votes = displayVotes.clone(); - } else { - votes = new SparseArray<>(); - } - - SparseArray globalVotes = mVotesByDisplay.get(GLOBAL_ID); - if (globalVotes != null) { - for (int i = 0; i < globalVotes.size(); i++) { - int priority = globalVotes.keyAt(i); - if (votes.indexOfKey(priority) < 0) { - votes.put(priority, globalVotes.valueAt(i)); - } - } - } - - return votes; + mVotesStorage.setLoggingEnabled(loggingEnabled); } private static final class VoteSummary { @@ -407,7 +374,7 @@ public class DisplayModeDirector { @NonNull public DesiredDisplayModeSpecs getDesiredDisplayModeSpecs(int displayId) { synchronized (mLock) { - SparseArray votes = getVotesLocked(displayId); + SparseArray votes = mVotesStorage.getVotes(displayId); Display.Mode[] modes = mSupportedModesByDisplay.get(displayId); Display.Mode defaultMode = mDefaultModeByDisplay.get(displayId); if (modes == null || defaultMode == null) { @@ -780,10 +747,8 @@ public class DisplayModeDirector { @VisibleForTesting @Nullable Vote getVote(int displayId, int priority) { - synchronized (mLock) { - SparseArray votes = getVotesLocked(displayId); - return votes.get(priority); - } + SparseArray votes = mVotesStorage.getVotes(displayId); + return votes.get(priority); } /** @@ -806,18 +771,6 @@ public class DisplayModeDirector { final Display.Mode mode = mDefaultModeByDisplay.valueAt(i); pw.println(" " + id + " -> " + mode); } - pw.println(" mVotesByDisplay:"); - for (int i = 0; i < mVotesByDisplay.size(); i++) { - pw.println(" " + mVotesByDisplay.keyAt(i) + ":"); - SparseArray votes = mVotesByDisplay.valueAt(i); - for (int p = Vote.MAX_PRIORITY; p >= Vote.MIN_PRIORITY; p--) { - Vote vote = votes.get(p); - if (vote == null) { - continue; - } - pw.println(" " + Vote.priorityToString(p) + " -> " + vote); - } - } pw.println(" mModeSwitchingType: " + switchingTypeToString(mModeSwitchingType)); pw.println(" mAlwaysRespectAppRequest: " + mAlwaysRespectAppRequest); mSettingsObserver.dumpLocked(pw); @@ -827,44 +780,10 @@ public class DisplayModeDirector { mHbmObserver.dumpLocked(pw); mSkinThermalStatusObserver.dumpLocked(pw); } - + mVotesStorage.dump(pw); mSensorObserver.dump(pw); } - private void updateVoteLocked(int priority, Vote vote) { - updateVoteLocked(GLOBAL_ID, priority, vote); - } - - private void updateVoteLocked(int displayId, int priority, Vote vote) { - if (mLoggingEnabled) { - Slog.i(TAG, "updateVoteLocked(displayId=" + displayId - + ", priority=" + Vote.priorityToString(priority) - + ", vote=" + vote + ")"); - } - if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) { - Slog.w(TAG, "Received a vote with an invalid priority, ignoring:" - + " priority=" + Vote.priorityToString(priority) - + ", vote=" + vote, new Throwable()); - return; - } - final SparseArray votes = getOrCreateVotesByDisplay(displayId); - - if (vote != null) { - votes.put(priority, vote); - } else { - votes.remove(priority); - } - - if (votes.size() == 0) { - if (mLoggingEnabled) { - Slog.i(TAG, "No votes left for display " + displayId + ", removing."); - } - mVotesByDisplay.remove(displayId); - } - - notifyDesiredDisplayModeSpecsChangedLocked(); - } - @GuardedBy("mLock") private float getMaxRefreshRateLocked(int displayId) { Display.Mode[] modes = mSupportedModesByDisplay.get(displayId); @@ -890,16 +809,6 @@ public class DisplayModeDirector { } } - private SparseArray getOrCreateVotesByDisplay(int displayId) { - if (mVotesByDisplay.indexOfKey(displayId) >= 0) { - return mVotesByDisplay.get(displayId); - } else { - SparseArray votes = new SparseArray<>(); - mVotesByDisplay.put(displayId, votes); - return votes; - } - } - private static String switchingTypeToString(@DisplayManager.SwitchingType int type) { switch (type) { case DisplayManager.SWITCHING_TYPE_NONE: @@ -927,7 +836,7 @@ public class DisplayModeDirector { @VisibleForTesting void injectVotesByDisplay(SparseArray> votesByDisplay) { - mVotesByDisplay = votesByDisplay; + mVotesStorage.injectVotesByDisplay(votesByDisplay); } @VisibleForTesting @@ -1156,225 +1065,6 @@ public class DisplayModeDirector { } } - @VisibleForTesting - static final class Vote { - // DEFAULT_RENDER_FRAME_RATE votes for render frame rate [0, DEFAULT]. As the lowest - // priority vote, it's overridden by all other considerations. It acts to set a default - // frame rate for a device. - public static final int PRIORITY_DEFAULT_RENDER_FRAME_RATE = 0; - - // PRIORITY_FLICKER_REFRESH_RATE votes for a single refresh rate like [60,60], [90,90] or - // null. It is used to set a preferred refresh rate value in case the higher priority votes - // result is a range. - public static final int PRIORITY_FLICKER_REFRESH_RATE = 1; - - // High-brightness-mode may need a specific range of refresh-rates to function properly. - public static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 2; - - // SETTING_MIN_RENDER_FRAME_RATE is used to propose a lower bound of the render frame rate. - // It votes [minRefreshRate, Float.POSITIVE_INFINITY] - public static final int PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE = 3; - - // APP_REQUEST_RENDER_FRAME_RATE_RANGE is used to for internal apps to limit the render - // frame rate in certain cases, mostly to preserve power. - // @see android.view.WindowManager.LayoutParams#preferredMinRefreshRate - // @see android.view.WindowManager.LayoutParams#preferredMaxRefreshRate - // It votes to [preferredMinRefreshRate, preferredMaxRefreshRate]. - public static final int PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE = 4; - - // We split the app request into different priorities in case we can satisfy one desire - // without the other. - - // Application can specify preferred refresh rate with below attrs. - // @see android.view.WindowManager.LayoutParams#preferredRefreshRate - // @see android.view.WindowManager.LayoutParams#preferredDisplayModeId - // - // When the app specifies a LayoutParams#preferredDisplayModeId, in addition to the - // refresh rate, it also chooses a preferred size (resolution) as part of the selected - // mode id. The app preference is then translated to APP_REQUEST_BASE_MODE_REFRESH_RATE and - // optionally to APP_REQUEST_SIZE as well, if a mode id was selected. - // The system also forces some apps like denylisted app to run at a lower refresh rate. - // @see android.R.array#config_highRefreshRateBlacklist - // - // When summarizing the votes and filtering the allowed display modes, these votes determine - // which mode id should be the base mode id to be sent to SurfaceFlinger: - // - APP_REQUEST_BASE_MODE_REFRESH_RATE is used to validate the vote summary. If a summary - // includes a base mode refresh rate, but it is not in the refresh rate range, then the - // summary is considered invalid so we could drop a lower priority vote and try again. - // - APP_REQUEST_SIZE is used to filter out display modes of a different size. - // - // The preferred refresh rate is set on the main surface of the app outside of - // DisplayModeDirector. - // @see com.android.server.wm.WindowState#updateFrameRateSelectionPriorityIfNeeded - public static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 5; - public static final int PRIORITY_APP_REQUEST_SIZE = 6; - - // SETTING_PEAK_RENDER_FRAME_RATE has a high priority and will restrict the bounds of the - // rest of low priority voters. It votes [0, max(PEAK, MIN)] - public static final int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 7; - - // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh - // rate to max value (same as for PRIORITY_UDFPS) on lock screen - public static final int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 8; - - // For concurrent displays we want to limit refresh rate on all displays - public static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 9; - - // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if - // Settings.Global.LOW_POWER_MODE is on. - public static final int PRIORITY_LOW_POWER_MODE = 10; - - // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the - // higher priority voters' result is a range, it will fix the rate to a single choice. - // It's used to avoid refresh rate switches in certain conditions which may result in the - // user seeing the display flickering when the switches occur. - public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 11; - - // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL. - public static final int PRIORITY_SKIN_TEMPERATURE = 12; - - // The proximity sensor needs the refresh rate to be locked in order to function, so this is - // set to a high priority. - public static final int PRIORITY_PROXIMITY = 13; - - // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order - // to function, so this needs to be the highest priority of all votes. - public static final int PRIORITY_UDFPS = 14; - - // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and - // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString. - - public static final int MIN_PRIORITY = PRIORITY_DEFAULT_RENDER_FRAME_RATE; - public static final int MAX_PRIORITY = PRIORITY_UDFPS; - - // The cutoff for the app request refresh rate range. Votes with priorities lower than this - // value will not be considered when constructing the app request refresh rate range. - public static final int APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF = - PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE; - - /** - * A value signifying an invalid width or height in a vote. - */ - public static final int INVALID_SIZE = -1; - - /** - * The requested width of the display in pixels, or INVALID_SIZE; - */ - public final int width; - /** - * The requested height of the display in pixels, or INVALID_SIZE; - */ - public final int height; - /** - * Information about the refresh rate frame rate ranges DM would like to set the display to. - */ - public final RefreshRateRanges refreshRateRanges; - - /** - * Whether refresh rate switching should be disabled (i.e. the refresh rate range is - * a single value). - */ - public final boolean disableRefreshRateSwitching; - - /** - * The preferred refresh rate selected by the app. It is used to validate that the summary - * refresh rate ranges include this value, and are not restricted by a lower priority vote. - */ - public final float appRequestBaseModeRefreshRate; - - public static Vote forPhysicalRefreshRates(float minRefreshRate, float maxRefreshRate) { - return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate, 0, - Float.POSITIVE_INFINITY, - minRefreshRate == maxRefreshRate, 0f); - } - - public static Vote forRenderFrameRates(float minFrameRate, float maxFrameRate) { - return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, minFrameRate, - maxFrameRate, - false, 0f); - } - - public static Vote forSize(int width, int height) { - return new Vote(width, height, 0, Float.POSITIVE_INFINITY, 0, Float.POSITIVE_INFINITY, - false, - 0f); - } - - public static Vote forDisableRefreshRateSwitching() { - return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, 0, - Float.POSITIVE_INFINITY, true, - 0f); - } - - public static Vote forBaseModeRefreshRate(float baseModeRefreshRate) { - return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, 0, - Float.POSITIVE_INFINITY, false, - baseModeRefreshRate); - } - - private Vote(int width, int height, - float minPhysicalRefreshRate, - float maxPhysicalRefreshRate, - float minRenderFrameRate, - float maxRenderFrameRate, - boolean disableRefreshRateSwitching, - float baseModeRefreshRate) { - this.width = width; - this.height = height; - this.refreshRateRanges = new RefreshRateRanges( - new RefreshRateRange(minPhysicalRefreshRate, maxPhysicalRefreshRate), - new RefreshRateRange(minRenderFrameRate, maxRenderFrameRate)); - this.disableRefreshRateSwitching = disableRefreshRateSwitching; - this.appRequestBaseModeRefreshRate = baseModeRefreshRate; - } - - public static String priorityToString(int priority) { - switch (priority) { - case PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE: - return "PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE"; - case PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE: - return "PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE"; - case PRIORITY_APP_REQUEST_SIZE: - return "PRIORITY_APP_REQUEST_SIZE"; - case PRIORITY_DEFAULT_RENDER_FRAME_RATE: - return "PRIORITY_DEFAULT_REFRESH_RATE"; - case PRIORITY_FLICKER_REFRESH_RATE: - return "PRIORITY_FLICKER_REFRESH_RATE"; - case PRIORITY_FLICKER_REFRESH_RATE_SWITCH: - return "PRIORITY_FLICKER_REFRESH_RATE_SWITCH"; - case PRIORITY_HIGH_BRIGHTNESS_MODE: - return "PRIORITY_HIGH_BRIGHTNESS_MODE"; - case PRIORITY_PROXIMITY: - return "PRIORITY_PROXIMITY"; - case PRIORITY_LOW_POWER_MODE: - return "PRIORITY_LOW_POWER_MODE"; - case PRIORITY_SKIN_TEMPERATURE: - return "PRIORITY_SKIN_TEMPERATURE"; - case PRIORITY_UDFPS: - return "PRIORITY_UDFPS"; - case PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE: - return "PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE"; - case PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE: - return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE"; - case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE: - return "PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE"; - case PRIORITY_LAYOUT_LIMITED_FRAME_RATE: - return "PRIORITY_LAYOUT_LIMITED_FRAME_RATE"; - default: - return Integer.toString(priority); - } - } - - @Override - public String toString() { - return "Vote{" - + "width=" + width + ", height=" + height - + ", refreshRateRanges=" + refreshRateRanges - + ", disableRefreshRateSwitching=" + disableRefreshRateSwitching - + ", appRequestBaseModeRefreshRate=" + appRequestBaseModeRefreshRate + "}"; - } - } - @VisibleForTesting final class SettingsObserver extends ContentObserver { private final Uri mSmoothDisplaySetting = @@ -1510,7 +1200,7 @@ public class DisplayModeDirector { } else { vote = null; } - updateVoteLocked(Vote.PRIORITY_LOW_POWER_MODE, vote); + mVotesStorage.updateGlobalVote(Vote.PRIORITY_LOW_POWER_MODE, vote); mBrightnessObserver.onLowPowerModeEnabledLocked(inLowPowerMode); } @@ -1529,13 +1219,14 @@ public class DisplayModeDirector { Vote peakVote = peakRefreshRate == 0f ? null : Vote.forRenderFrameRates(0f, Math.max(minRefreshRate, peakRefreshRate)); - updateVoteLocked(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE, peakVote); - updateVoteLocked(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, + mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE, + peakVote); + mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, Vote.forRenderFrameRates(minRefreshRate, Float.POSITIVE_INFINITY)); Vote defaultVote = defaultRefreshRate == 0f ? null : Vote.forRenderFrameRates(0f, defaultRefreshRate); - updateVoteLocked(Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE, defaultVote); + mVotesStorage.updateGlobalVote(Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE, defaultVote); float maxRefreshRate; if (peakRefreshRate == 0f && defaultRefreshRate == 0f) { @@ -1619,9 +1310,9 @@ public class DisplayModeDirector { sizeVote = null; } - updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, baseModeRefreshRateVote); - updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote); + mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote); } private void setAppPreferredRefreshRateRangeLocked(int displayId, @@ -1652,11 +1343,8 @@ public class DisplayModeDirector { mAppPreferredRefreshRateRangeByDisplay.remove(displayId); vote = null; } - synchronized (mLock) { - updateVoteLocked(displayId, - Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE, - vote); - } + mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE, + vote); } private Display.Mode findModeByIdLocked(int displayId, int modeId) { @@ -1696,12 +1384,12 @@ public class DisplayModeDirector { // calling into us already holding its own lock. private final Context mContext; private final Handler mHandler; - private final BallotBox mBallotBox; + private final VotesStorage mVotesStorage; - DisplayObserver(Context context, Handler handler, BallotBox ballotBox) { + DisplayObserver(Context context, Handler handler, VotesStorage votesStorage) { mContext = context; mHandler = handler; - mBallotBox = ballotBox; + mVotesStorage = votesStorage; } public void observe() { @@ -1761,7 +1449,7 @@ public class DisplayModeDirector { Vote vote = info != null && info.layoutLimitedRefreshRate != null ? Vote.forPhysicalRefreshRates(info.layoutLimitedRefreshRate.min, info.layoutLimitedRefreshRate.max) : null; - mBallotBox.vote(displayId, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE, vote); + mVotesStorage.updateVote(displayId, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE, vote); } private void updateDisplayModes(int displayId, @Nullable DisplayInfo info) { @@ -2083,8 +1771,8 @@ public class DisplayModeDirector { updateSensorStatus(); if (!changeable) { // Revoke previous vote from BrightnessObserver - updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE, null); - updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, null); + mVotesStorage.updateGlobalVote(Vote.PRIORITY_FLICKER_REFRESH_RATE, null); + mVotesStorage.updateGlobalVote(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, null); } } } @@ -2409,8 +2097,9 @@ public class DisplayModeDirector { Slog.d(TAG, "Display brightness " + mBrightness + ", ambient lux " + mAmbientLux + ", Vote " + refreshRateVote); } - updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE, refreshRateVote); - updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, refreshRateSwitchingVote); + mVotesStorage.updateGlobalVote(Vote.PRIORITY_FLICKER_REFRESH_RATE, refreshRateVote); + mVotesStorage.updateGlobalVote(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, + refreshRateSwitchingVote); } private boolean hasValidLowZone() { @@ -2678,7 +2367,7 @@ public class DisplayModeDirector { } else { vote = null; } - DisplayModeDirector.this.updateVoteLocked(displayId, votePriority, vote); + mVotesStorage.updateVote(displayId, votePriority, vote); } void dumpLocked(PrintWriter pw) { @@ -2704,7 +2393,7 @@ public class DisplayModeDirector { private final String mProximitySensorName = null; private final String mProximitySensorType = Sensor.STRING_TYPE_PROXIMITY; - private final BallotBox mBallotBox; + private final VotesStorage mVotesStorage; private final Context mContext; private final Injector mInjector; @GuardedBy("mSensorObserverLock") @@ -2716,9 +2405,9 @@ public class DisplayModeDirector { @GuardedBy("mSensorObserverLock") private boolean mIsProxActive = false; - SensorObserver(Context context, BallotBox ballotBox, Injector injector) { + SensorObserver(Context context, VotesStorage votesStorage, Injector injector) { mContext = context; - mBallotBox = ballotBox; + mVotesStorage = votesStorage; mInjector = injector; } @@ -2764,7 +2453,7 @@ public class DisplayModeDirector { vote = Vote.forPhysicalRefreshRates(rate.min, rate.max); } } - mBallotBox.vote(displayId, Vote.PRIORITY_PROXIMITY, vote); + mVotesStorage.updateVote(displayId, Vote.PRIORITY_PROXIMITY, vote); } } @@ -2817,7 +2506,7 @@ public class DisplayModeDirector { * DisplayManagerInternal but originate in the display-device-config file. */ public class HbmObserver implements DisplayManager.DisplayListener { - private final BallotBox mBallotBox; + private final VotesStorage mVotesStorage; private final Handler mHandler; private final SparseIntArray mHbmMode = new SparseIntArray(); private final SparseBooleanArray mHbmActive = new SparseBooleanArray(); @@ -2828,10 +2517,10 @@ public class DisplayModeDirector { private DisplayManagerInternal mDisplayManagerInternal; - HbmObserver(Injector injector, BallotBox ballotBox, Handler handler, + HbmObserver(Injector injector, VotesStorage votesStorage, Handler handler, DeviceConfigDisplaySettings displaySettings) { mInjector = injector; - mBallotBox = ballotBox; + mVotesStorage = votesStorage; mHandler = handler; mDeviceConfigDisplaySettings = displaySettings; } @@ -2901,7 +2590,7 @@ public class DisplayModeDirector { @Override public void onDisplayRemoved(int displayId) { - mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, null); + mVotesStorage.updateVote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, null); mHbmMode.delete(displayId); mHbmActive.delete(displayId); } @@ -2968,7 +2657,7 @@ public class DisplayModeDirector { } } - mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, vote); + mVotesStorage.updateVote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, vote); } void dumpLocked(PrintWriter pw) { @@ -3299,8 +2988,4 @@ public class DisplayModeDirector { ServiceManager.getService(Context.THERMAL_SERVICE)); } } - - interface BallotBox { - void vote(int displayId, int priority, Vote vote); - } } diff --git a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java index 8a3b329211cf7..58e15503be7d0 100644 --- a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java +++ b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java @@ -37,7 +37,7 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme DisplayManager.DisplayListener { private static final String TAG = "SkinThermalStatusObserver"; - private final DisplayModeDirector.BallotBox mBallotBox; + private final VotesStorage mVotesStorage; private final DisplayModeDirector.Injector mInjector; private boolean mLoggingEnabled; @@ -52,15 +52,15 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme mThermalThrottlingByDisplay = new SparseArray<>(); SkinThermalStatusObserver(DisplayModeDirector.Injector injector, - DisplayModeDirector.BallotBox ballotBox) { - this(injector, ballotBox, BackgroundThread.getHandler()); + VotesStorage votesStorage) { + this(injector, votesStorage, BackgroundThread.getHandler()); } @VisibleForTesting SkinThermalStatusObserver(DisplayModeDirector.Injector injector, - DisplayModeDirector.BallotBox ballotBox, Handler handler) { + VotesStorage votesStorage, Handler handler) { mInjector = injector; - mBallotBox = ballotBox; + mVotesStorage = votesStorage; mHandler = handler; } @@ -112,8 +112,8 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme public void onDisplayRemoved(int displayId) { synchronized (mThermalObserverLock) { mThermalThrottlingByDisplay.remove(displayId); - mHandler.post(() -> mBallotBox.vote(displayId, - DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE, null)); + mHandler.post(() -> mVotesStorage.updateVote(displayId, + Vote.PRIORITY_SKIN_TEMPERATURE, null)); } if (mLoggingEnabled) { Slog.d(TAG, "Display removed and voted: displayId=" + displayId); @@ -218,11 +218,11 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme SurfaceControl.RefreshRateRange foundRange = findBestMatchingRefreshRateRange(currentStatus, throttlingMap); // if status <= currentStatus not found in the map reset vote - DisplayModeDirector.Vote vote = null; + Vote vote = null; if (foundRange != null) { // otherwise vote with found range - vote = DisplayModeDirector.Vote.forRenderFrameRates(foundRange.min, foundRange.max); + vote = Vote.forRenderFrameRates(foundRange.min, foundRange.max); } - mBallotBox.vote(displayId, DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE, vote); + mVotesStorage.updateVote(displayId, Vote.PRIORITY_SKIN_TEMPERATURE, vote); if (mLoggingEnabled) { Slog.d(TAG, "Voted: vote=" + vote + ", display =" + displayId); } @@ -244,11 +244,11 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme private void fallbackReportThrottlingIfNeeded(int displayId, @Temperature.ThrottlingStatus int currentStatus) { - DisplayModeDirector.Vote vote = null; + Vote vote = null; if (currentStatus >= Temperature.THROTTLING_CRITICAL) { - vote = DisplayModeDirector.Vote.forRenderFrameRates(0f, 60f); + vote = Vote.forRenderFrameRates(0f, 60f); } - mBallotBox.vote(displayId, DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE, vote); + mVotesStorage.updateVote(displayId, Vote.PRIORITY_SKIN_TEMPERATURE, vote); if (mLoggingEnabled) { Slog.d(TAG, "Voted(fallback): vote=" + vote + ", display =" + displayId); } diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java new file mode 100644 index 0000000000000..a42d8f257ddfe --- /dev/null +++ b/services/core/java/com/android/server/display/mode/Vote.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2023 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.mode; + +import android.view.SurfaceControl; + +final class Vote { + // DEFAULT_RENDER_FRAME_RATE votes for render frame rate [0, DEFAULT]. As the lowest + // priority vote, it's overridden by all other considerations. It acts to set a default + // frame rate for a device. + static final int PRIORITY_DEFAULT_RENDER_FRAME_RATE = 0; + + // PRIORITY_FLICKER_REFRESH_RATE votes for a single refresh rate like [60,60], [90,90] or + // null. It is used to set a preferred refresh rate value in case the higher priority votes + // result is a range. + static final int PRIORITY_FLICKER_REFRESH_RATE = 1; + + // High-brightness-mode may need a specific range of refresh-rates to function properly. + static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 2; + + // SETTING_MIN_RENDER_FRAME_RATE is used to propose a lower bound of the render frame rate. + // It votes [minRefreshRate, Float.POSITIVE_INFINITY] + static final int PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE = 3; + + // APP_REQUEST_RENDER_FRAME_RATE_RANGE is used to for internal apps to limit the render + // frame rate in certain cases, mostly to preserve power. + // @see android.view.WindowManager.LayoutParams#preferredMinRefreshRate + // @see android.view.WindowManager.LayoutParams#preferredMaxRefreshRate + // It votes to [preferredMinRefreshRate, preferredMaxRefreshRate]. + static final int PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE = 4; + + // We split the app request into different priorities in case we can satisfy one desire + // without the other. + + // Application can specify preferred refresh rate with below attrs. + // @see android.view.WindowManager.LayoutParams#preferredRefreshRate + // @see android.view.WindowManager.LayoutParams#preferredDisplayModeId + // + // When the app specifies a LayoutParams#preferredDisplayModeId, in addition to the + // refresh rate, it also chooses a preferred size (resolution) as part of the selected + // mode id. The app preference is then translated to APP_REQUEST_BASE_MODE_REFRESH_RATE and + // optionally to APP_REQUEST_SIZE as well, if a mode id was selected. + // The system also forces some apps like denylisted app to run at a lower refresh rate. + // @see android.R.array#config_highRefreshRateBlacklist + // + // When summarizing the votes and filtering the allowed display modes, these votes determine + // which mode id should be the base mode id to be sent to SurfaceFlinger: + // - APP_REQUEST_BASE_MODE_REFRESH_RATE is used to validate the vote summary. If a summary + // includes a base mode refresh rate, but it is not in the refresh rate range, then the + // summary is considered invalid so we could drop a lower priority vote and try again. + // - APP_REQUEST_SIZE is used to filter out display modes of a different size. + // + // The preferred refresh rate is set on the main surface of the app outside of + // DisplayModeDirector. + // @see com.android.server.wm.WindowState#updateFrameRateSelectionPriorityIfNeeded + static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 5; + static final int PRIORITY_APP_REQUEST_SIZE = 6; + + // SETTING_PEAK_RENDER_FRAME_RATE has a high priority and will restrict the bounds of the + // rest of low priority voters. It votes [0, max(PEAK, MIN)] + static final int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 7; + + // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh + // rate to max value (same as for PRIORITY_UDFPS) on lock screen + static final int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 8; + + // For concurrent displays we want to limit refresh rate on all displays + static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 9; + + // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if + // Settings.Global.LOW_POWER_MODE is on. + static final int PRIORITY_LOW_POWER_MODE = 10; + + // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the + // higher priority voters' result is a range, it will fix the rate to a single choice. + // It's used to avoid refresh rate switches in certain conditions which may result in the + // user seeing the display flickering when the switches occur. + static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 11; + + // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL. + static final int PRIORITY_SKIN_TEMPERATURE = 12; + + // The proximity sensor needs the refresh rate to be locked in order to function, so this is + // set to a high priority. + static final int PRIORITY_PROXIMITY = 13; + + // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order + // to function, so this needs to be the highest priority of all votes. + static final int PRIORITY_UDFPS = 14; + + // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and + // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString. + + static final int MIN_PRIORITY = PRIORITY_DEFAULT_RENDER_FRAME_RATE; + static final int MAX_PRIORITY = PRIORITY_UDFPS; + + // The cutoff for the app request refresh rate range. Votes with priorities lower than this + // value will not be considered when constructing the app request refresh rate range. + static final int APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF = + PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE; + + /** + * A value signifying an invalid width or height in a vote. + */ + static final int INVALID_SIZE = -1; + + /** + * The requested width of the display in pixels, or INVALID_SIZE; + */ + public final int width; + /** + * The requested height of the display in pixels, or INVALID_SIZE; + */ + public final int height; + /** + * Information about the refresh rate frame rate ranges DM would like to set the display to. + */ + public final SurfaceControl.RefreshRateRanges refreshRateRanges; + + /** + * Whether refresh rate switching should be disabled (i.e. the refresh rate range is + * a single value). + */ + public final boolean disableRefreshRateSwitching; + + /** + * The preferred refresh rate selected by the app. It is used to validate that the summary + * refresh rate ranges include this value, and are not restricted by a lower priority vote. + */ + public final float appRequestBaseModeRefreshRate; + + static Vote forPhysicalRefreshRates(float minRefreshRate, float maxRefreshRate) { + return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate, 0, + Float.POSITIVE_INFINITY, + minRefreshRate == maxRefreshRate, 0f); + } + + static Vote forRenderFrameRates(float minFrameRate, float maxFrameRate) { + return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, minFrameRate, + maxFrameRate, + false, 0f); + } + + static Vote forSize(int width, int height) { + return new Vote(width, height, 0, Float.POSITIVE_INFINITY, 0, Float.POSITIVE_INFINITY, + false, + 0f); + } + + static Vote forDisableRefreshRateSwitching() { + return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, 0, + Float.POSITIVE_INFINITY, true, + 0f); + } + + static Vote forBaseModeRefreshRate(float baseModeRefreshRate) { + return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, 0, + Float.POSITIVE_INFINITY, false, + baseModeRefreshRate); + } + + private Vote(int width, int height, + float minPhysicalRefreshRate, + float maxPhysicalRefreshRate, + float minRenderFrameRate, + float maxRenderFrameRate, + boolean disableRefreshRateSwitching, + float baseModeRefreshRate) { + this.width = width; + this.height = height; + this.refreshRateRanges = new SurfaceControl.RefreshRateRanges( + new SurfaceControl.RefreshRateRange(minPhysicalRefreshRate, maxPhysicalRefreshRate), + new SurfaceControl.RefreshRateRange(minRenderFrameRate, maxRenderFrameRate)); + this.disableRefreshRateSwitching = disableRefreshRateSwitching; + this.appRequestBaseModeRefreshRate = baseModeRefreshRate; + } + + static String priorityToString(int priority) { + switch (priority) { + case PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE: + return "PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE"; + case PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE: + return "PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE"; + case PRIORITY_APP_REQUEST_SIZE: + return "PRIORITY_APP_REQUEST_SIZE"; + case PRIORITY_DEFAULT_RENDER_FRAME_RATE: + return "PRIORITY_DEFAULT_REFRESH_RATE"; + case PRIORITY_FLICKER_REFRESH_RATE: + return "PRIORITY_FLICKER_REFRESH_RATE"; + case PRIORITY_FLICKER_REFRESH_RATE_SWITCH: + return "PRIORITY_FLICKER_REFRESH_RATE_SWITCH"; + case PRIORITY_HIGH_BRIGHTNESS_MODE: + return "PRIORITY_HIGH_BRIGHTNESS_MODE"; + case PRIORITY_PROXIMITY: + return "PRIORITY_PROXIMITY"; + case PRIORITY_LOW_POWER_MODE: + return "PRIORITY_LOW_POWER_MODE"; + case PRIORITY_SKIN_TEMPERATURE: + return "PRIORITY_SKIN_TEMPERATURE"; + case PRIORITY_UDFPS: + return "PRIORITY_UDFPS"; + case PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE: + return "PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE"; + case PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE: + return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE"; + case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE: + return "PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE"; + case PRIORITY_LAYOUT_LIMITED_FRAME_RATE: + return "PRIORITY_LAYOUT_LIMITED_FRAME_RATE"; + default: + return Integer.toString(priority); + } + } + + @Override + public String toString() { + return "Vote: {" + + "width: " + width + ", height: " + height + + ", refreshRateRanges: " + refreshRateRanges + + ", disableRefreshRateSwitching: " + disableRefreshRateSwitching + + ", appRequestBaseModeRefreshRate: " + appRequestBaseModeRefreshRate + "}"; + } +} diff --git a/services/core/java/com/android/server/display/mode/VotesStorage.java b/services/core/java/com/android/server/display/mode/VotesStorage.java new file mode 100644 index 0000000000000..dadcebe8d8a1b --- /dev/null +++ b/services/core/java/com/android/server/display/mode/VotesStorage.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2023 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.mode; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.io.PrintWriter; + +class VotesStorage { + private static final String TAG = "VotesStorage"; + // Special ID used to indicate that given vote is to be applied globally, rather than to a + // specific display. + private static final int GLOBAL_ID = -1; + + private boolean mLoggingEnabled; + + private final Listener mListener; + + private final Object mStorageLock = new Object(); + // A map from the display ID to the collection of votes and their priority. The latter takes + // the form of another map from the priority to the vote itself so that each priority is + // guaranteed to have exactly one vote, which is also easily and efficiently replaceable. + @GuardedBy("mStorageLock") + private final SparseArray> mVotesByDisplay = new SparseArray<>(); + + VotesStorage(@NonNull Listener listener) { + mListener = listener; + } + /** sets logging enabled/disabled for this class */ + void setLoggingEnabled(boolean loggingEnabled) { + mLoggingEnabled = loggingEnabled; + } + /** + * gets all votes for specific display, note that global display votes are also added to result + */ + @NonNull + SparseArray getVotes(int displayId) { + SparseArray votesLocal; + SparseArray globalVotesLocal; + synchronized (mStorageLock) { + SparseArray displayVotes = mVotesByDisplay.get(displayId); + votesLocal = displayVotes != null ? displayVotes.clone() : new SparseArray<>(); + SparseArray globalVotes = mVotesByDisplay.get(GLOBAL_ID); + globalVotesLocal = globalVotes != null ? globalVotes.clone() : new SparseArray<>(); + } + for (int i = 0; i < globalVotesLocal.size(); i++) { + int priority = globalVotesLocal.keyAt(i); + if (!votesLocal.contains(priority)) { + votesLocal.put(priority, globalVotesLocal.valueAt(i)); + } + } + return votesLocal; + } + + /** updates vote storage for all displays */ + void updateGlobalVote(int priority, @Nullable Vote vote) { + updateVote(GLOBAL_ID, priority, vote); + } + + /** updates vote storage */ + void updateVote(int displayId, int priority, @Nullable Vote vote) { + if (mLoggingEnabled) { + Slog.i(TAG, "updateVoteLocked(displayId=" + displayId + + ", priority=" + Vote.priorityToString(priority) + + ", vote=" + vote + ")"); + } + if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) { + Slog.w(TAG, "Received a vote with an invalid priority, ignoring:" + + " priority=" + Vote.priorityToString(priority) + + ", vote=" + vote); + return; + } + SparseArray votes; + synchronized (mStorageLock) { + if (mVotesByDisplay.contains(displayId)) { + votes = mVotesByDisplay.get(displayId); + } else { + votes = new SparseArray<>(); + mVotesByDisplay.put(displayId, votes); + } + if (vote != null) { + votes.put(priority, vote); + } else { + votes.remove(priority); + } + } + if (mLoggingEnabled) { + Slog.i(TAG, "Updated votes for display=" + displayId + " votes=" + votes); + } + mListener.onChanged(); + } + + /** dump class values, for debugging */ + void dump(@NonNull PrintWriter pw) { + SparseArray> votesByDisplayLocal = new SparseArray<>(); + synchronized (mStorageLock) { + for (int i = 0; i < mVotesByDisplay.size(); i++) { + votesByDisplayLocal.put(mVotesByDisplay.keyAt(i), + mVotesByDisplay.valueAt(i).clone()); + } + } + pw.println(" mVotesByDisplay:"); + for (int i = 0; i < votesByDisplayLocal.size(); i++) { + SparseArray votes = votesByDisplayLocal.valueAt(i); + if (votes.size() == 0) { + continue; + } + pw.println(" " + votesByDisplayLocal.keyAt(i) + ":"); + for (int p = Vote.MAX_PRIORITY; p >= Vote.MIN_PRIORITY; p--) { + Vote vote = votes.get(p); + if (vote == null) { + continue; + } + pw.println(" " + Vote.priorityToString(p) + " -> " + vote); + } + } + } + + @VisibleForTesting + void injectVotesByDisplay(SparseArray> votesByDisplay) { + synchronized (mStorageLock) { + mVotesByDisplay.clear(); + for (int i = 0; i < votesByDisplay.size(); i++) { + mVotesByDisplay.put(votesByDisplay.keyAt(i), votesByDisplay.valueAt(i)); + } + } + } + + interface Listener { + void onChanged(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index 1b393c45d1cd5..e49225216b6b9 100644 --- a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -27,7 +27,7 @@ import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_R import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE; import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE; -import static com.android.server.display.mode.DisplayModeDirector.Vote.INVALID_SIZE; +import static com.android.server.display.mode.Vote.INVALID_SIZE; import static com.google.common.truth.Truth.assertThat; @@ -94,7 +94,6 @@ import com.android.server.display.DisplayDeviceConfig; import com.android.server.display.TestUtils; import com.android.server.display.mode.DisplayModeDirector.BrightnessObserver; import com.android.server.display.mode.DisplayModeDirector.DesiredDisplayModeSpecs; -import com.android.server.display.mode.DisplayModeDirector.Vote; import com.android.server.sensors.SensorManagerInternal; import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener; import com.android.server.statusbar.StatusBarManagerInternal; @@ -224,8 +223,7 @@ public class DisplayModeDirectorTest { assertThat(modeSpecs.appRequest.render.min).isEqualTo(0f); assertThat(modeSpecs.appRequest.render.max).isEqualTo(Float.POSITIVE_INFINITY); - int numPriorities = - DisplayModeDirector.Vote.MAX_PRIORITY - DisplayModeDirector.Vote.MIN_PRIORITY + 1; + int numPriorities = Vote.MAX_PRIORITY - Vote.MIN_PRIORITY + 1; // Ensure vote priority works as expected. As we add new votes with higher priority, they // should take precedence over lower priority votes. diff --git a/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java b/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java index 13540d60b9305..9ab6ee5bd230f 100644 --- a/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java +++ b/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java @@ -18,7 +18,6 @@ package com.android.server.display.mode; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import android.hardware.display.DisplayManager; @@ -57,7 +56,7 @@ public class SkinThermalStatusObserverTest { private RegisteringFakesInjector mInjector = new RegisteringFakesInjector(); private final TestHandler mHandler = new TestHandler(null); - private final FakeVoteStorage mStorage = new FakeVoteStorage(); + private final VotesStorage mStorage = new VotesStorage(() -> {}); @Before public void setUp() { @@ -92,28 +91,26 @@ public class SkinThermalStatusObserverTest { public void testNotifyWithDefaultVotesForCritical() { // GIVEN 2 displays with no thermalThrottling config mObserver.observe(); - assertEquals(0, mStorage.mVoteRegistry.size()); + assertEquals(0, mStorage.getVotes(DISPLAY_ID).size()); + assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size()); // WHEN thermal sensor notifies CRITICAL mObserver.notifyThrottling(createTemperature(Temperature.THROTTLING_CRITICAL)); mHandler.flush(); // THEN 2 votes are added to storage with (0,60) render refresh rate(default behaviour) - assertEquals(2, mStorage.mVoteRegistry.size()); - - SparseArray displayVotes = mStorage.mVoteRegistry.get(DISPLAY_ID); + SparseArray displayVotes = mStorage.getVotes(DISPLAY_ID); assertEquals(1, displayVotes.size()); - DisplayModeDirector.Vote vote = displayVotes.get( - DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE); + Vote vote = displayVotes.get( + Vote.PRIORITY_SKIN_TEMPERATURE); assertEquals(0, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE); assertEquals(60, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE); - SparseArray otherDisplayVotes = mStorage.mVoteRegistry.get( - DISPLAY_ID_OTHER); + SparseArray otherDisplayVotes = mStorage.getVotes(DISPLAY_ID_OTHER); assertEquals(1, otherDisplayVotes.size()); - vote = otherDisplayVotes.get(DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE); + vote = otherDisplayVotes.get(Vote.PRIORITY_SKIN_TEMPERATURE); assertEquals(0, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE); assertEquals(60, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE); } @@ -122,25 +119,29 @@ public class SkinThermalStatusObserverTest { public void testNotifyWithDefaultVotesChangeFromCriticalToSevere() { // GIVEN 2 displays with no thermalThrottling config AND temperature level CRITICAL mObserver.observe(); - assertEquals(0, mStorage.mVoteRegistry.size()); + assertEquals(0, mStorage.getVotes(DISPLAY_ID).size()); + assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size()); mObserver.notifyThrottling(createTemperature(Temperature.THROTTLING_CRITICAL)); // WHEN thermal sensor notifies SEVERE mObserver.notifyThrottling(createTemperature(Temperature.THROTTLING_SEVERE)); mHandler.flush(); // THEN all votes with PRIORITY_SKIN_TEMPERATURE are removed from the storage - assertEquals(0, mStorage.mVoteRegistry.size()); + assertEquals(0, mStorage.getVotes(DISPLAY_ID).size()); + assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size()); } @Test public void testNotifyWithDefaultVotesForSevere() { // GIVEN 2 displays with no thermalThrottling config mObserver.observe(); - assertEquals(0, mStorage.mVoteRegistry.size()); + assertEquals(0, mStorage.getVotes(DISPLAY_ID).size()); + assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size()); // WHEN thermal sensor notifies CRITICAL mObserver.notifyThrottling(createTemperature(Temperature.THROTTLING_SEVERE)); mHandler.flush(); // THEN nothing is added to the storage - assertEquals(0, mStorage.mVoteRegistry.size()); + assertEquals(0, mStorage.getVotes(DISPLAY_ID).size()); + assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size()); } @Test @@ -155,18 +156,20 @@ public class SkinThermalStatusObserverTest { mObserver = new SkinThermalStatusObserver(mInjector, mStorage, mHandler); mObserver.observe(); mObserver.onDisplayChanged(DISPLAY_ID); - assertEquals(0, mStorage.mVoteRegistry.size()); + assertEquals(0, mStorage.getVotes(DISPLAY_ID).size()); + assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size()); // WHEN thermal sensor notifies temperature above configured mObserver.notifyThrottling(createTemperature(Temperature.THROTTLING_SEVERE)); mHandler.flush(); // THEN vote with refreshRate from config is added to the storage - assertEquals(1, mStorage.mVoteRegistry.size()); - SparseArray displayVotes = mStorage.mVoteRegistry.get(DISPLAY_ID); + assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size()); + + SparseArray displayVotes = mStorage.getVotes(DISPLAY_ID); assertEquals(1, displayVotes.size()); - DisplayModeDirector.Vote vote = displayVotes.get( - DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE); + Vote vote = displayVotes.get(Vote.PRIORITY_SKIN_TEMPERATURE); assertEquals(90, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE); assertEquals(120, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE); + assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size()); } @Test @@ -178,14 +181,13 @@ public class SkinThermalStatusObserverTest { mObserver.onDisplayAdded(DISPLAY_ID_ADDED); mHandler.flush(); // THEN 3rd vote is added to storage with (0,60) render refresh rate(default behaviour) - assertEquals(3, mStorage.mVoteRegistry.size()); + assertEquals(1, mStorage.getVotes(DISPLAY_ID).size()); + assertEquals(1, mStorage.getVotes(DISPLAY_ID_OTHER).size()); + assertEquals(1, mStorage.getVotes(DISPLAY_ID_ADDED).size()); - SparseArray displayVotes = mStorage.mVoteRegistry.get( - DISPLAY_ID_ADDED); - assertEquals(1, displayVotes.size()); + SparseArray displayVotes = mStorage.getVotes(DISPLAY_ID_ADDED); - DisplayModeDirector.Vote vote = displayVotes.get( - DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE); + Vote vote = displayVotes.get(Vote.PRIORITY_SKIN_TEMPERATURE); assertEquals(0, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE); assertEquals(60, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE); } @@ -200,9 +202,9 @@ public class SkinThermalStatusObserverTest { mObserver.onDisplayRemoved(DISPLAY_ID_ADDED); mHandler.flush(); // THEN there are 2 votes in registry - assertEquals(2, mStorage.mVoteRegistry.size()); - assertNotNull(mStorage.mVoteRegistry.get(DISPLAY_ID)); - assertNotNull(mStorage.mVoteRegistry.get(DISPLAY_ID_OTHER)); + assertEquals(1, mStorage.getVotes(DISPLAY_ID).size()); + assertEquals(1, mStorage.getVotes(DISPLAY_ID_OTHER).size()); + assertEquals(0, mStorage.getVotes(DISPLAY_ID_ADDED).size()); } private static Temperature createTemperature(@Temperature.ThrottlingStatus int status) { @@ -259,27 +261,4 @@ public class SkinThermalStatusObserverTest { return false; } } - - - private static class FakeVoteStorage implements DisplayModeDirector.BallotBox { - private final SparseArray> mVoteRegistry = - new SparseArray<>(); - - @Override - public void vote(int displayId, int priority, DisplayModeDirector.Vote vote) { - SparseArray votesPerDisplay = mVoteRegistry.get(displayId); - if (votesPerDisplay == null) { - votesPerDisplay = new SparseArray<>(); - mVoteRegistry.put(displayId, votesPerDisplay); - } - if (vote == null) { - votesPerDisplay.remove(priority); - } else { - votesPerDisplay.put(priority, vote); - } - if (votesPerDisplay.size() == 0) { - mVoteRegistry.remove(displayId); - } - } - } } diff --git a/services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java b/services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java new file mode 100644 index 0000000000000..287fdd5c344bf --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2023 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.mode; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.util.SparseArray; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class VotesStorageTest { + private static final int DISPLAY_ID = 100; + private static final int PRIORITY = Vote.PRIORITY_APP_REQUEST_SIZE; + private static final Vote VOTE = Vote.forDisableRefreshRateSwitching(); + private static final int DISPLAY_ID_OTHER = 101; + private static final int PRIORITY_OTHER = Vote.PRIORITY_FLICKER_REFRESH_RATE; + private static final Vote VOTE_OTHER = Vote.forBaseModeRefreshRate(10f); + + @Mock + public VotesStorage.Listener mVotesListener; + + private VotesStorage mVotesStorage; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mVotesStorage = new VotesStorage(mVotesListener); + } + + @Test + public void addsVoteToStorage() { + // WHEN updateVote is called + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE); + // THEN vote is added to the storage + SparseArray votes = mVotesStorage.getVotes(DISPLAY_ID); + assertThat(votes.size()).isEqualTo(1); + assertThat(votes.get(PRIORITY)).isEqualTo(VOTE); + assertThat(mVotesStorage.getVotes(DISPLAY_ID_OTHER).size()).isEqualTo(0); + } + + @Test + public void notifiesVoteListenerIfVoteAdded() { + // WHEN updateVote is called + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE); + // THEN listener is notified + verify(mVotesListener).onChanged(); + } + + @Test + public void addsAnotherVoteToStorageWithDifferentPriority() { + // GIVEN vote storage with one vote + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE); + // WHEN updateVote is called with other priority + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY_OTHER, VOTE_OTHER); + // THEN another vote is added to storage + SparseArray votes = mVotesStorage.getVotes(DISPLAY_ID); + assertThat(votes.size()).isEqualTo(2); + assertThat(votes.get(PRIORITY)).isEqualTo(VOTE); + assertThat(votes.get(PRIORITY_OTHER)).isEqualTo(VOTE_OTHER); + assertThat(mVotesStorage.getVotes(DISPLAY_ID_OTHER).size()).isEqualTo(0); + } + + @Test + public void replacesVoteInStorageForSamePriority() { + // GIVEN vote storage with one vote + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE); + // WHEN updateVote is called with same priority + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE_OTHER); + // THEN vote is replaced by other vote + SparseArray votes = mVotesStorage.getVotes(DISPLAY_ID); + assertThat(votes.size()).isEqualTo(1); + assertThat(votes.get(PRIORITY)).isEqualTo(VOTE_OTHER); + assertThat(mVotesStorage.getVotes(DISPLAY_ID_OTHER).size()).isEqualTo(0); + } + + @Test + public void removesVoteInStorageForSamePriority() { + // GIVEN vote storage with one vote + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE); + // WHEN update is called with same priority and null vote + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, null); + // THEN vote removed from the storage + assertThat(mVotesStorage.getVotes(DISPLAY_ID).size()).isEqualTo(0); + assertThat(mVotesStorage.getVotes(DISPLAY_ID_OTHER).size()).isEqualTo(0); + } + + @Test + public void addsGlobalDisplayVoteToStorage() { + // WHEN updateGlobalVote is called + mVotesStorage.updateGlobalVote(PRIORITY, VOTE); + // THEN it is added to the storage for every display + SparseArray votes = mVotesStorage.getVotes(DISPLAY_ID); + assertThat(votes.size()).isEqualTo(1); + assertThat(votes.get(PRIORITY)).isEqualTo(VOTE); + votes = mVotesStorage.getVotes(DISPLAY_ID_OTHER); + assertThat(votes.size()).isEqualTo(1); + assertThat(votes.get(PRIORITY)).isEqualTo(VOTE); + } + + @Test + public void ignoresVotesWithLowerThanMinPriority() { + // WHEN updateVote is called with invalid (lower than min) priority + mVotesStorage.updateVote(DISPLAY_ID, Vote.MIN_PRIORITY - 1, VOTE); + // THEN vote is not added to the storage AND listener not notified + assertThat(mVotesStorage.getVotes(DISPLAY_ID).size()).isEqualTo(0); + verify(mVotesListener, never()).onChanged(); + } + + @Test + public void ignoresVotesWithGreaterThanMaxPriority() { + // WHEN updateVote is called with invalid (greater than max) priority + mVotesStorage.updateVote(DISPLAY_ID, Vote.MAX_PRIORITY + 1, VOTE); + // THEN vote is not added to the storage AND listener not notified + assertThat(mVotesStorage.getVotes(DISPLAY_ID).size()).isEqualTo(0); + verify(mVotesListener, never()).onChanged(); + } +}