diff options
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); + } + } + } +} |