Merge "Extracting VotesStorage to separate class with local locking" into udc-dev

This commit is contained in:
Oleg Petšjonkin
2023-04-24 12:51:56 +00:00
committed by Android (Google) Code Review
7 changed files with 619 additions and 425 deletions

View File

@@ -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<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 @@ 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<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 @@ public class DisplayModeDirector {
@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 @@ public class DisplayModeDirector {
@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 @@ 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<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 @@ 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<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 @@ public class DisplayModeDirector {
}
}
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 @@ public class DisplayModeDirector {
@VisibleForTesting
void injectVotesByDisplay(SparseArray<SparseArray<Vote>> 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);
}
}

View File

@@ -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);
}

View File

@@ -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 + "}";
}
}

View File

@@ -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();
}
}

View File

@@ -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.

View File

@@ -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<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 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<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 @@ 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<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 @@ 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<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);
}
}
}
}

View File

@@ -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();
}
}