Use setFrameRate for high refresh rate deny list

Add visibility to SurfaceFlinger into the high refresh rate deny list
and let SurfaceFlinger handle it. Previously WM was setting the
preferredDisplayModeId on the denied app's window. The old way prevented
SurfaceFlinger to use the frame rate override feature as it didn't
know that a specific app is causing the refresh rate spec to be limited.

With this change, SurfaceFlinger will limit the display refresh rate
based on the high refresh rate deny list, and if possible, will use the
frame rate override feature to change the display rate to a multiple,
allowing other animations to be smooth while the denied app remains in
the low refresh rate.

Bug: 170502573
Test: manual
Change-Id: Ib75a3c229cea298b65aa56dc1c1b20ca016059c4
This commit is contained in:
Ady Abraham
2021-01-07 18:00:49 -08:00
parent 5682f5657b
commit 3f0856b41a
8 changed files with 160 additions and 22 deletions

View File

@@ -547,16 +547,17 @@ public final class DisplayInfo implements Parcelable {
* Returns the id of the "default" mode with the given refresh rate, or {@code 0} if no suitable
* mode could be found.
*/
public int findDefaultModeByRefreshRate(float refreshRate) {
@Nullable
public Display.Mode findDefaultModeByRefreshRate(float refreshRate) {
Display.Mode[] modes = supportedModes;
Display.Mode defaultMode = getDefaultMode();
for (int i = 0; i < modes.length; i++) {
if (modes[i].matches(
defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), refreshRate)) {
return modes[i].getModeId();
return modes[i];
}
}
return 0;
return null;
}
/**

View File

@@ -217,6 +217,15 @@ public class Surface implements Parcelable {
*/
public static final int FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1;
/**
* This surface belongs to an app on the High Refresh Rate Deny list, and needs the display
* to operate at the exact frame rate.
*
* This is used internally by the platform and should not be used by apps.
* @hide
*/
public static final int FRAME_RATE_COMPATIBILITY_EXACT = 100;
/**
* Create an empty surface, which will later be filled in by readFromParcel().
* @hide

View File

@@ -1377,7 +1377,7 @@ public final class DisplayManagerService extends SystemService {
// Scan supported modes returned by display.getInfo() to find a mode with the same
// size as the default display mode but with the specified refresh rate instead.
requestedModeId = display.getDisplayInfoLocked().findDefaultModeByRefreshRate(
requestedRefreshRate);
requestedRefreshRate).getModeId();
}
mDisplayModeDirector.getAppRequestObserver().setAppRequestedMode(
displayId, requestedModeId);

View File

@@ -28,7 +28,7 @@ import android.view.DisplayInfo;
*/
class RefreshRatePolicy {
private final int mLowRefreshRateId;
private final Mode mLowRefreshRateMode;
private final ArraySet<String> mNonHighRefreshRatePackages = new ArraySet<>();
private final HighRefreshRateDenylist mHighRefreshRateDenylist;
private final WindowManagerService mWmService;
@@ -56,7 +56,7 @@ class RefreshRatePolicy {
RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo,
HighRefreshRateDenylist denylist) {
mLowRefreshRateId = findLowRefreshRateModeId(displayInfo);
mLowRefreshRateMode = findLowRefreshRateMode(displayInfo);
mHighRefreshRateDenylist = denylist;
mWmService = wmService;
}
@@ -65,7 +65,7 @@ class RefreshRatePolicy {
* Finds the mode id with the lowest refresh rate which is >= 60hz and same resolution as the
* default mode.
*/
private int findLowRefreshRateModeId(DisplayInfo displayInfo) {
private Mode findLowRefreshRateMode(DisplayInfo displayInfo) {
Mode mode = displayInfo.getDefaultMode();
float[] refreshRates = displayInfo.getDefaultRefreshRates();
float bestRefreshRate = mode.getRefreshRate();
@@ -104,13 +104,9 @@ class RefreshRatePolicy {
// If app is using Camera, force it to default (lower) refresh rate.
if (mNonHighRefreshRatePackages.contains(packageName)) {
return mLowRefreshRateId;
return mLowRefreshRateMode.getModeId();
}
// If app is denylisted using higher refresh rate, return default (lower) refresh rate
if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
return mLowRefreshRateId;
}
return 0;
}
@@ -137,4 +133,18 @@ class RefreshRatePolicy {
}
return LAYER_PRIORITY_UNSET;
}
float getPreferredRefreshRate(WindowState w) {
// If app is animating, it's not able to control refresh rate because we want the animation
// to run in default refresh rate.
if (w.isAnimating(TRANSITION | PARENTS)) {
return 0;
}
final String packageName = w.getOwningPackage();
if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
return mLowRefreshRateMode.getRefreshRate();
}
return 0;
}
}

View File

@@ -233,6 +233,7 @@ import android.view.InputWindowHandle;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
import android.view.Surface;
import android.view.Surface.Rotation;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
@@ -730,6 +731,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
*/
int mFrameRateSelectionPriority = RefreshRatePolicy.LAYER_PRIORITY_UNSET;
/**
* This is the frame rate which is passed to SurfaceFlinger if the window is part of the
* high refresh rate deny list. The variable is cached, so we do not send too many updates to
* SF.
*/
float mDenyListFrameRate = 0f;
static final int BLAST_TIMEOUT_DURATION = 5000; /* milliseconds */
private final WindowProcessController mWpcForDisplayAreaConfigChanges;
@@ -5255,7 +5263,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return (mAttrs.flags & FLAG_BLUR_BEHIND) != 0 && mOwnerCanUseBackgroundBlur;
}
/**
* Notifies SF about the priority of the window, if it changed. SF then uses this information
* to decide which window's desired rendering rate should have a priority when deciding about
@@ -5264,13 +5271,21 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
*/
@VisibleForTesting
void updateFrameRateSelectionPriorityIfNeeded() {
final int priority = getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
.calculatePriority(this);
RefreshRatePolicy refreshRatePolicy =
getDisplayContent().getDisplayPolicy().getRefreshRatePolicy();
final int priority = refreshRatePolicy.calculatePriority(this);
if (mFrameRateSelectionPriority != priority) {
mFrameRateSelectionPriority = priority;
getPendingTransaction().setFrameRateSelectionPriority(mSurfaceControl,
mFrameRateSelectionPriority);
}
final float refreshRate = refreshRatePolicy.getPreferredRefreshRate(this);
if (mDenyListFrameRate != refreshRate) {
mDenyListFrameRate = refreshRate;
getPendingTransaction().setFrameRate(
mSurfaceControl, mDenyListFrameRate, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
}
}
private void updateGlobalScaleIfNeeded() {

View File

@@ -18,15 +18,24 @@ package com.android.server.wm;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.platform.test.annotations.Presubmit;
import android.view.Display.Mode;
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.SurfaceControl;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,16 +49,40 @@ import org.junit.runner.RunWith;
@Presubmit
@RunWith(WindowTestRunner.class)
public class FrameRateSelectionPriorityTests extends WindowTestsBase {
private static final float FLOAT_TOLERANCE = 0.01f;
private static final int LOW_MODE_ID = 3;
private DisplayPolicy mDisplayPolicy = mock(DisplayPolicy.class);
private RefreshRatePolicy mRefreshRatePolicy;
private HighRefreshRateDenylist mDenylist = mock(HighRefreshRateDenylist.class);
@Before
public void setUp() {
DisplayInfo di = new DisplayInfo(mDisplayInfo);
Mode defaultMode = di.getDefaultMode();
di.supportedModes = new Mode[] {
new Mode(1, defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 90),
new Mode(2, defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 70),
new Mode(LOW_MODE_ID,
defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 60),
};
di.defaultModeId = 1;
mRefreshRatePolicy = new RefreshRatePolicy(mWm, di, mDenylist);
when(mDisplayPolicy.getRefreshRatePolicy()).thenReturn(mRefreshRatePolicy);
}
@Test
public void basicTest() {
final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
assertNotNull("Window state is created", appWindow);
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority doesn't change.
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
// Call the function a few times.
appWindow.updateFrameRateSelectionPriorityIfNeeded();
@@ -57,7 +90,9 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
// Since nothing changed in the priority state, the transaction should not be updating.
verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
any(SurfaceControl.class), anyInt());
verify(appWindow.getPendingTransaction(), never()).setFrameRate(
any(SurfaceControl.class), anyInt(), anyInt());
}
@Test
@@ -66,10 +101,16 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
.getPreferredModeId(appWindow), 0);
assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
.getPreferredRefreshRate(appWindow), 0, FLOAT_TOLERANCE);
assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority stays MAX_VALUE.
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
@@ -78,31 +119,38 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority changes to 1.
assertEquals(appWindow.mFrameRateSelectionPriority, 1);
assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), 1);
verify(appWindow.getPendingTransaction(), never()).setFrameRate(
any(SurfaceControl.class), anyInt(), anyInt());
}
@Test
public void testApplicationInFocusWithModeId() {
final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
// Application is in focus.
appWindow.mToken.mDisplayContent.mCurrentFocus = appWindow;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority changes.
assertEquals(appWindow.mFrameRateSelectionPriority, 1);
assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
// Update the mode ID to a requested number.
appWindow.mAttrs.preferredDisplayModeId = 1;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority changes.
assertEquals(appWindow.mFrameRateSelectionPriority, 0);
assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
// Remove the mode ID request.
appWindow.mAttrs.preferredDisplayModeId = 0;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority changes.
assertEquals(appWindow.mFrameRateSelectionPriority, 1);
assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
// Verify we called actions on Transactions correctly.
verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
@@ -111,12 +159,15 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
appWindow.getSurfaceControl(), 0);
verify(appWindow.getPendingTransaction(), times(2)).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), 1);
verify(appWindow.getPendingTransaction(), never()).setFrameRate(
any(SurfaceControl.class), anyInt(), anyInt());
}
@Test
public void testApplicationNotInFocusWithModeId() {
final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
final WindowState inFocusWindow = createWindow(null, TYPE_APPLICATION, "inFocus");
appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
@@ -124,23 +175,28 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// The window is not in focus.
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
// Update the mode ID to a requested number.
appWindow.mAttrs.preferredDisplayModeId = 1;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority changes.
assertEquals(appWindow.mFrameRateSelectionPriority, 2);
assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), 2);
verify(appWindow.getPendingTransaction(), never()).setFrameRate(
any(SurfaceControl.class), anyInt(), anyInt());
}
@Test
public void testApplicationNotInFocusWithoutModeId() {
final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
final WindowState inFocusWindow = createWindow(null, TYPE_APPLICATION, "inFocus");
appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
@@ -148,14 +204,45 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// The window is not in focus.
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
// Make sure that the mode ID is not set.
appWindow.mAttrs.preferredDisplayModeId = 0;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority doesn't change.
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
verify(appWindow.getPendingTransaction(), never()).setFrameRate(
any(SurfaceControl.class), anyInt(), anyInt());
}
@Test
public void testPreferredRefreshRate() {
final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
assertNotNull("Window state is created", appWindow);
when(appWindow.getDisplayContent().getDisplayPolicy()).thenReturn(mDisplayPolicy);
appWindow.mAttrs.packageName = "com.android.test";
when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
assertEquals(0, mRefreshRatePolicy.getPreferredModeId(appWindow));
assertEquals(60, mRefreshRatePolicy.getPreferredRefreshRate(appWindow), FLOAT_TOLERANCE);
appWindow.updateFrameRateSelectionPriorityIfNeeded();
assertEquals(RefreshRatePolicy.LAYER_PRIORITY_UNSET, appWindow.mFrameRateSelectionPriority);
assertEquals(60, appWindow.mDenyListFrameRate, FLOAT_TOLERANCE);
// Call the function a few times.
appWindow.updateFrameRateSelectionPriorityIfNeeded();
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Since nothing changed in the priority state, the transaction should not be updating.
verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
any(SurfaceControl.class), anyInt());
verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
appWindow.getSurfaceControl(), 60, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
}
}

View File

@@ -44,7 +44,7 @@ import org.junit.runner.RunWith;
@RunWith(WindowTestRunner.class)
@FlakyTest
public class RefreshRatePolicyTest extends WindowTestsBase {
private static final float FLOAT_TOLERANCE = 0.01f;
private static final int LOW_MODE_ID = 3;
private RefreshRatePolicy mPolicy;
@@ -70,28 +70,34 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
"cameraUsingWindow");
cameraUsingWindow.mAttrs.packageName = "com.android.test";
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
mPolicy.addNonHighRefreshRatePackage("com.android.test");
assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(cameraUsingWindow));
assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
mPolicy.removeNonHighRefreshRatePackage("com.android.test");
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
}
@Test
public void testBlacklist() {
final WindowState blacklistedWindow = createWindow(null, TYPE_BASE_APPLICATION,
"blacklistedWindow");
blacklistedWindow.mAttrs.packageName = "com.android.test";
public void testDenyList() {
final WindowState denylistedWindow = createWindow(null, TYPE_BASE_APPLICATION,
"denylistedWindow");
denylistedWindow.mAttrs.packageName = "com.android.test";
when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(blacklistedWindow));
assertEquals(0, mPolicy.getPreferredModeId(denylistedWindow));
assertEquals(60, mPolicy.getPreferredRefreshRate(denylistedWindow), FLOAT_TOLERANCE);
}
@Test
public void testAppOverride_blacklist() {
final WindowState overrideWindow = createWindow(null, TYPE_BASE_APPLICATION,
"overrideWindow");
overrideWindow.mAttrs.packageName = "com.android.test";
overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID;
when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
assertEquals(60, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
}
@Test
@@ -102,6 +108,7 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID;
mPolicy.addNonHighRefreshRatePackage("com.android.test");
assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
}
@Test
@@ -115,6 +122,7 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
mPolicy.addNonHighRefreshRatePackage("com.android.test");
assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
}
@Test
@@ -125,10 +133,12 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
mPolicy.addNonHighRefreshRatePackage("com.android.test");
assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(cameraUsingWindow));
assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
cameraUsingWindow.mActivityRecord.mSurfaceAnimator.startAnimation(
cameraUsingWindow.getPendingTransaction(), mock(AnimationAdapter.class),
false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
}
}

View File

@@ -249,6 +249,12 @@ public class StubTransaction extends SurfaceControl.Transaction {
return this;
}
@Override
public SurfaceControl.Transaction setFrameRate(SurfaceControl sc, float frameRate,
int compatibility) {
return this;
}
@Override
public SurfaceControl.Transaction unsetColor(SurfaceControl sc) {
return this;