Extracting VotesStorage to separate class with local locking
Bug: b/266789924
Test: atest com.android.server.display.mode
Change-Id: I8fb46b393a96db6476db58a025cf881516754bf9
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 03b49f0..86ba1dd 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 @@
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 @@
@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<SparseArray<Vote>> mVotesByDisplay;
// A map from the display ID to the supported modes on that display.
private SparseArray<Display.Mode[]> mSupportedModesByDisplay;
// A map from the display ID to the default mode of that display.
@@ -146,6 +138,8 @@
private final boolean mSupportsFrameRateOverride;
+ private final VotesStorage mVotesStorage;
+
/**
* The allowed refresh rate switching type. This is used by SurfaceFlinger.
*/
@@ -161,7 +155,6 @@
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 @@
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 @@
mLoggingEnabled = loggingEnabled;
mBrightnessObserver.setLoggingEnabled(loggingEnabled);
mSkinThermalStatusObserver.setLoggingEnabled(loggingEnabled);
- }
-
- @NonNull
- private SparseArray<Vote> getVotesLocked(int displayId) {
- SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId);
- final SparseArray<Vote> votes;
- if (displayVotes != null) {
- votes = displayVotes.clone();
- } else {
- votes = new SparseArray<>();
- }
-
- SparseArray<Vote> 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 @@
@NonNull
public DesiredDisplayModeSpecs getDesiredDisplayModeSpecs(int displayId) {
synchronized (mLock) {
- SparseArray<Vote> votes = getVotesLocked(displayId);
+ SparseArray<Vote> 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 @@
@VisibleForTesting
@Nullable
Vote getVote(int displayId, int priority) {
- synchronized (mLock) {
- SparseArray<Vote> votes = getVotesLocked(displayId);
- return votes.get(priority);
- }
+ SparseArray<Vote> votes = mVotesStorage.getVotes(displayId);
+ return votes.get(priority);
}
/**
@@ -806,18 +771,6 @@
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<Vote> 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 @@
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<Vote> 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 @@
}
}
- private SparseArray<Vote> getOrCreateVotesByDisplay(int displayId) {
- if (mVotesByDisplay.indexOfKey(displayId) >= 0) {
- return mVotesByDisplay.get(displayId);
- } else {
- SparseArray<Vote> 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 @@
@VisibleForTesting
void injectVotesByDisplay(SparseArray<SparseArray<Vote>> votesByDisplay) {
- mVotesByDisplay = votesByDisplay;
+ mVotesStorage.injectVotesByDisplay(votesByDisplay);
}
@VisibleForTesting
@@ -1157,225 +1066,6 @@
}
@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 =
Settings.System.getUriFor(Settings.System.SMOOTH_DISPLAY);
@@ -1510,7 +1200,7 @@
} 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 @@
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 @@
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 @@
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 @@
// 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() {
@@ -1769,7 +1457,7 @@
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) {
@@ -2091,8 +1779,8 @@
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);
}
}
}
@@ -2417,8 +2105,9 @@
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() {
@@ -2687,7 +2376,7 @@
} else {
vote = null;
}
- DisplayModeDirector.this.updateVoteLocked(displayId, votePriority, vote);
+ mVotesStorage.updateVote(displayId, votePriority, vote);
}
void dumpLocked(PrintWriter pw) {
@@ -2713,7 +2402,7 @@
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")
@@ -2725,9 +2414,9 @@
@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;
}
@@ -2775,7 +2464,7 @@
vote = Vote.forPhysicalRefreshRates(rate.min, rate.max);
}
}
- mBallotBox.vote(displayId, Vote.PRIORITY_PROXIMITY, vote);
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_PROXIMITY, vote);
}
}
@@ -2828,7 +2517,7 @@
* 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();
@@ -2839,10 +2528,10 @@
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;
}
@@ -2912,7 +2601,7 @@
@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);
}
@@ -2979,7 +2668,7 @@
}
}
- mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, vote);
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, vote);
}
void dumpLocked(PrintWriter pw) {
@@ -3291,8 +2980,4 @@
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 c04735d..d2822ca 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 @@
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 @@
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 @@
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 @@
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 @@
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 0000000..a42d8f2
--- /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 0000000..dadcebe
--- /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<SparseArray<Vote>> 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<Vote> getVotes(int displayId) {
+ SparseArray<Vote> votesLocal;
+ SparseArray<Vote> globalVotesLocal;
+ synchronized (mStorageLock) {
+ SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId);
+ votesLocal = displayVotes != null ? displayVotes.clone() : new SparseArray<>();
+ SparseArray<Vote> 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<Vote> 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<SparseArray<Vote>> 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<Vote> 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<SparseArray<Vote>> 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 42c1fd9..1d47b32 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_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.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;
@@ -223,8 +222,7 @@
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 fd1889c..487d752 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 @@
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 @@
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 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<DisplayModeDirector.Vote> displayVotes = mStorage.mVoteRegistry.get(DISPLAY_ID);
+ SparseArray<Vote> 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<DisplayModeDirector.Vote> otherDisplayVotes = mStorage.mVoteRegistry.get(
- DISPLAY_ID_OTHER);
+ SparseArray<Vote> 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 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 @@
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<DisplayModeDirector.Vote> displayVotes = mStorage.mVoteRegistry.get(DISPLAY_ID);
+ assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size());
+
+ SparseArray<Vote> 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 @@
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<DisplayModeDirector.Vote> displayVotes = mStorage.mVoteRegistry.get(
- DISPLAY_ID_ADDED);
- assertEquals(1, displayVotes.size());
+ SparseArray<Vote> 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 @@
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 @@
return false;
}
}
-
-
- private static class FakeVoteStorage implements DisplayModeDirector.BallotBox {
- private final SparseArray<SparseArray<DisplayModeDirector.Vote>> mVoteRegistry =
- new SparseArray<>();
-
- @Override
- public void vote(int displayId, int priority, DisplayModeDirector.Vote vote) {
- SparseArray<DisplayModeDirector.Vote> 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 0000000..287fdd5
--- /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<Vote> 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<Vote> 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<Vote> 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<Vote> 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();
+ }
+}