Merge "Read the high refresh rate blacklist from DeviceConfig." into qt-r1-dev

This commit is contained in:
TreeHugger Robot
2019-07-12 03:22:35 +00:00
committed by Android (Google) Code Review
6 changed files with 225 additions and 71 deletions

View File

@@ -336,6 +336,15 @@ public final class DeviceConfig {
@TestApi
String KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE =
"system_gestures_excluded_by_pre_q_sticky_immersive";
/**
* Key for controlling which packages are explicitly blocked from running at refresh rates
* higher than 60hz.
*
* @see android.provider.DeviceConfig#NAMESPACE_WINDOW_MANAGER
* @hide
*/
String KEY_HIGH_REFRESH_RATE_BLACKLIST = "high_refresh_rate_blacklist";
}
private static final Object sLock = new Object();

View File

@@ -4208,4 +4208,8 @@
<!-- Sharesheet: define a max number of targets per application for new shortcuts-based direct share introduced in Q -->
<integer name="config_maxShortcutTargetsPerApp">3</integer>
<!-- The list of packages to automatically opt out of refresh rates higher than 60hz because
of known compatibility issues. -->
<string-array name="config_highRefreshRateBlacklist"></string-array>
</resources>

View File

@@ -3822,6 +3822,8 @@
<java-symbol type="string" name="config_defaultSupervisionProfileOwnerComponent" />
<java-symbol type="bool" name="config_inflateSignalStrength" />
<java-symbol type="array" name="config_highRefreshRateBlacklist" />
<java-symbol type="drawable" name="android_logotype" />
<java-symbol type="layout" name="platlogo_layout" />

View File

@@ -16,60 +16,102 @@
package com.android.server.wm;
import static android.provider.DeviceConfig.WindowManager.KEY_HIGH_REFRESH_RATE_BLACKLIST;
import android.annotation.NonNull;
import android.os.SystemProperties;
import android.annotation.Nullable;
import android.content.res.Resources;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
/**
* A Blacklist for packages that should force the display out of high refresh rate.
*/
class HighRefreshRateBlacklist {
private static final String SYSPROP_KEY = "ro.window_manager.high_refresh_rate_blacklist";
private static final String SYSPROP_KEY_LENGTH_SUFFIX = "_length";
private static final String SYSPROP_KEY_ENTRY_SUFFIX = "_entry";
private static final int MAX_ENTRIES = 50;
private final ArraySet<String> mBlacklistedPackages = new ArraySet<>();
@NonNull
private final String[] mDefaultBlacklist;
private final Object mLock = new Object();
private ArraySet<String> mBlacklistedPackages = new ArraySet<>();
static HighRefreshRateBlacklist create() {
return new HighRefreshRateBlacklist(new SystemPropertyGetter() {
static HighRefreshRateBlacklist create(@NonNull Resources r) {
return new HighRefreshRateBlacklist(r, new DeviceConfigInterface() {
@Override
public int getInt(String key, int def) {
return SystemProperties.getInt(key, def);
public @Nullable String getProperty(@NonNull String namespace, @NonNull String name) {
return DeviceConfig.getProperty(namespace, name);
}
@Override
public String get(String key) {
return SystemProperties.get(key);
public void addOnPropertyChangedListener(@NonNull String namespace,
@NonNull Executor executor,
@NonNull DeviceConfig.OnPropertyChangedListener listener) {
DeviceConfig.addOnPropertyChangedListener(namespace, executor, listener);
}
});
}
@VisibleForTesting
HighRefreshRateBlacklist(SystemPropertyGetter propertyGetter) {
HighRefreshRateBlacklist(Resources r, DeviceConfigInterface deviceConfig) {
mDefaultBlacklist = r.getStringArray(R.array.config_highRefreshRateBlacklist);
deviceConfig.addOnPropertyChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
BackgroundThread.getExecutor(), new OnPropertyChangedListener());
final String property = deviceConfig.getProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
KEY_HIGH_REFRESH_RATE_BLACKLIST);
updateBlacklist(property);
}
// Read and populate the blacklist
final int length = Math.min(
propertyGetter.getInt(SYSPROP_KEY + SYSPROP_KEY_LENGTH_SUFFIX, 0),
MAX_ENTRIES);
for (int i = 1; i <= length; i++) {
final String packageName = propertyGetter.get(
SYSPROP_KEY + SYSPROP_KEY_ENTRY_SUFFIX + i);
if (!packageName.isEmpty()) {
mBlacklistedPackages.add(packageName);
private void updateBlacklist(@Nullable String property) {
synchronized (mLock) {
mBlacklistedPackages.clear();
if (property != null) {
String[] packages = property.split(",");
for (String pkg : packages) {
String pkgName = pkg.trim();
if (!pkgName.isEmpty()) {
mBlacklistedPackages.add(pkgName);
}
}
} else {
// If there's no config, or the config has been deleted, fallback to the device's
// default blacklist
for (String pkg : mDefaultBlacklist) {
mBlacklistedPackages.add(pkg);
}
}
}
}
boolean isBlacklisted(String packageName) {
return mBlacklistedPackages.contains(packageName);
synchronized (mLock) {
return mBlacklistedPackages.contains(packageName);
}
}
void dump(PrintWriter pw) {
pw.println("High Refresh Rate Blacklist");
pw.println(" Packages:");
synchronized (mLock) {
for (String pkg : mBlacklistedPackages) {
pw.println(" " + pkg);
}
}
}
interface SystemPropertyGetter {
int getInt(String key, int def);
@NonNull String get(String key);
interface DeviceConfigInterface {
@Nullable String getProperty(@NonNull String namespace, @NonNull String name);
void addOnPropertyChangedListener(@NonNull String namespace, @NonNull Executor executor,
@NonNull DeviceConfig.OnPropertyChangedListener listener);
}
private class OnPropertyChangedListener implements DeviceConfig.OnPropertyChangedListener {
public void onPropertyChanged(@NonNull String namespace, @NonNull String name,
@Nullable String value) {
updateBlacklist(value);
}
}
}

View File

@@ -854,7 +854,7 @@ public class WindowManagerService extends IWindowManager.Stub
final Configuration mTempConfiguration = new Configuration();
final HighRefreshRateBlacklist mHighRefreshRateBlacklist = HighRefreshRateBlacklist.create();
final HighRefreshRateBlacklist mHighRefreshRateBlacklist;
// If true, only the core apps and services are being launched because the device
// is in a special boot mode, such as being encrypted or waiting for a decryption password.
@@ -1141,6 +1141,8 @@ public class WindowManagerService extends IWindowManager.Stub
this, mInputManager, mActivityTaskManager, mH.getLooper());
mDragDropController = new DragDropController(this, mH.getLooper());
mHighRefreshRateBlacklist = HighRefreshRateBlacklist.create(context.getResources());
mSystemGestureExclusionLimitDp = Math.max(MIN_GESTURE_EXCLUSION_LIMIT_DP,
DeviceConfig.getInt(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, 0));
@@ -5922,6 +5924,12 @@ public class WindowManagerService extends IWindowManager.Stub
mRoot.dumpTokens(pw, dumpAll);
}
private void dumpHighRefreshRateBlacklist(PrintWriter pw) {
pw.println("WINDOW MANAGER HIGH REFRESH RATE BLACKLIST (dumpsys window refresh)");
mHighRefreshRateBlacklist.dump(pw);
}
private void dumpTraceStatus(PrintWriter pw) {
pw.println("WINDOW MANAGER TRACE (dumpsys window trace)");
pw.print(mWindowTracing.getStatus() + "\n");
@@ -6321,6 +6329,9 @@ public class WindowManagerService extends IWindowManager.Stub
} else if ("trace".equals(cmd)) {
dumpTraceStatus(pw);
return;
} else if ("refresh".equals(cmd)) {
dumpHighRefreshRateBlacklist(pw);
return;
} else {
// Dumping a single name?
if (!dumpWindows(pw, cmd, args, opti, dumpAll)) {
@@ -6376,6 +6387,10 @@ public class WindowManagerService extends IWindowManager.Stub
pw.println(separator);
}
dumpTraceStatus(pw);
if (dumpAll) {
pw.println(separator);
}
dumpHighRefreshRateBlacklist(pw);
}
}

View File

@@ -16,71 +16,153 @@
package com.android.server.wm;
import static android.provider.DeviceConfig.WindowManager.KEY_HIGH_REFRESH_RATE_BLACKLIST;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.res.Resources;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.util.Pair;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import com.android.server.wm.HighRefreshRateBlacklist.SystemPropertyGetter;
import com.android.internal.R;
import com.android.server.wm.HighRefreshRateBlacklist.DeviceConfigInterface;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
* Build/Install/Run:
* atest WmTests:HighRefreshRateBlacklistTest
* atest WmTests:HighRefreshRateBlacklistTest
*/
@SmallTest
@Presubmit
@FlakyTest
public class HighRefreshRateBlacklistTest {
@Test
public void testBlacklist() {
HighRefreshRateBlacklist blacklist = new HighRefreshRateBlacklist(
new SystemPropertyGetter() {
@Override
public int getInt(String key, int def) {
if ("ro.window_manager.high_refresh_rate_blacklist_length".equals(key)) {
return 2;
}
return def;
}
@Override
public String get(String key) {
if ("ro.window_manager.high_refresh_rate_blacklist_entry1".equals(key)) {
return "com.android.sample1";
}
if ("ro.window_manager.high_refresh_rate_blacklist_entry2".equals(key)) {
return "com.android.sample2";
}
return "";
}
});
public void testDefaultBlacklist() {
final Resources r = createResources("com.android.sample1", "com.android.sample2");
HighRefreshRateBlacklist blacklist =
new HighRefreshRateBlacklist(r, new FakeDeviceConfigInterface());
assertTrue(blacklist.isBlacklisted("com.android.sample1"));
assertTrue(blacklist.isBlacklisted("com.android.sample2"));
assertFalse(blacklist.isBlacklisted("com.android.sample3"));
}
@Test
public void testNoBlacklist() {
HighRefreshRateBlacklist blacklist = new HighRefreshRateBlacklist(
new SystemPropertyGetter() {
@Override
public int getInt(String key, int def) {
return def;
}
@Override
public String get(String key) {
return "";
}
});
public void testNoDefaultBlacklist() {
final Resources r = createResources();
HighRefreshRateBlacklist blacklist =
new HighRefreshRateBlacklist(r, new FakeDeviceConfigInterface());
assertFalse(blacklist.isBlacklisted("com.android.sample1"));
}
@Test
public void testDefaultBlacklistIsOverriddenByDeviceConfig() {
final Resources r = createResources("com.android.sample1");
final FakeDeviceConfigInterface config = new FakeDeviceConfigInterface();
config.setBlacklist("com.android.sample2,com.android.sample3");
HighRefreshRateBlacklist blacklist = new HighRefreshRateBlacklist(r, config);
assertFalse(blacklist.isBlacklisted("com.android.sample1"));
assertTrue(blacklist.isBlacklisted("com.android.sample2"));
assertTrue(blacklist.isBlacklisted("com.android.sample3"));
}
@Test
public void testDefaultBlacklistIsOverriddenByEmptyDeviceConfig() {
final Resources r = createResources("com.android.sample1");
final FakeDeviceConfigInterface config = new FakeDeviceConfigInterface();
config.setBlacklist("");
HighRefreshRateBlacklist blacklist = new HighRefreshRateBlacklist(r, config);
assertFalse(blacklist.isBlacklisted("com.android.sample1"));
}
@Test
public void testDefaultBlacklistIsOverriddenByDeviceConfigUpdate() {
final Resources r = createResources("com.android.sample1");
final FakeDeviceConfigInterface config = new FakeDeviceConfigInterface();
HighRefreshRateBlacklist blacklist = new HighRefreshRateBlacklist(r, config);
// First check that the default blacklist is in effect
assertTrue(blacklist.isBlacklisted("com.android.sample1"));
assertFalse(blacklist.isBlacklisted("com.android.sample2"));
assertFalse(blacklist.isBlacklisted("com.android.sample3"));
// Then confirm that the DeviceConfig list has propagated and taken effect.
config.setBlacklist("com.android.sample2,com.android.sample3");
assertFalse(blacklist.isBlacklisted("com.android.sample1"));
assertTrue(blacklist.isBlacklisted("com.android.sample2"));
assertTrue(blacklist.isBlacklisted("com.android.sample3"));
// Finally make sure we go back to the default list if the DeviceConfig gets deleted.
config.setBlacklist(null);
assertTrue(blacklist.isBlacklisted("com.android.sample1"));
assertFalse(blacklist.isBlacklisted("com.android.sample2"));
assertFalse(blacklist.isBlacklisted("com.android.sample3"));
}
private Resources createResources(String... defaultBlacklist) {
Resources r = mock(Resources.class);
when(r.getStringArray(R.array.config_highRefreshRateBlacklist))
.thenReturn(defaultBlacklist);
return r;
}
class FakeDeviceConfigInterface implements DeviceConfigInterface {
private List<Pair<DeviceConfig.OnPropertyChangedListener, Executor>> mListeners =
new ArrayList<>();
private String mBlacklist;
@Override
public String getProperty(String namespace, String name) {
if (!DeviceConfig.NAMESPACE_WINDOW_MANAGER.equals(namespace)
|| !KEY_HIGH_REFRESH_RATE_BLACKLIST.equals(name)) {
throw new IllegalArgumentException("Only things in NAMESPACE_WINDOW_MANAGER "
+ "supported.");
}
return mBlacklist;
}
@Override
public void addOnPropertyChangedListener(String namespace, Executor executor,
DeviceConfig.OnPropertyChangedListener listener) {
if (!DeviceConfig.NAMESPACE_WINDOW_MANAGER.equals(namespace)) {
throw new IllegalArgumentException("Only things in NAMESPACE_WINDOW_MANAGER "
+ "supported.");
}
mListeners.add(new Pair<>(listener, executor));
}
void setBlacklist(String blacklist) {
mBlacklist = blacklist;
CountDownLatch latch = new CountDownLatch(mListeners.size());
for (Pair<DeviceConfig.OnPropertyChangedListener, Executor> listenerInfo :
mListeners) {
final Executor executor = listenerInfo.second;
final DeviceConfig.OnPropertyChangedListener listener = listenerInfo.first;
executor.execute(() -> {
listener.onPropertyChanged(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
KEY_HIGH_REFRESH_RATE_BLACKLIST, blacklist);
latch.countDown();
});
}
try {
latch.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException("Failed to notify all blacklist listeners in time.", e);
}
}
}
}