From d6795179ab2a54fd0376b83cfcc033433148def6 Mon Sep 17 00:00:00 2001 From: Oleg Petsjonkin Date: Fri, 21 Apr 2023 12:20:57 +0000 Subject: [PATCH] Properly handling situation when display is added and removed instantly Bug: b/279146324 b/279145472 b/270742628 Test: atest DisplayModeDirectorTest atest ActivityThreadTest atest LogicalDisplayTest Change-Id: Ic48d67f4c1e383ca1419eb3980625a61ce766a7c --- core/java/android/view/DisplayInfo.java | 17 +++-- .../server/display/LogicalDisplay.java | 34 +++++++-- .../display/mode/DisplayModeDirector.java | 54 ++++++++------ .../mode/SkinThermalStatusObserver.java | 4 +- .../server/display/LogicalDisplayTest.java | 41 +++++++++-- .../display/mode/DisplayModeDirectorTest.java | 73 ++++++++++++++++++- .../mode/SkinThermalStatusObserverTest.java | 2 +- 7 files changed, 173 insertions(+), 52 deletions(-) diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index e31adcfd699e8..f2373fbfa858a 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -341,6 +341,9 @@ public final class DisplayInfo implements Parcelable { @Nullable public DisplayShape displayShape; + /** + * Refresh rate range limitation based on the current device layout + */ @Nullable public SurfaceControl.RefreshRateRange layoutLimitedRefreshRate; @@ -354,7 +357,7 @@ public final class DisplayInfo implements Parcelable { * RefreshRateRange limitation for @Temperature.ThrottlingStatus */ @NonNull - public SparseArray refreshRateThermalThrottling = + public SparseArray thermalRefreshRateThrottling = new SparseArray<>(); public static final @android.annotation.NonNull Creator CREATOR = new Creator() { @@ -434,7 +437,7 @@ public final class DisplayInfo implements Parcelable { && Objects.equals(displayShape, other.displayShape) && Objects.equals(layoutLimitedRefreshRate, other.layoutLimitedRefreshRate) && BrightnessSynchronizer.floatEquals(hdrSdrRatio, other.hdrSdrRatio) - && refreshRateThermalThrottling.contentEquals(other.refreshRateThermalThrottling); + && thermalRefreshRateThrottling.contentEquals(other.thermalRefreshRateThrottling); } @Override @@ -491,7 +494,7 @@ public final class DisplayInfo implements Parcelable { displayShape = other.displayShape; layoutLimitedRefreshRate = other.layoutLimitedRefreshRate; hdrSdrRatio = other.hdrSdrRatio; - refreshRateThermalThrottling = other.refreshRateThermalThrottling; + thermalRefreshRateThrottling = other.thermalRefreshRateThrottling; } public void readFromParcel(Parcel source) { @@ -554,7 +557,7 @@ public final class DisplayInfo implements Parcelable { displayShape = source.readTypedObject(DisplayShape.CREATOR); layoutLimitedRefreshRate = source.readTypedObject(SurfaceControl.RefreshRateRange.CREATOR); hdrSdrRatio = source.readFloat(); - refreshRateThermalThrottling = source.readSparseArray(null, + thermalRefreshRateThrottling = source.readSparseArray(null, SurfaceControl.RefreshRateRange.class); } @@ -616,7 +619,7 @@ public final class DisplayInfo implements Parcelable { dest.writeTypedObject(displayShape, flags); dest.writeTypedObject(layoutLimitedRefreshRate, flags); dest.writeFloat(hdrSdrRatio); - dest.writeSparseArray(refreshRateThermalThrottling); + dest.writeSparseArray(thermalRefreshRateThrottling); } @Override @@ -884,8 +887,8 @@ public final class DisplayInfo implements Parcelable { } else { sb.append(hdrSdrRatio); } - sb.append(", refreshRateThermalThrottling "); - sb.append(refreshRateThermalThrottling); + sb.append(", thermalRefreshRateThrottling "); + sb.append(thermalRefreshRateThrottling); sb.append("}"); return sb.toString(); } diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index dab00d8070d4a..0b6d1c851dc45 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -181,6 +181,19 @@ final class LogicalDisplay { */ private String mThermalBrightnessThrottlingDataId; + /** + * Refresh rate range limitation based on the current device layout + */ + @Nullable + private SurfaceControl.RefreshRateRange mLayoutLimitedRefreshRate; + + /** + * RefreshRateRange limitation for @Temperature.ThrottlingStatus + */ + @NonNull + private SparseArray mThermalRefreshRateThrottling = + new SparseArray<>(); + public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) { mDisplayId = displayId; mLayerStack = layerStack; @@ -339,24 +352,24 @@ final class LogicalDisplay { */ public void updateLayoutLimitedRefreshRateLocked( @Nullable SurfaceControl.RefreshRateRange layoutLimitedRefreshRate) { - if (!Objects.equals(layoutLimitedRefreshRate, mBaseDisplayInfo.layoutLimitedRefreshRate)) { - mBaseDisplayInfo.layoutLimitedRefreshRate = layoutLimitedRefreshRate; - mInfo.set(null); + if (!Objects.equals(layoutLimitedRefreshRate, mLayoutLimitedRefreshRate)) { + mLayoutLimitedRefreshRate = layoutLimitedRefreshRate; + mDirty = true; } } /** - * Updates refreshRateThermalThrottling + * Updates thermalRefreshRateThrottling * - * @param refreshRanges new refreshRateThermalThrottling ranges limited by layout or default + * @param refreshRanges new thermalRefreshRateThrottling ranges limited by layout or default */ public void updateThermalRefreshRateThrottling( @Nullable SparseArray refreshRanges) { if (refreshRanges == null) { refreshRanges = new SparseArray<>(); } - if (!mBaseDisplayInfo.refreshRateThermalThrottling.contentEquals(refreshRanges)) { - mBaseDisplayInfo.refreshRateThermalThrottling = refreshRanges; - mInfo.set(null); + if (!mThermalRefreshRateThrottling.contentEquals(refreshRanges)) { + mThermalRefreshRateThrottling = refreshRanges; + mDirty = true; } } @@ -499,6 +512,9 @@ final class LogicalDisplay { mBaseDisplayInfo.removeMode = Display.REMOVE_MODE_DESTROY_CONTENT; } + mBaseDisplayInfo.layoutLimitedRefreshRate = mLayoutLimitedRefreshRate; + mBaseDisplayInfo.thermalRefreshRateThrottling = mThermalRefreshRateThrottling; + mPrimaryDisplayDeviceInfo = deviceInfo; mInfo.set(null); mDirty = false; @@ -952,6 +968,8 @@ final class LogicalDisplay { pw.println("mDisplayGroupName=" + mDisplayGroupName); pw.println("mThermalBrightnessThrottlingDataId=" + mThermalBrightnessThrottlingDataId); pw.println("mLeadDisplayId=" + mLeadDisplayId); + pw.println("mLayoutLimitedRefreshRate=" + mLayoutLimitedRefreshRate); + pw.println("mThermalRefreshRateThrottling=" + mThermalRefreshRateThrottling); } @Override 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 03b49f088c2f4..2653b2ac0b88f 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -1705,14 +1705,13 @@ public class DisplayModeDirector { } public void observe() { - DisplayManager dm = mContext.getSystemService(DisplayManager.class); - dm.registerDisplayListener(this, mHandler); + mInjector.registerDisplayListener(this, mHandler); // Populate existing displays SparseArray modes = new SparseArray<>(); SparseArray defaultModes = new SparseArray<>(); DisplayInfo info = new DisplayInfo(); - Display[] displays = dm.getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED); + Display[] displays = mInjector.getDisplays(); for (Display d : displays) { final int displayId = d.getDisplayId(); d.getDisplayInfo(info); @@ -1753,16 +1752,9 @@ public class DisplayModeDirector { @Nullable private DisplayInfo getDisplayInfo(int displayId) { - Display d = mContext.getSystemService(DisplayManager.class).getDisplay(displayId); - if (d == null) { - // We can occasionally get a display added or changed event for a display that was - // subsequently removed, which means this returns null. Check this case and bail - // out early; if it gets re-attached we'll eventually get another call back for it. - return null; - } DisplayInfo info = new DisplayInfo(); - d.getDisplayInfo(info); - return info; + // Display info might be invalid, in this case return null + return mInjector.getDisplayInfo(displayId, info) ? info : null; } private void updateLayoutLimitedFrameRate(int displayId, @Nullable DisplayInfo info) { @@ -2432,8 +2424,7 @@ public class DisplayModeDirector { } private void updateDefaultDisplayState() { - Display display = mContext.getSystemService(DisplayManager.class) - .getDisplay(Display.DEFAULT_DISPLAY); + Display display = mInjector.getDisplay(Display.DEFAULT_DISPLAY); if (display == null) { return; } @@ -2750,8 +2741,7 @@ public class DisplayModeDirector { sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this); synchronized (mSensorObserverLock) { - for (Display d : mDisplayManager.getDisplays( - DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) { + for (Display d : mInjector.getDisplays()) { mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d)); } } @@ -2762,8 +2752,7 @@ public class DisplayModeDirector { } private void recalculateVotesLocked() { - final Display[] displays = mDisplayManager.getDisplays( - DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED); + final Display[] displays = mInjector.getDisplays(); for (Display d : displays) { int displayId = d.getDisplayId(); Vote vote = null; @@ -2794,7 +2783,7 @@ public class DisplayModeDirector { @Override public void onDisplayAdded(int displayId) { - boolean isDozeState = mInjector.isDozeState(mDisplayManager.getDisplay(displayId)); + boolean isDozeState = mInjector.isDozeState(mInjector.getDisplay(displayId)); synchronized (mSensorObserverLock) { mDozeStateByDisplay.put(displayId, isDozeState); recalculateVotesLocked(); @@ -2806,7 +2795,7 @@ public class DisplayModeDirector { boolean wasDozeState = mDozeStateByDisplay.get(displayId); synchronized (mSensorObserverLock) { mDozeStateByDisplay.put(displayId, - mInjector.isDozeState(mDisplayManager.getDisplay(displayId))); + mInjector.isDozeState(mInjector.getDisplay(displayId))); if (wasDozeState != mDozeStateByDisplay.get(displayId)) { recalculateVotesLocked(); } @@ -3175,9 +3164,14 @@ public class DisplayModeDirector { void registerForcePeakRefreshRateObserver(@NonNull ContentResolver cr, @NonNull ContentObserver observer); + void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener, + Handler handler); + void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener, Handler handler, long flags); + Display getDisplay(int displayId); + Display[] getDisplays(); boolean getDisplayInfo(int displayId, DisplayInfo displayInfo); @@ -3220,12 +3214,23 @@ public class DisplayModeDirector { observer, UserHandle.USER_SYSTEM); } + @Override + public void registerDisplayListener(DisplayManager.DisplayListener listener, + Handler handler) { + getDisplayManager().registerDisplayListener(listener, handler); + } + @Override public void registerDisplayListener(DisplayManager.DisplayListener listener, Handler handler, long flags) { getDisplayManager().registerDisplayListener(listener, handler, flags); } + @Override + public Display getDisplay(int displayId) { + return getDisplayManager().getDisplay(displayId); + } + @Override public Display[] getDisplays() { return getDisplayManager().getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED); @@ -3234,10 +3239,13 @@ public class DisplayModeDirector { @Override public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) { Display display = getDisplayManager().getDisplay(displayId); - if (display != null) { - return display.getDisplayInfo(displayInfo); + if (display == null) { + // We can occasionally get a display added or changed event for a display that was + // subsequently removed, which means this returns null. Check this case and bail + // out early; if it gets re-attached we'll eventually get another call back for it. + return false; } - return false; + return display.getDisplayInfo(displayInfo); } @Override diff --git a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java index c04735d8f7e2d..8a3b329211cf7 100644 --- a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java +++ b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java @@ -138,7 +138,7 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme for (Display d : displays) { final int displayId = d.getDisplayId(); d.getDisplayInfo(info); - localMap.put(displayId, info.refreshRateThermalThrottling); + localMap.put(displayId, info.thermalRefreshRateThrottling); } synchronized (mThermalObserverLock) { for (int i = 0; i < size; i++) { @@ -154,7 +154,7 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme DisplayInfo displayInfo = new DisplayInfo(); mInjector.getDisplayInfo(displayId, displayInfo); SparseArray throttlingMap = - displayInfo.refreshRateThermalThrottling; + displayInfo.thermalRefreshRateThrottling; synchronized (mThermalObserverLock) { mThermalThrottlingByDisplay.put(displayId, throttlingMap); diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java index ff89be75c5d4f..5ea30298890fa 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java @@ -17,6 +17,8 @@ package com.android.server.display; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -26,6 +28,7 @@ import static org.mockito.Mockito.when; import android.app.PropertyInvalidatedCache; import android.graphics.Point; +import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; import android.view.Surface; @@ -47,6 +50,7 @@ public class LogicalDisplayTest { private static final int LAYER_STACK = 0; private static final int DISPLAY_WIDTH = 100; private static final int DISPLAY_HEIGHT = 200; + private static final int MODE_ID = 1; private LogicalDisplay mLogicalDisplay; private DisplayDevice mDisplayDevice; @@ -65,6 +69,9 @@ public class LogicalDisplayTest { mDisplayDeviceInfo.height = DISPLAY_HEIGHT; mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT; mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL; + mDisplayDeviceInfo.modeId = MODE_ID; + mDisplayDeviceInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID, + DISPLAY_WIDTH, DISPLAY_HEIGHT, /* refreshRate= */ 60)}; when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(mDisplayDeviceInfo); // Disable binder caches in this process. @@ -168,14 +175,34 @@ public class LogicalDisplayTest { } @Test - public void testLayoutLimitedRefreshRateNotClearedAfterUpdate() { - SurfaceControl.RefreshRateRange refreshRateRange = new SurfaceControl.RefreshRateRange(1, - 2); - mLogicalDisplay.updateLayoutLimitedRefreshRateLocked(refreshRateRange); - mLogicalDisplay.updateDisplayGroupIdLocked(1); + public void testUpdateLayoutLimitedRefreshRate() { + SurfaceControl.RefreshRateRange layoutLimitedRefreshRate = + new SurfaceControl.RefreshRateRange(0, 120); + DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked(); + mLogicalDisplay.updateLayoutLimitedRefreshRateLocked(layoutLimitedRefreshRate); + DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked(); + // Display info should only be updated when updateLocked is called + assertEquals(info2, info1); - DisplayInfo result = mLogicalDisplay.getDisplayInfoLocked(); + mLogicalDisplay.updateLocked(mDeviceRepo); + DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked(); + assertNotEquals(info3, info2); + assertEquals(layoutLimitedRefreshRate, info3.layoutLimitedRefreshRate); + } - assertEquals(refreshRateRange, result.layoutLimitedRefreshRate); + @Test + public void testUpdateRefreshRateThermalThrottling() { + SparseArray refreshRanges = new SparseArray<>(); + refreshRanges.put(0, new SurfaceControl.RefreshRateRange(0, 120)); + DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked(); + mLogicalDisplay.updateThermalRefreshRateThrottling(refreshRanges); + DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked(); + // Display info should only be updated when updateLocked is called + assertEquals(info2, info1); + + mLogicalDisplay.updateLocked(mDeviceRepo); + DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked(); + assertNotEquals(info3, info2); + assertTrue(refreshRanges.contentEquals(info3.thermalRefreshRateThrottling)); } } 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 42c1fd9e33219..1b393c45d1cd5 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 @@ -127,7 +127,8 @@ public class DisplayModeDirectorTest { private static final String TAG = "DisplayModeDirectorTest"; private static final boolean DEBUG = false; private static final float FLOAT_TOLERANCE = 0.01f; - private static final int DISPLAY_ID = 0; + private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY; + private static final int MODE_ID = 1; private static final float TRANSITION_POINT = 0.763f; private static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY; @@ -2644,6 +2645,53 @@ public class DisplayModeDirectorTest { assertNull(vote); } + @Test + public void testUpdateLayoutLimitedRefreshRate_validDisplayInfo() { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0); + director.start(createMockSensorManager()); + + ArgumentCaptor displayListenerCaptor = + ArgumentCaptor.forClass(DisplayListener.class); + verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(), + any(Handler.class)); + DisplayListener displayListener = displayListenerCaptor.getValue(); + + float refreshRate = 60; + mInjector.mDisplayInfo.layoutLimitedRefreshRate = + new RefreshRateRange(refreshRate, refreshRate); + displayListener.onDisplayChanged(DISPLAY_ID); + + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE); + assertVoteForPhysicalRefreshRate(vote, /* refreshRate= */ refreshRate); + + mInjector.mDisplayInfo.layoutLimitedRefreshRate = null; + displayListener.onDisplayChanged(DISPLAY_ID); + + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE); + assertNull(vote); + } + + @Test + public void testUpdateLayoutLimitedRefreshRate_invalidDisplayInfo() { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0); + director.start(createMockSensorManager()); + + ArgumentCaptor displayListenerCaptor = + ArgumentCaptor.forClass(DisplayListener.class); + verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(), + any(Handler.class)); + DisplayListener displayListener = displayListenerCaptor.getValue(); + + mInjector.mDisplayInfo.layoutLimitedRefreshRate = new RefreshRateRange(10, 10); + mInjector.mDisplayInfoValid = false; + displayListener.onDisplayChanged(DISPLAY_ID); + + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE); + assertNull(vote); + } + private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) { return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status); } @@ -2850,12 +2898,20 @@ public class DisplayModeDirectorTest { public static class FakesInjector implements DisplayModeDirector.Injector { private final FakeDeviceConfig mDeviceConfig; + private final DisplayInfo mDisplayInfo; + private final Display mDisplay; + private boolean mDisplayInfoValid = true; private ContentObserver mBrightnessObserver; private ContentObserver mSmoothDisplaySettingObserver; private ContentObserver mForcePeakRefreshRateSettingObserver; FakesInjector() { mDeviceConfig = new FakeDeviceConfig(); + mDisplayInfo = new DisplayInfo(); + mDisplayInfo.defaultModeId = MODE_ID; + mDisplayInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID, + 800, 600, /* refreshRate= */ 60)}; + mDisplay = createDisplay(DISPLAY_ID); } @NonNull @@ -2875,17 +2931,26 @@ public class DisplayModeDirectorTest { mForcePeakRefreshRateSettingObserver = observer; } + @Override + public void registerDisplayListener(DisplayListener listener, Handler handler) {} + @Override public void registerDisplayListener(DisplayListener listener, Handler handler, long flag) {} + @Override + public Display getDisplay(int displayId) { + return mDisplay; + } + @Override public Display[] getDisplays() { - return new Display[] { createDisplay(DISPLAY_ID) }; + return new Display[] { mDisplay }; } @Override public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) { - return false; + displayInfo.copyFrom(mDisplayInfo); + return mDisplayInfoValid; } @Override @@ -2909,7 +2974,7 @@ public class DisplayModeDirectorTest { } protected Display createDisplay(int id) { - return new Display(DisplayManagerGlobal.getInstance(), id, new DisplayInfo(), + return new Display(DisplayManagerGlobal.getInstance(), id, mDisplayInfo, ApplicationProvider.getApplicationContext().getResources()); } 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 index fd1889ca09821..13540d60b9305 100644 --- a/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java +++ b/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java @@ -253,7 +253,7 @@ public class SkinThermalStatusObserverTest { public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) { SparseArray config = mOverriddenConfig.get(displayId); if (config != null) { - displayInfo.refreshRateThermalThrottling = config; + displayInfo.thermalRefreshRateThrottling = config; return true; } return false;