Merge "Adding refreshRate thermal throttling configuration" into udc-dev

This commit is contained in:
Oleg Petšjonkin
2023-03-03 12:42:42 +00:00
committed by Android (Google) Code Review
19 changed files with 867 additions and 78 deletions

View File

@@ -24,6 +24,7 @@ import static android.view.DisplayInfoProto.LOGICAL_HEIGHT;
import static android.view.DisplayInfoProto.LOGICAL_WIDTH;
import static android.view.DisplayInfoProto.NAME;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.compat.annotation.UnsupportedAppUsage;
@@ -37,6 +38,7 @@ import android.os.Parcelable;
import android.os.Process;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import com.android.internal.display.BrightnessSynchronizer;
@@ -348,6 +350,12 @@ public final class DisplayInfo implements Parcelable {
*/
public float hdrSdrRatio = Float.NaN;
/**
* RefreshRateRange limitation for @Temperature.ThrottlingStatus
*/
@NonNull
public SparseArray<SurfaceControl.RefreshRateRange> refreshRateThermalThrottling =
new SparseArray<>();
public static final @android.annotation.NonNull Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() {
@Override
@@ -425,7 +433,8 @@ public final class DisplayInfo implements Parcelable {
&& installOrientation == other.installOrientation
&& Objects.equals(displayShape, other.displayShape)
&& Objects.equals(layoutLimitedRefreshRate, other.layoutLimitedRefreshRate)
&& BrightnessSynchronizer.floatEquals(hdrSdrRatio, other.hdrSdrRatio);
&& BrightnessSynchronizer.floatEquals(hdrSdrRatio, other.hdrSdrRatio)
&& refreshRateThermalThrottling.contentEquals(other.refreshRateThermalThrottling);
}
@Override
@@ -482,6 +491,7 @@ public final class DisplayInfo implements Parcelable {
displayShape = other.displayShape;
layoutLimitedRefreshRate = other.layoutLimitedRefreshRate;
hdrSdrRatio = other.hdrSdrRatio;
refreshRateThermalThrottling = other.refreshRateThermalThrottling;
}
public void readFromParcel(Parcel source) {
@@ -544,6 +554,8 @@ public final class DisplayInfo implements Parcelable {
displayShape = source.readTypedObject(DisplayShape.CREATOR);
layoutLimitedRefreshRate = source.readTypedObject(SurfaceControl.RefreshRateRange.CREATOR);
hdrSdrRatio = source.readFloat();
refreshRateThermalThrottling = source.readSparseArray(null,
SurfaceControl.RefreshRateRange.class);
}
@Override
@@ -604,6 +616,7 @@ public final class DisplayInfo implements Parcelable {
dest.writeTypedObject(displayShape, flags);
dest.writeTypedObject(layoutLimitedRefreshRate, flags);
dest.writeFloat(hdrSdrRatio);
dest.writeSparseArray(refreshRateThermalThrottling);
}
@Override
@@ -871,6 +884,8 @@ public final class DisplayInfo implements Parcelable {
} else {
sb.append(hdrSdrRatio);
}
sb.append(", refreshRateThermalThrottling ");
sb.append(refreshRateThermalThrottling);
sb.append("}");
return sb.toString();
}

View File

@@ -138,6 +138,8 @@ class DeviceStateToLayoutMap {
display.setPosition(POSITION_UNKNOWN);
}
display.setRefreshRateZoneId(d.getRefreshRateZoneId());
display.setRefreshRateThermalThrottlingMapId(
d.getRefreshRateThermalThrottlingMapId());
}
}
} catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {

View File

@@ -31,6 +31,7 @@ import android.text.TextUtils;
import android.util.MathUtils;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Spline;
import android.view.DisplayAddress;
import android.view.SurfaceControl;
@@ -54,6 +55,8 @@ import com.android.server.display.config.NitsMap;
import com.android.server.display.config.Point;
import com.android.server.display.config.RefreshRateConfigs;
import com.android.server.display.config.RefreshRateRange;
import com.android.server.display.config.RefreshRateThrottlingMap;
import com.android.server.display.config.RefreshRateThrottlingPoint;
import com.android.server.display.config.RefreshRateZone;
import com.android.server.display.config.SdrHdrRatioMap;
import com.android.server.display.config.SdrHdrRatioPoint;
@@ -149,9 +152,26 @@ import javax.xml.datatype.DatatypeConfigurationException;
* <brightness>0.005</brightness>
* </brightnessThrottlingPoint>
* </concurrentDisplaysBrightnessThrottlingMap>
* <refreshRateThrottlingMap>
* <refreshRateThrottlingPoint>
* <thermalStatus>critical</thermalStatus>
* <refreshRateRange>
* <minimum>0</minimum>
* <maximum>60</maximum>
* </refreshRateRange>
* </refreshRateThrottlingPoint>
* </refreshRateThrottlingMap>
* </thermalThrottling>
*
* <refreshRate>
* <refreshRateZoneProfiles>
* <refreshRateZoneProfile id="concurrent">
* <refreshRateRange>
* <minimum>60</minimum>
* <maximum>60</maximum>
* </refreshRateRange>
* </refreshRateZoneProfile>
* </refreshRateZoneProfiles>
* <defaultRefreshRateInHbmHdr>75</defaultRefreshRateInHbmHdr>
* <defaultRefreshRateInHbmSunlight>75</defaultRefreshRateInHbmSunlight>
* <lowerBlockingZoneConfigs>
@@ -417,7 +437,7 @@ public class DisplayDeviceConfig {
public static final String QUIRK_CAN_SET_BRIGHTNESS_VIA_HWC = "canSetBrightnessViaHwc";
static final String DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID = "default";
static final String DEFAULT_ID = "default";
private static final float BRIGHTNESS_DEFAULT = 0.5f;
private static final String ETC_DIR = "etc";
@@ -662,7 +682,11 @@ public class DisplayDeviceConfig {
private int[] mHighDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
private int[] mHighAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
private Map<String, BrightnessThrottlingData> mBrightnessThrottlingDataMap = new HashMap();
private final Map<String, BrightnessThrottlingData> mBrightnessThrottlingDataMap =
new HashMap<>();
private final Map<String, SparseArray<SurfaceControl.RefreshRateRange>>
mRefreshRateThrottlingMap = new HashMap<>();
@Nullable
private HostUsiVersion mHostUsiVersion;
@@ -1314,6 +1338,17 @@ public class DisplayDeviceConfig {
return BrightnessThrottlingData.create(mBrightnessThrottlingDataMap.get(id));
}
/**
* @param id - throttling data id or null for default
* @return refresh rate throttling configuration
*/
@Nullable
public SparseArray<SurfaceControl.RefreshRateRange> getRefreshRateThrottlingData(
@Nullable String id) {
String key = id == null ? DEFAULT_ID : id;
return mRefreshRateThrottlingMap.get(key);
}
/**
* @return Auto brightness darkening light debounce
*/
@@ -1552,6 +1587,8 @@ public class DisplayDeviceConfig {
+ ", mRefreshRateZoneProfiles= " + mRefreshRateZoneProfiles
+ ", mDefaultRefreshRateInHbmHdr= " + mDefaultRefreshRateInHbmHdr
+ ", mDefaultRefreshRateInHbmSunlight= " + mDefaultRefreshRateInHbmSunlight
+ ", mRefreshRateThrottlingMap= " + mRefreshRateThrottlingMap
+ "\n"
+ ", mLowDisplayBrightnessThresholds= "
+ Arrays.toString(mLowDisplayBrightnessThresholds)
+ ", mLowAmbientBrightnessThresholds= "
@@ -1613,7 +1650,7 @@ public class DisplayDeviceConfig {
loadBrightnessDefaultFromDdcXml(config);
loadBrightnessConstraintsFromConfigXml();
loadBrightnessMap(config);
loadBrightnessThrottlingMaps(config);
loadThermalThrottlingConfig(config);
loadHighBrightnessModeData(config);
loadQuirks(config);
loadBrightnessRamps(config);
@@ -1823,13 +1860,17 @@ public class DisplayDeviceConfig {
return Spline.createSpline(nits, ratios);
}
private void loadBrightnessThrottlingMaps(DisplayConfiguration config) {
private void loadThermalThrottlingConfig(DisplayConfiguration config) {
final ThermalThrottling throttlingConfig = config.getThermalThrottling();
if (throttlingConfig == null) {
Slog.i(TAG, "No thermal throttling config found");
return;
}
loadBrightnessThrottlingMaps(throttlingConfig);
loadRefreshRateThermalThrottlingMap(throttlingConfig);
}
private void loadBrightnessThrottlingMaps(ThermalThrottling throttlingConfig) {
final List<BrightnessThrottlingMap> maps = throttlingConfig.getBrightnessThrottlingMap();
if (maps == null || maps.isEmpty()) {
Slog.i(TAG, "No brightness throttling map found");
@@ -1855,7 +1896,7 @@ public class DisplayDeviceConfig {
}
if (!badConfig) {
String id = map.getId() == null ? DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID
String id = map.getId() == null ? DEFAULT_ID
: map.getId();
if (mBrightnessThrottlingDataMap.containsKey(id)) {
throw new RuntimeException("Brightness throttling data with ID " + id
@@ -1867,6 +1908,57 @@ public class DisplayDeviceConfig {
}
}
private void loadRefreshRateThermalThrottlingMap(ThermalThrottling throttlingConfig) {
List<RefreshRateThrottlingMap> maps = throttlingConfig.getRefreshRateThrottlingMap();
if (maps == null || maps.isEmpty()) {
Slog.w(TAG, "RefreshRateThrottling: map not found");
return;
}
for (RefreshRateThrottlingMap map : maps) {
List<RefreshRateThrottlingPoint> points = map.getRefreshRateThrottlingPoint();
String id = map.getId() == null ? DEFAULT_ID : map.getId();
if (points == null || points.isEmpty()) {
// Expected at lease 1 throttling point for each map
Slog.w(TAG, "RefreshRateThrottling: points not found for mapId=" + id);
continue;
}
if (mRefreshRateThrottlingMap.containsKey(id)) {
Slog.wtf(TAG, "RefreshRateThrottling: map already exists, mapId=" + id);
continue;
}
SparseArray<SurfaceControl.RefreshRateRange> refreshRates = new SparseArray<>();
for (RefreshRateThrottlingPoint point : points) {
ThermalStatus status = point.getThermalStatus();
if (!thermalStatusIsValid(status)) {
Slog.wtf(TAG,
"RefreshRateThrottling: Invalid thermalStatus=" + status.getRawName()
+ ",mapId=" + id);
continue;
}
int thermalStatusInt = convertThermalStatus(status);
if (refreshRates.contains(thermalStatusInt)) {
Slog.wtf(TAG, "RefreshRateThrottling: thermalStatus=" + status.getRawName()
+ " is already in the map, mapId=" + id);
continue;
}
refreshRates.put(thermalStatusInt, new SurfaceControl.RefreshRateRange(
point.getRefreshRateRange().getMinimum().floatValue(),
point.getRefreshRateRange().getMaximum().floatValue()
));
}
if (refreshRates.size() == 0) {
Slog.w(TAG, "RefreshRateThrottling: no valid throttling points fond for map, mapId="
+ id);
continue;
}
mRefreshRateThrottlingMap.put(id, refreshRates);
}
}
private void loadRefreshRateSetting(DisplayConfiguration config) {
final RefreshRateConfigs refreshRateConfigs =
(config == null) ? null : config.getRefreshRate();

View File

@@ -189,7 +189,7 @@ final class LogicalDisplay {
mTempFrameRateOverride = new SparseArray<>();
mIsEnabled = true;
mIsInTransition = false;
mBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID;
mBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
}
public void setDevicePositionLocked(int position) {
@@ -344,6 +344,21 @@ final class LogicalDisplay {
mInfo.set(null);
}
}
/**
* Updates refreshRateThermalThrottling
*
* @param refreshRanges new refreshRateThermalThrottling ranges limited by layout or default
*/
public void updateRefreshRateThermalThrottling(
@Nullable SparseArray<SurfaceControl.RefreshRateRange> refreshRanges) {
if (refreshRanges == null) {
refreshRanges = new SparseArray<>();
}
if (!mBaseDisplayInfo.refreshRateThermalThrottling.contentEquals(refreshRanges)) {
mBaseDisplayInfo.refreshRateThermalThrottling = refreshRanges;
mInfo.set(null);
}
}
/**
* Updates the state of the logical display based on the available display devices.

View File

@@ -1013,19 +1013,23 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
if (newDisplay != oldDisplay) {
newDisplay.swapDisplaysLocked(oldDisplay);
}
DisplayDeviceConfig config = device.getDisplayDeviceConfig();
newDisplay.setDevicePositionLocked(displayLayout.getPosition());
newDisplay.setLeadDisplayLocked(displayLayout.getLeadDisplayId());
newDisplay.updateLayoutLimitedRefreshRateLocked(
device.getDisplayDeviceConfig().getRefreshRange(
displayLayout.getRefreshRateZoneId()
config.getRefreshRange(displayLayout.getRefreshRateZoneId())
);
newDisplay.updateRefreshRateThermalThrottling(
config.getRefreshRateThrottlingData(
displayLayout.getRefreshRateThermalThrottlingMapId()
)
);
setEnabledLocked(newDisplay, displayLayout.isEnabled());
newDisplay.setBrightnessThrottlingDataIdLocked(
displayLayout.getBrightnessThrottlingMapId() == null
? DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID
? DisplayDeviceConfig.DEFAULT_ID
: displayLayout.getBrightnessThrottlingMapId());
newDisplay.setDisplayGroupNameLocked(displayLayout.getDisplayGroupName());

View File

@@ -257,6 +257,9 @@ public class Layout {
@Nullable
private String mRefreshRateZoneId;
@Nullable
private String mRefreshRateThermalThrottlingMapId;
Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,
@NonNull String displayGroupName, String brightnessThrottlingMapId, int position,
int leadDisplayId) {
@@ -286,6 +289,7 @@ public class Layout {
+ ", brightnessThrottlingMapId: " + mBrightnessThrottlingMapId
+ ", mRefreshRateZoneId: " + mRefreshRateZoneId
+ ", mLeadDisplayId: " + mLeadDisplayId
+ ", mRefreshRateThermalThrottlingMapId: " + mRefreshRateThermalThrottlingMapId
+ "}";
}
@@ -305,7 +309,9 @@ public class Layout {
&& Objects.equals(mBrightnessThrottlingMapId,
otherDisplay.mBrightnessThrottlingMapId)
&& Objects.equals(otherDisplay.mRefreshRateZoneId, this.mRefreshRateZoneId)
&& this.mLeadDisplayId == otherDisplay.mLeadDisplayId;
&& this.mLeadDisplayId == otherDisplay.mLeadDisplayId
&& Objects.equals(mRefreshRateThermalThrottlingMapId,
otherDisplay.mRefreshRateThermalThrottlingMapId);
}
@Override
@@ -319,6 +325,7 @@ public class Layout {
result = 31 * result + mBrightnessThrottlingMapId.hashCode();
result = 31 * result + Objects.hashCode(mRefreshRateZoneId);
result = 31 * result + mLeadDisplayId;
result = 31 * result + Objects.hashCode(mRefreshRateThermalThrottlingMapId);
return result;
}
@@ -388,5 +395,13 @@ public class Layout {
public int getLeadDisplayId() {
return mLeadDisplayId;
}
public void setRefreshRateThermalThrottlingMapId(String refreshRateThermalThrottlingMapId) {
mRefreshRateThermalThrottlingMapId = refreshRateThermalThrottlingMapId;
}
public String getRefreshRateThermalThrottlingMapId() {
return mRefreshRateThermalThrottlingMapId;
}
}
}

View File

@@ -224,6 +224,7 @@ public class DisplayModeDirector {
}
mLoggingEnabled = loggingEnabled;
mBrightnessObserver.setLoggingEnabled(loggingEnabled);
mSkinThermalStatusObserver.setLoggingEnabled(loggingEnabled);
}
@NonNull
@@ -2669,7 +2670,7 @@ public class DisplayModeDirector {
}
}
private static final class SensorObserver implements ProximityActiveListener,
protected static final class SensorObserver implements ProximityActiveListener,
DisplayManager.DisplayListener {
private final String mProximitySensorName = null;
private final String mProximitySensorType = Sensor.STRING_TYPE_PROXIMITY;
@@ -2952,52 +2953,6 @@ public class DisplayModeDirector {
}
}
private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
private final BallotBox mBallotBox;
private final Injector mInjector;
private @Temperature.ThrottlingStatus int mStatus = -1;
SkinThermalStatusObserver(Injector injector, BallotBox ballotBox) {
mInjector = injector;
mBallotBox = ballotBox;
}
@Override
public void notifyThrottling(Temperature temp) {
mStatus = temp.getStatus();
if (mLoggingEnabled) {
Slog.d(TAG, "New thermal throttling status "
+ ", current thermal status = " + mStatus);
}
final Vote vote;
if (mStatus >= Temperature.THROTTLING_CRITICAL) {
vote = Vote.forRenderFrameRates(0f, 60f);
} else {
vote = null;
}
mBallotBox.vote(GLOBAL_ID, Vote.PRIORITY_SKIN_TEMPERATURE, vote);
}
public void observe() {
IThermalService thermalService = mInjector.getThermalService();
if (thermalService == null) {
Slog.w(TAG, "Could not observe thermal status. Service not available");
return;
}
try {
thermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to register thermal status listener", e);
}
}
void dumpLocked(PrintWriter writer) {
writer.println(" SkinThermalStatusObserver:");
writer.println(" mStatus: " + mStatus);
}
}
private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener {
public void startListening() {
mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
@@ -3184,11 +3139,15 @@ public class DisplayModeDirector {
void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener,
Handler handler, long flags);
Display[] getDisplays();
boolean getDisplayInfo(int displayId, DisplayInfo displayInfo);
BrightnessInfo getBrightnessInfo(int displayId);
boolean isDozeState(Display d);
IThermalService getThermalService();
boolean registerThermalServiceListener(IThermalEventListener listener);
boolean supportsFrameRateOverride();
}
@@ -3221,6 +3180,20 @@ public class DisplayModeDirector {
getDisplayManager().registerDisplayListener(listener, handler, flags);
}
@Override
public Display[] getDisplays() {
return getDisplayManager().getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
}
@Override
public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) {
Display display = getDisplayManager().getDisplay(displayId);
if (display != null) {
return display.getDisplayInfo(displayInfo);
}
return false;
}
@Override
public BrightnessInfo getBrightnessInfo(int displayId) {
final Display display = getDisplayManager().getDisplay(displayId);
@@ -3239,9 +3212,20 @@ public class DisplayModeDirector {
}
@Override
public IThermalService getThermalService() {
return IThermalService.Stub.asInterface(
ServiceManager.getService(Context.THERMAL_SERVICE));
public boolean registerThermalServiceListener(IThermalEventListener listener) {
IThermalService thermalService = getThermalService();
if (thermalService == null) {
Slog.w(TAG, "Could not observe thermal status. Service not available");
return false;
}
try {
thermalService.registerThermalEventListenerWithType(listener,
Temperature.TYPE_SKIN);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to register thermal status listener", e);
return false;
}
return true;
}
@Override
@@ -3255,6 +3239,11 @@ public class DisplayModeDirector {
}
return mDisplayManager;
}
private IThermalService getThermalService() {
return IThermalService.Stub.asInterface(
ServiceManager.getService(Context.THERMAL_SERVICE));
}
}
interface BallotBox {

View File

@@ -0,0 +1,263 @@
/*
* 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.Nullable;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.IThermalEventListener;
import android.os.Temperature;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.SurfaceControl;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import java.io.PrintWriter;
final class SkinThermalStatusObserver extends IThermalEventListener.Stub implements
DisplayManager.DisplayListener {
private static final String TAG = "SkinThermalStatusObserver";
private final DisplayModeDirector.BallotBox mBallotBox;
private final DisplayModeDirector.Injector mInjector;
private boolean mLoggingEnabled;
private final Handler mHandler;
private final Object mThermalObserverLock = new Object();
@GuardedBy("mThermalObserverLock")
@Temperature.ThrottlingStatus
private int mStatus = -1;
@GuardedBy("mThermalObserverLock")
private final SparseArray<SparseArray<SurfaceControl.RefreshRateRange>>
mThermalThrottlingByDisplay = new SparseArray<>();
SkinThermalStatusObserver(DisplayModeDirector.Injector injector,
DisplayModeDirector.BallotBox ballotBox) {
this(injector, ballotBox, BackgroundThread.getHandler());
}
@VisibleForTesting
SkinThermalStatusObserver(DisplayModeDirector.Injector injector,
DisplayModeDirector.BallotBox ballotBox, Handler handler) {
mInjector = injector;
mBallotBox = ballotBox;
mHandler = handler;
}
void observe() {
// if failed to register thermal service listener, don't register display listener
if (!mInjector.registerThermalServiceListener(this)) {
return;
}
mInjector.registerDisplayListener(this, mHandler,
DisplayManager.EVENT_FLAG_DISPLAY_ADDED | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
| DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
populateInitialDisplayInfo();
}
void setLoggingEnabled(boolean enabled) {
mLoggingEnabled = enabled;
}
@Override
public void notifyThrottling(Temperature temp) {
@Temperature.ThrottlingStatus int currentStatus = temp.getStatus();
synchronized (mThermalObserverLock) {
mStatus = currentStatus;
mHandler.post(this::updateVotes);
}
if (mLoggingEnabled) {
Slog.d(TAG, "New thermal throttling status " + ", current thermal status = "
+ currentStatus);
}
}
//region DisplayManager.DisplayListener
@Override
public void onDisplayAdded(int displayId) {
updateRefreshRateThermalThrottling(displayId);
if (mLoggingEnabled) {
Slog.d(TAG, "Display added:" + displayId);
}
}
@Override
public void onDisplayRemoved(int displayId) {
synchronized (mThermalObserverLock) {
mThermalThrottlingByDisplay.remove(displayId);
mHandler.post(() -> mBallotBox.vote(displayId,
DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE, null));
}
if (mLoggingEnabled) {
Slog.d(TAG, "Display removed and voted: displayId=" + displayId);
}
}
@Override
public void onDisplayChanged(int displayId) {
updateRefreshRateThermalThrottling(displayId);
if (mLoggingEnabled) {
Slog.d(TAG, "Display changed:" + displayId);
}
}
//endregion
private void populateInitialDisplayInfo() {
DisplayInfo info = new DisplayInfo();
Display[] displays = mInjector.getDisplays();
int size = displays.length;
SparseArray<SparseArray<SurfaceControl.RefreshRateRange>> localMap = new SparseArray<>(
size);
for (Display d : displays) {
final int displayId = d.getDisplayId();
d.getDisplayInfo(info);
localMap.put(displayId, info.refreshRateThermalThrottling);
}
synchronized (mThermalObserverLock) {
for (int i = 0; i < size; i++) {
mThermalThrottlingByDisplay.put(localMap.keyAt(i), localMap.valueAt(i));
}
}
if (mLoggingEnabled) {
Slog.d(TAG, "Display initial info:" + localMap);
}
}
private void updateRefreshRateThermalThrottling(int displayId) {
DisplayInfo displayInfo = new DisplayInfo();
mInjector.getDisplayInfo(displayId, displayInfo);
SparseArray<SurfaceControl.RefreshRateRange> throttlingMap =
displayInfo.refreshRateThermalThrottling;
synchronized (mThermalObserverLock) {
mThermalThrottlingByDisplay.put(displayId, throttlingMap);
mHandler.post(() -> updateVoteForDisplay(displayId));
}
if (mLoggingEnabled) {
Slog.d(TAG,
"Thermal throttling updated: display=" + displayId + ", map=" + throttlingMap);
}
}
//region in mHandler thread
private void updateVotes() {
@Temperature.ThrottlingStatus int localStatus;
SparseArray<SparseArray<SurfaceControl.RefreshRateRange>> localMap;
synchronized (mThermalObserverLock) {
localStatus = mStatus;
localMap = mThermalThrottlingByDisplay.clone();
}
if (mLoggingEnabled) {
Slog.d(TAG, "Updating votes for status=" + localStatus + ", map=" + localMap);
}
int size = localMap.size();
for (int i = 0; i < size; i++) {
reportThrottlingIfNeeded(localMap.keyAt(i), localStatus, localMap.valueAt(i));
}
}
private void updateVoteForDisplay(int displayId) {
@Temperature.ThrottlingStatus int localStatus;
SparseArray<SurfaceControl.RefreshRateRange> localMap;
synchronized (mThermalObserverLock) {
localStatus = mStatus;
localMap = mThermalThrottlingByDisplay.get(displayId);
}
if (mLoggingEnabled) {
Slog.d(TAG, "Updating votes for status=" + localStatus + ", display =" + displayId
+ ", map=" + localMap);
}
reportThrottlingIfNeeded(displayId, localStatus, localMap);
}
private void reportThrottlingIfNeeded(int displayId,
@Temperature.ThrottlingStatus int currentStatus,
SparseArray<SurfaceControl.RefreshRateRange> throttlingMap) {
if (currentStatus == -1) { // no throttling status reported from thermal sensor yet
return;
}
if (throttlingMap.size() == 0) { // map is not configured, using default behaviour
fallbackReportThrottlingIfNeeded(displayId, currentStatus);
return;
}
SurfaceControl.RefreshRateRange foundRange = findBestMatchingRefreshRateRange(currentStatus,
throttlingMap);
// if status <= currentStatus not found in the map reset vote
DisplayModeDirector.Vote vote = null;
if (foundRange != null) { // otherwise vote with found range
vote = DisplayModeDirector.Vote.forRenderFrameRates(foundRange.min, foundRange.max);
}
mBallotBox.vote(displayId, DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE, vote);
if (mLoggingEnabled) {
Slog.d(TAG, "Voted: vote=" + vote + ", display =" + displayId);
}
}
@Nullable
private SurfaceControl.RefreshRateRange findBestMatchingRefreshRateRange(
@Temperature.ThrottlingStatus int currentStatus,
SparseArray<SurfaceControl.RefreshRateRange> throttlingMap) {
SurfaceControl.RefreshRateRange foundRange = null;
for (int status = currentStatus; status >= 0; status--) {
foundRange = throttlingMap.get(status);
if (foundRange != null) {
break;
}
}
return foundRange;
}
private void fallbackReportThrottlingIfNeeded(int displayId,
@Temperature.ThrottlingStatus int currentStatus) {
DisplayModeDirector.Vote vote = null;
if (currentStatus >= Temperature.THROTTLING_CRITICAL) {
vote = DisplayModeDirector.Vote.forRenderFrameRates(0f, 60f);
}
mBallotBox.vote(displayId, DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE, vote);
if (mLoggingEnabled) {
Slog.d(TAG, "Voted(fallback): vote=" + vote + ", display =" + displayId);
}
}
//endregion
void dumpLocked(PrintWriter writer) {
@Temperature.ThrottlingStatus int localStatus;
SparseArray<SparseArray<SurfaceControl.RefreshRateRange>> localMap;
synchronized (mThermalObserverLock) {
localStatus = mStatus;
localMap = mThermalThrottlingByDisplay.clone();
}
writer.println(" SkinThermalStatusObserver:");
writer.println(" mStatus: " + localStatus);
writer.println(" mThermalThrottlingByDisplay: " + localMap);
}
}

View File

@@ -211,6 +211,32 @@
<xs:element type="brightnessThrottlingMap" name="brightnessThrottlingMap" maxOccurs="unbounded">
<xs:annotation name="final"/>
</xs:element>
<xs:element type="refreshRateThrottlingMap" name="refreshRateThrottlingMap" maxOccurs="unbounded">
<xs:annotation name="final"/>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="refreshRateThrottlingMap">
<xs:attribute name="id" type="xs:string" />
<xs:sequence>
<xs:element name="refreshRateThrottlingPoint" type="refreshRateThrottlingPoint" maxOccurs="unbounded">
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="refreshRateThrottlingPoint">
<xs:sequence>
<xs:element type="thermalStatus" name="thermalStatus">
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
<xs:element type="refreshRateRange" name="refreshRateRange">
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
</xs:sequence>
</xs:complexType>

View File

@@ -214,6 +214,21 @@ package com.android.server.display.config {
method public final void setMinimum(java.math.BigInteger);
}
public class RefreshRateThrottlingMap {
ctor public RefreshRateThrottlingMap();
method public String getId();
method @NonNull public final java.util.List<com.android.server.display.config.RefreshRateThrottlingPoint> getRefreshRateThrottlingPoint();
method public void setId(String);
}
public class RefreshRateThrottlingPoint {
ctor public RefreshRateThrottlingPoint();
method @NonNull public final com.android.server.display.config.RefreshRateRange getRefreshRateRange();
method @NonNull public final com.android.server.display.config.ThermalStatus getThermalStatus();
method public final void setRefreshRateRange(@NonNull com.android.server.display.config.RefreshRateRange);
method public final void setThermalStatus(@NonNull com.android.server.display.config.ThermalStatus);
}
public class RefreshRateZone {
ctor public RefreshRateZone();
method public String getId();
@@ -264,6 +279,7 @@ package com.android.server.display.config {
public class ThermalThrottling {
ctor public ThermalThrottling();
method public final java.util.List<com.android.server.display.config.BrightnessThrottlingMap> getBrightnessThrottlingMap();
method public final java.util.List<com.android.server.display.config.RefreshRateThrottlingMap> getRefreshRateThrottlingMap();
}
public class ThresholdPoint {

View File

@@ -52,6 +52,7 @@
<xs:element name="address" type="xs:nonNegativeInteger"/>
<xs:element name="position" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="brightnessThrottlingMapId" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="refreshRateThermalThrottlingMapId" type="xs:string" minOccurs="0" />
</xs:sequence>
<xs:attribute name="enabled" type="xs:boolean" use="optional" />
<xs:attribute name="defaultDisplay" type="xs:boolean" use="optional" />

View File

@@ -7,6 +7,7 @@ package com.android.server.display.config.layout {
method public String getBrightnessThrottlingMapId();
method public String getDisplayGroup();
method public String getPosition();
method public String getRefreshRateThermalThrottlingMapId();
method public String getRefreshRateZoneId();
method public boolean isDefaultDisplay();
method public boolean isEnabled();
@@ -16,6 +17,7 @@ package com.android.server.display.config.layout {
method public void setDisplayGroup(String);
method public void setEnabled(boolean);
method public void setPosition(String);
method public void setRefreshRateThermalThrottlingMapId(String);
method public void setRefreshRateZoneId(String);
}

View File

@@ -249,7 +249,7 @@ public final class DisplayPowerController2Test {
when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
when(logicalDisplayMock.getBrightnessThrottlingDataIdLocked()).thenReturn(
DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID);
DisplayDeviceConfig.DEFAULT_ID);
when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);

View File

@@ -252,7 +252,7 @@ public final class DisplayPowerControllerTest {
when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
when(logicalDisplayMock.getBrightnessThrottlingDataIdLocked()).thenReturn(
DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID);
DisplayDeviceConfig.DEFAULT_ID);
when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);

View File

@@ -157,6 +157,26 @@ public class DeviceStateToLayoutMapTest {
assertEquals(testLayout, configLayout);
}
@Test
public void testRefreshRateThermalThrottlingMapId() {
Layout configLayout = mDeviceStateToLayoutMap.get(4);
Layout testLayout = new Layout();
Layout.Display display1 = testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(345L), /* isDefault= */ true,
/* isEnabled= */ true, /* displayGroup= */ null, mDisplayIdProducerMock,
/* brightnessThrottlingMapId= */ null,
/* leadDisplayId= */ Display.DEFAULT_DISPLAY);
display1.setRefreshRateThermalThrottlingMapId("test2");
testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(678L), /* isDefault= */ false,
/* isEnabled= */ true, /* displayGroup= */ null, mDisplayIdProducerMock,
/* brightnessThrottlingMapId= */ null,
/* leadDisplayId= */ Display.DEFAULT_DISPLAY);
assertEquals(testLayout, configLayout);
}
////////////////////
// Helper Methods //
////////////////////
@@ -221,6 +241,19 @@ public class DeviceStateToLayoutMapTest {
+ "<address>678</address>\n"
+ "</display>\n"
+ "</layout>\n"
+ "<layout>\n"
+ "<state>4</state> \n"
+ "<display enabled=\"true\" defaultDisplay=\"true\" >\n"
+ "<address>345</address>\n"
+ "<refreshRateThermalThrottlingMapId>"
+ "test2"
+ "</refreshRateThermalThrottlingMapId>"
+ "</display>\n"
+ "<display enabled=\"true\">\n"
+ "<address>678</address>\n"
+ "</display>\n"
+ "</layout>\n"
+ "</layouts>\n";
}
}

View File

@@ -28,6 +28,9 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Temperature;
import android.util.SparseArray;
import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -315,6 +318,55 @@ public final class DisplayDeviceConfigTest {
// HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
}
@Test
public void testRefreshRateThermalThrottlingFromDisplayConfig() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile();
SparseArray<SurfaceControl.RefreshRateRange> defaultMap =
mDisplayDeviceConfig.getRefreshRateThrottlingData(null);
assertNotNull(defaultMap);
assertEquals(2, defaultMap.size());
assertEquals(30, defaultMap.get(Temperature.THROTTLING_CRITICAL).min, SMALL_DELTA);
assertEquals(60, defaultMap.get(Temperature.THROTTLING_CRITICAL).max, SMALL_DELTA);
assertEquals(0, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).min, SMALL_DELTA);
assertEquals(30, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).max, SMALL_DELTA);
SparseArray<SurfaceControl.RefreshRateRange> testMap =
mDisplayDeviceConfig.getRefreshRateThrottlingData("test");
assertNotNull(testMap);
assertEquals(1, testMap.size());
assertEquals(60, testMap.get(Temperature.THROTTLING_EMERGENCY).min, SMALL_DELTA);
assertEquals(90, testMap.get(Temperature.THROTTLING_EMERGENCY).max, SMALL_DELTA);
}
private String getRefreshThermalThrottlingMaps() {
return "<refreshRateThrottlingMap>\n"
+ " <refreshRateThrottlingPoint>\n"
+ " <thermalStatus>critical</thermalStatus>\n"
+ " <refreshRateRange>\n"
+ " <minimum>30</minimum>\n"
+ " <maximum>60</maximum>\n"
+ " </refreshRateRange>\n"
+ " </refreshRateThrottlingPoint>\n"
+ " <refreshRateThrottlingPoint>\n"
+ " <thermalStatus>shutdown</thermalStatus>\n"
+ " <refreshRateRange>\n"
+ " <minimum>0</minimum>\n"
+ " <maximum>30</maximum>\n"
+ " </refreshRateRange>\n"
+ " </refreshRateThrottlingPoint>\n"
+ "</refreshRateThrottlingMap>\n"
+ "<refreshRateThrottlingMap id=\"test\">\n"
+ " <refreshRateThrottlingPoint>\n"
+ " <thermalStatus>emergency</thermalStatus>\n"
+ " <refreshRateRange>\n"
+ " <minimum>60</minimum>\n"
+ " <maximum>90</maximum>\n"
+ " </refreshRateRange>\n"
+ " </refreshRateThrottlingPoint>\n"
+ "</refreshRateThrottlingMap>\n";
}
private String getContent() {
return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ "<displayConfiguration>\n"
@@ -557,6 +609,7 @@ public final class DisplayDeviceConfigTest {
+ "<brightness>0.0125</brightness>\n"
+ "</brightnessThrottlingPoint>\n"
+ "</brightnessThrottlingMap>\n"
+ getRefreshThermalThrottlingMaps()
+ "</thermalThrottling>\n"
+ "<refreshRate>\n"
+ "<defaultRefreshRate>45</defaultRefreshRate>\n"

View File

@@ -687,10 +687,10 @@ public class LogicalDisplayMapperTest {
assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
assertEquals(DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID,
assertEquals(DisplayDeviceConfig.DEFAULT_ID,
mLogicalDisplayMapper.getDisplayLocked(device1)
.getBrightnessThrottlingDataIdLocked());
assertEquals(DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID,
assertEquals(DisplayDeviceConfig.DEFAULT_ID,
mLogicalDisplayMapper.getDisplayLocked(device2)
.getBrightnessThrottlingDataIdLocked());
@@ -700,10 +700,10 @@ public class LogicalDisplayMapperTest {
assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
assertEquals(DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID,
assertEquals(DisplayDeviceConfig.DEFAULT_ID,
mLogicalDisplayMapper.getDisplayLocked(device1)
.getBrightnessThrottlingDataIdLocked());
assertEquals(DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID,
assertEquals(DisplayDeviceConfig.DEFAULT_ID,
mLogicalDisplayMapper.getDisplayLocked(device2)
.getBrightnessThrottlingDataIdLocked());
}

View File

@@ -59,12 +59,12 @@ import android.hardware.SensorManager;
import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Handler;
import android.os.IThermalEventListener;
import android.os.IThermalService;
import android.os.Looper;
import android.os.RemoteException;
import android.os.Temperature;
@@ -75,6 +75,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.SurfaceControl.RefreshRateRange;
import android.view.SurfaceControl.RefreshRateRanges;
@@ -83,6 +84,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.Preconditions;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
@@ -140,8 +142,6 @@ public class DisplayModeDirectorTest {
public SensorManagerInternal mSensorManagerInternalMock;
@Mock
public DisplayManagerInternal mDisplayManagerInternalMock;
@Mock
public IThermalService mThermalServiceMock;
@Before
public void setUp() throws Exception {
@@ -150,7 +150,6 @@ public class DisplayModeDirectorTest {
final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
when(mContext.getContentResolver()).thenReturn(resolver);
mInjector = spy(new FakesInjector());
when(mInjector.getThermalService()).thenReturn(mThermalServiceMock);
mHandler = new Handler(Looper.getMainLooper());
LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
@@ -1773,11 +1772,12 @@ public class DisplayModeDirectorTest {
ArgumentCaptor<DisplayListener> DisplayCaptor =
ArgumentCaptor.forClass(DisplayListener.class);
verify(mInjector).registerDisplayListener(DisplayCaptor.capture(), any(Handler.class),
verify(mInjector, times(2)).registerDisplayListener(DisplayCaptor.capture(),
any(Handler.class),
eq(DisplayManager.EVENT_FLAG_DISPLAY_ADDED
| DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
| DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
DisplayListener displayListener = DisplayCaptor.getValue();
DisplayListener displayListener = DisplayCaptor.getAllValues().get(0);
// Verify that there is no proximity vote initially
Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_PROXIMITY);
@@ -2236,8 +2236,7 @@ public class DisplayModeDirectorTest {
ArgumentCaptor<IThermalEventListener> thermalEventListener =
ArgumentCaptor.forClass(IThermalEventListener.class);
verify(mThermalServiceMock).registerThermalEventListenerWithType(
thermalEventListener.capture(), eq(Temperature.TYPE_SKIN));
verify(mInjector).registerThermalServiceListener(thermalEventListener.capture());
final IThermalEventListener listener = thermalEventListener.getValue();
// Verify that there is no skin temperature vote initially.
@@ -2246,11 +2245,13 @@ public class DisplayModeDirectorTest {
// Set the skin temperature to critical and verify that we added a vote.
listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
BackgroundThread.getHandler().runWithScissors(() -> { }, 500 /*timeout*/);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
assertVoteForRenderFrameRateRange(vote, 0f, 60.f);
// Set the skin temperature to severe and verify that the vote is gone.
listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_SEVERE));
BackgroundThread.getHandler().runWithScissors(() -> { }, 500 /*timeout*/);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
assertNull(vote);
}
@@ -2708,6 +2709,16 @@ public class DisplayModeDirectorTest {
@Override
public void registerDisplayListener(DisplayListener listener, Handler handler, long flag) {}
@Override
public Display[] getDisplays() {
return new Display[] { createDisplay(DISPLAY_ID) };
}
@Override
public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) {
return false;
}
@Override
public BrightnessInfo getBrightnessInfo(int displayId) {
return null;
@@ -2719,8 +2730,8 @@ public class DisplayModeDirectorTest {
}
@Override
public IThermalService getThermalService() {
return null;
public boolean registerThermalServiceListener(IThermalEventListener listener) {
return true;
}
@Override
@@ -2728,6 +2739,11 @@ public class DisplayModeDirectorTest {
return true;
}
protected Display createDisplay(int id) {
return new Display(DisplayManagerGlobal.getInstance(), id, new DisplayInfo(),
ApplicationProvider.getApplicationContext().getResources());
}
void notifyPeakRefreshRateChanged() {
if (mPeakRefreshRateObserver != null) {
mPeakRefreshRateObserver.dispatchChange(false /*selfChange*/,

View File

@@ -0,0 +1,247 @@
/*
* 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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.IThermalEventListener;
import android.os.Temperature;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.SurfaceControl;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.server.testutils.TestHandler;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests for DisplayModeDirector.SkinThermalStatusObserver. Comply with changes described in
* b/266789924
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class SkinThermalStatusObserverTest {
private static final float FLOAT_TOLERANCE = 0.01f;
private static final int DISPLAY_ID = 1;
private static final int DISPLAY_ID_OTHER = 2;
SkinThermalStatusObserver mObserver;
private RegisteringFakesInjector mInjector = new RegisteringFakesInjector();
private final TestHandler mHandler = new TestHandler(null);
private final FakeVoteStorage mStorage = new FakeVoteStorage();
@Before
public void setUp() {
mObserver = new SkinThermalStatusObserver(mInjector, mStorage, mHandler);
}
@Test
public void testRegisterListenersOnObserve() {
// GIVEN thermal sensor is available
assertNull(mInjector.mThermalEventListener);
assertNull(mInjector.mDisplayListener);
// WHEN observe is called
mObserver.observe();
// THEN thermal and display listeners are registered
assertEquals(mObserver, mInjector.mThermalEventListener);
assertEquals(mObserver, mInjector.mDisplayListener);
}
@Test
public void testFailToRegisterThermalListenerOnObserve() {
// GIVEN thermal sensor is not available
mInjector = new RegisteringFakesInjector(false);
mObserver = new SkinThermalStatusObserver(mInjector, mStorage, mHandler);
// WHEN observe is called
mObserver.observe();
// THEN nothing is registered
assertNull(mInjector.mThermalEventListener);
assertNull(mInjector.mDisplayListener);
}
@Test
public void testNotifyWithDefaultVotesForCritical() {
// GIVEN 2 displays with no thermalThrottling config
mObserver.observe();
assertEquals(0, mStorage.mVoteRegistry.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);
assertEquals(1, displayVotes.size());
DisplayModeDirector.Vote vote = displayVotes.get(
DisplayModeDirector.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);
assertEquals(1, otherDisplayVotes.size());
vote = otherDisplayVotes.get(DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE);
assertEquals(0, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE);
assertEquals(60, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE);
}
@Test
public void testNotifyWithDefaultVotesChangeFromCriticalToSevere() {
// GIVEN 2 displays with no thermalThrottling config AND temperature level CRITICAL
mObserver.observe();
assertEquals(0, mStorage.mVoteRegistry.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());
}
@Test
public void testNotifyWithDefaultVotesForSevere() {
// GIVEN 2 displays with no thermalThrottling config
mObserver.observe();
assertEquals(0, mStorage.mVoteRegistry.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());
}
@Test
public void testNotifiesWithConfigVotes() {
// GIVEN 2 displays AND one has thermalThrottling config defined
SparseArray<SurfaceControl.RefreshRateRange> displayConfig = new SparseArray<>();
displayConfig.put(Temperature.THROTTLING_MODERATE,
new SurfaceControl.RefreshRateRange(90.0f, 120.0f));
SparseArray<SparseArray<SurfaceControl.RefreshRateRange>> config = new SparseArray<>();
config.put(DISPLAY_ID, displayConfig);
mInjector = new RegisteringFakesInjector(true, config);
mObserver = new SkinThermalStatusObserver(mInjector, mStorage, mHandler);
mObserver.observe();
mObserver.onDisplayChanged(DISPLAY_ID);
assertEquals(0, mStorage.mVoteRegistry.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(1, displayVotes.size());
DisplayModeDirector.Vote vote = displayVotes.get(
DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE);
assertEquals(90, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE);
assertEquals(120, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE);
}
private static Temperature createTemperature(@Temperature.ThrottlingStatus int status) {
return new Temperature(40.0f, Temperature.TYPE_SKIN, "test_temp", status);
}
private static class RegisteringFakesInjector extends DisplayModeDirectorTest.FakesInjector {
private IThermalEventListener mThermalEventListener;
private DisplayManager.DisplayListener mDisplayListener;
private final boolean mRegisterThermalListener;
private final SparseArray<SparseArray<SurfaceControl.RefreshRateRange>> mOverriddenConfig;
private RegisteringFakesInjector() {
this(true);
}
private RegisteringFakesInjector(boolean registerThermalListener) {
this(registerThermalListener, new SparseArray<>());
}
private RegisteringFakesInjector(boolean registerThermalListener,
SparseArray<SparseArray<SurfaceControl.RefreshRateRange>> overriddenConfig) {
mRegisterThermalListener = registerThermalListener;
mOverriddenConfig = overriddenConfig;
}
@Override
public boolean registerThermalServiceListener(IThermalEventListener listener) {
mThermalEventListener = (mRegisterThermalListener ? listener : null);
return mRegisterThermalListener;
}
@Override
public void registerDisplayListener(DisplayManager.DisplayListener listener,
Handler handler, long flag) {
mDisplayListener = listener;
}
@Override
public Display[] getDisplays() {
return new Display[] {createDisplay(DISPLAY_ID), createDisplay(DISPLAY_ID_OTHER)};
}
@Override
public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) {
SparseArray<SurfaceControl.RefreshRateRange> config = mOverriddenConfig.get(displayId);
if (config != null) {
displayInfo.refreshRateThermalThrottling = config;
return true;
}
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);
}
}
}
}