Loading services/core/java/com/android/server/display/mode/DisplayModeDirector.java +41 −356 File changed.Preview size limit exceeded, changes collapsed. Show changes services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java +13 −13 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; } Loading Loading @@ -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); Loading Loading @@ -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); } Loading @@ -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); } Loading services/core/java/com/android/server/display/mode/Vote.java 0 → 100644 +237 −0 Original line number Diff line number Diff line /* * 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 + "}"; } } services/core/java/com/android/server/display/mode/VotesStorage.java 0 → 100644 +152 −0 Original line number Diff line number Diff line /* * 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(); } } services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +2 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. Loading Loading
services/core/java/com/android/server/display/mode/DisplayModeDirector.java +41 −356 File changed.Preview size limit exceeded, changes collapsed. Show changes
services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java +13 −13 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; } Loading Loading @@ -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); Loading Loading @@ -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); } Loading @@ -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); } Loading
services/core/java/com/android/server/display/mode/Vote.java 0 → 100644 +237 −0 Original line number Diff line number Diff line /* * 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 + "}"; } }
services/core/java/com/android/server/display/mode/VotesStorage.java 0 → 100644 +152 −0 Original line number Diff line number Diff line /* * 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(); } }
services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +2 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. Loading