summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/view/DisplayInfo.java17
-rw-r--r--services/core/java/com/android/server/display/DeviceStateToLayoutMap.java2
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java102
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java17
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java10
-rw-r--r--services/core/java/com/android/server/display/layout/Layout.java17
-rw-r--r--services/core/java/com/android/server/display/mode/DisplayModeDirector.java91
-rw-r--r--services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java263
-rw-r--r--services/core/xsd/display-device-config/display-device-config.xsd26
-rw-r--r--services/core/xsd/display-device-config/schema/current.txt16
-rw-r--r--services/core/xsd/display-layout-config/display-layout-config.xsd1
-rw-r--r--services/core/xsd/display-layout-config/schema/current.txt2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java33
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java53
-rw-r--r--services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java36
-rw-r--r--services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java247
19 files changed, 867 insertions, 78 deletions
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 03689188a9b3..e31adcfd699e 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -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();
}
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index ce29013f1623..63218ee4e12d 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -138,6 +138,8 @@ class DeviceStateToLayoutMap {
display.setPosition(POSITION_UNKNOWN);
}
display.setRefreshRateZoneId(d.getRefreshRateZoneId());
+ display.setRefreshRateThermalThrottlingMapId(
+ d.getRefreshRateThermalThrottlingMapId());
}
}
} catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 75f8accde3a5..ce08fd138080 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -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;
@@ -1315,6 +1339,17 @@ public class DisplayDeviceConfig {
}
/**
+ * @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
*/
public long getAutoBrightnessDarkeningLightDebounce() {
@@ -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();
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 04532f98a0c7..dee4cdea65fe 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -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.
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 2ac7d9d1a73e..e290b7a8b708 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -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());
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index f1e885e982e1..6a4d23b18763 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -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;
+ }
}
}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 24d5ca402dd0..db6944d011c9 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -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();
}
@@ -3222,6 +3181,20 @@ public class DisplayModeDirector {
}
@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);
if (display != null) {
@@ -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 {
diff --git a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
new file mode 100644
index 000000000000..1bb34abe9025
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
@@ -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);
+ }
+}
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 9260d2b4194a..6904f3058e30 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -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>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index e81c27d94c88..5a749b065eb2 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -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 {
diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd
index d4556d710429..ce022e9cb78d 100644
--- a/services/core/xsd/display-layout-config/display-layout-config.xsd
+++ b/services/core/xsd/display-layout-config/display-layout-config.xsd
@@ -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" />
diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt
index 52133ab76086..42a800db1474 100644
--- a/services/core/xsd/display-layout-config/schema/current.txt
+++ b/services/core/xsd/display-layout-config/schema/current.txt
@@ -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);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 3e0e5a8414dd..7942e246c2a7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -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);
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index 6c4afd37d8b1..16bf2a227c8a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -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);
diff --git a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
index a380efffb62f..e74b278bd904 100644
--- a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -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";
}
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index fdfcd81cbef9..c6a28e4ee441 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -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"
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index b698cdf10816..9eb600304f98 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -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());
}
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index f256c8a17c56..1b02799e1da4 100644
--- a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -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);
}
@@ -2709,6 +2710,16 @@ public class DisplayModeDirectorTest {
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*/,
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java b/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
new file mode 100644
index 000000000000..dd0cd965b8a0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
@@ -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);
+ }
+ }
+ }
+}