Merge "Persist compat framework overrides across reboot"

This commit is contained in:
Andrei-Valentin Onea
2021-01-27 18:17:11 +00:00
committed by Gerrit Code Review
16 changed files with 377 additions and 11 deletions

View File

@@ -84,6 +84,7 @@ java_library_static {
":storaged_aidl",
":vold_aidl",
":platform-compat-config",
":platform-compat-overrides",
":display-device-config",
"java/com/android/server/EventLogTags.logtags",
"java/com/android/server/am/EventLogTags.logtags",

View File

@@ -23,8 +23,11 @@ import android.content.pm.ApplicationInfo;
import com.android.internal.compat.CompatibilityChangeInfo;
import com.android.server.compat.config.Change;
import com.android.server.compat.overrides.ChangeOverrides;
import com.android.server.compat.overrides.OverrideValue;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@@ -253,6 +256,71 @@ public final class CompatChange extends CompatibilityChangeInfo {
return mDeferredOverrides != null && mDeferredOverrides.containsKey(packageName);
}
/**
* Checks whether a change has any package overrides.
* @return true if the change has at least one deferred override
*/
boolean hasAnyPackageOverride() {
return mDeferredOverrides != null && !mDeferredOverrides.isEmpty();
}
/**
* Checks whether a change has any deferred overrides.
* @return true if the change has at least one deferred override
*/
boolean hasAnyDeferredOverride() {
return mPackageOverrides != null && !mPackageOverrides.isEmpty();
}
void loadOverrides(ChangeOverrides changeOverrides) {
if (mDeferredOverrides == null) {
mDeferredOverrides = new HashMap<>();
}
mDeferredOverrides.clear();
for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) {
mDeferredOverrides.put(override.getPackageName(), override.getEnabled());
}
if (mPackageOverrides == null) {
mPackageOverrides = new HashMap<>();
}
mPackageOverrides.clear();
for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) {
mPackageOverrides.put(override.getPackageName(), override.getEnabled());
}
}
ChangeOverrides saveOverrides() {
if (!hasAnyDeferredOverride() && !hasAnyPackageOverride()) {
return null;
}
ChangeOverrides changeOverrides = new ChangeOverrides();
changeOverrides.setChangeId(getId());
ChangeOverrides.Deferred deferredOverrides = new ChangeOverrides.Deferred();
List<OverrideValue> deferredList = deferredOverrides.getOverrideValue();
if (mDeferredOverrides != null) {
for (Map.Entry<String, Boolean> entry : mDeferredOverrides.entrySet()) {
OverrideValue override = new OverrideValue();
override.setPackageName(entry.getKey());
override.setEnabled(entry.getValue());
deferredList.add(override);
}
}
changeOverrides.setDeferred(deferredOverrides);
ChangeOverrides.Validated validatedOverrides = new ChangeOverrides.Validated();
List<OverrideValue> validatedList = validatedOverrides.getOverrideValue();
if (mPackageOverrides != null) {
for (Map.Entry<String, Boolean> entry : mPackageOverrides.entrySet()) {
OverrideValue override = new OverrideValue();
override.setPackageName(entry.getKey());
override.setEnabled(entry.getValue());
validatedList.add(override);
}
}
changeOverrides.setValidated(validatedOverrides);
return changeOverrides;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("ChangeId(")

View File

@@ -34,7 +34,10 @@ import com.android.internal.compat.CompatibilityChangeInfo;
import com.android.internal.compat.IOverrideValidator;
import com.android.internal.compat.OverrideAllowedState;
import com.android.server.compat.config.Change;
import com.android.server.compat.config.XmlParser;
import com.android.server.compat.config.Config;
import com.android.server.compat.overrides.ChangeOverrides;
import com.android.server.compat.overrides.Overrides;
import com.android.server.compat.overrides.XmlWriter;
import com.android.server.pm.ApexManager;
import org.xmlpull.v1.XmlPullParserException;
@@ -60,11 +63,14 @@ import javax.xml.datatype.DatatypeConfigurationException;
final class CompatConfig {
private static final String TAG = "CompatConfig";
private static final String APP_COMPAT_DATA_DIR = "/data/misc/appcompat";
private static final String OVERRIDES_FILE = "compat_framework_overrides.xml";
@GuardedBy("mChanges")
private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>();
private final OverrideValidatorImpl mOverrideValidator;
private File mOverridesFile;
@VisibleForTesting
CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) {
@@ -83,6 +89,8 @@ final class CompatConfig {
config.initConfigFromLib(Environment.buildPath(
apex.apexDirectory, "etc", "compatconfig"));
}
File overridesFile = new File(APP_COMPAT_DATA_DIR, OVERRIDES_FILE);
config.initOverrides(overridesFile);
config.invalidateCache();
return config;
}
@@ -202,6 +210,17 @@ final class CompatConfig {
* @throws IllegalStateException if overriding is not allowed
*/
boolean addOverride(long changeId, String packageName, boolean enabled) {
boolean alreadyKnown = addOverrideUnsafe(changeId, packageName, enabled);
saveOverrides();
invalidateCache();
return alreadyKnown;
}
/**
* Unsafe version of {@link #addOverride(long, String, boolean)}.
* It does not invalidate the cache nor save the overrides.
*/
private boolean addOverrideUnsafe(long changeId, String packageName, boolean enabled) {
boolean alreadyKnown = true;
OverrideAllowedState allowedState =
mOverrideValidator.getOverrideAllowedState(changeId, packageName);
@@ -224,7 +243,6 @@ final class CompatConfig {
throw new IllegalStateException("Should only be able to override changes that "
+ "are allowed or can be deferred.");
}
invalidateCache();
}
return alreadyKnown;
}
@@ -282,6 +300,17 @@ final class CompatConfig {
* @return {@code true} if an override existed;
*/
boolean removeOverride(long changeId, String packageName) {
boolean overrideExists = removeOverrideUnsafe(changeId, packageName);
saveOverrides();
invalidateCache();
return overrideExists;
}
/**
* Unsafe version of {@link #removeOverride(long, String)}.
* It does not invalidate the cache nor save the overrides.
*/
private boolean removeOverrideUnsafe(long changeId, String packageName) {
boolean overrideExists = false;
synchronized (mChanges) {
CompatChange c = mChanges.get(changeId);
@@ -300,7 +329,6 @@ final class CompatConfig {
}
}
}
invalidateCache();
return overrideExists;
}
@@ -315,12 +343,13 @@ final class CompatConfig {
void addOverrides(CompatibilityChangeConfig overrides, String packageName) {
synchronized (mChanges) {
for (Long changeId : overrides.enabledChanges()) {
addOverride(changeId, packageName, true);
addOverrideUnsafe(changeId, packageName, true);
}
for (Long changeId : overrides.disabledChanges()) {
addOverride(changeId, packageName, false);
addOverrideUnsafe(changeId, packageName, false);
}
saveOverrides();
invalidateCache();
}
}
@@ -337,8 +366,9 @@ final class CompatConfig {
synchronized (mChanges) {
for (int i = 0; i < mChanges.size(); ++i) {
CompatChange change = mChanges.valueAt(i);
removeOverride(change.getId(), packageName);
removeOverrideUnsafe(change.getId(), packageName);
}
saveOverrides();
invalidateCache();
}
}
@@ -372,8 +402,10 @@ final class CompatConfig {
int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
for (long changeId : changes) {
addOverride(changeId, packageName, true);
addOverrideUnsafe(changeId, packageName, true);
}
saveOverrides();
invalidateCache();
return changes.length;
}
@@ -386,8 +418,10 @@ final class CompatConfig {
int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
for (long changeId : changes) {
addOverride(changeId, packageName, false);
addOverrideUnsafe(changeId, packageName, false);
}
saveOverrides();
invalidateCache();
return changes.length;
}
@@ -494,7 +528,8 @@ final class CompatConfig {
private void readConfig(File configFile) {
try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) {
for (Change change : XmlParser.read(in).getCompatChange()) {
Config config = com.android.server.compat.config.XmlParser.read(in);
for (Change change : config.getCompatChange()) {
Slog.d(TAG, "Adding: " + change.toString());
addChange(new CompatChange(change));
}
@@ -503,6 +538,65 @@ final class CompatConfig {
}
}
void initOverrides(File overridesFile) {
if (!overridesFile.exists()) {
mOverridesFile = overridesFile;
// There have not been any overrides added yet.
return;
}
try (InputStream in = new BufferedInputStream(new FileInputStream(overridesFile))) {
Overrides overrides = com.android.server.compat.overrides.XmlParser.read(in);
for (ChangeOverrides changeOverrides : overrides.getChangeOverrides()) {
long changeId = changeOverrides.getChangeId();
CompatChange compatChange = mChanges.get(changeId);
if (compatChange == null) {
Slog.w(TAG, "Change ID " + changeId + " not found. "
+ "Skipping overrides for it.");
continue;
}
compatChange.loadOverrides(changeOverrides);
}
} catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
Slog.w(TAG, "Error processing " + overridesFile + " " + e.toString());
return;
}
mOverridesFile = overridesFile;
}
/**
* Persist compat framework overrides to /data/misc/appcompat/compat_framework_overrides.xml
*/
void saveOverrides() {
if (mOverridesFile == null) {
return;
}
synchronized (mChanges) {
// Create the file if it doesn't already exist
try {
mOverridesFile.createNewFile();
} catch (IOException e) {
Slog.e(TAG, "Could not create override config file: " + e.toString());
return;
}
try (PrintWriter out = new PrintWriter(mOverridesFile)) {
XmlWriter writer = new XmlWriter(out);
Overrides overrides = new Overrides();
List<ChangeOverrides> changeOverridesList = overrides.getChangeOverrides();
for (int idx = 0; idx < mChanges.size(); ++idx) {
CompatChange c = mChanges.valueAt(idx);
ChangeOverrides changeOverrides = c.saveOverrides();
if (changeOverrides != null) {
changeOverridesList.add(changeOverrides);
}
}
XmlWriter.write(writer, overrides);
} catch (IOException e) {
Slog.e(TAG, e.toString());
}
}
}
IOverrideValidator getOverrideValidator() {
return mOverrideValidator;
}

View File

@@ -8,11 +8,19 @@ xsd_config {
xsd_config {
name: "platform-compat-config",
srcs: ["platform-compat-config.xsd"],
api_dir: "platform-compat-schema",
srcs: ["platform-compat/config/platform-compat-config.xsd"],
api_dir: "platform-compat/config/schema",
package_name: "com.android.server.compat.config",
}
xsd_config {
name: "platform-compat-overrides",
srcs: ["platform-compat/overrides/platform-compat-overrides.xsd"],
api_dir: "platform-compat/overrides/schema",
package_name: "com.android.server.compat.overrides",
gen_writer: true,
}
xsd_config {
name: "display-device-config",

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2019 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.
-->
<!-- This defines the format of the XML file used to store compat config overrides in
~ /data/misc/appcompat/compat_framework_overrides.xml
-->
<xs:schema version="2.0" elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="override-value">
<xs:attribute type="xs:string" name="packageName" use="required" />
<xs:attribute type="xs:boolean" name="enabled" use="required" />
</xs:complexType>
<xs:complexType name="change-overrides">
<xs:attribute type="xs:long" name="changeId" use="required"/>
<xs:element name="validated">
<xs:complexType>
<xs:sequence>
<xs:element name="override-value" type="override-value" maxOccurs="unbounded" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="deferred">
<xs:complexType>
<xs:sequence>
<xs:element name="override-value" type="override-value" maxOccurs="unbounded" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:complexType>
<xs:element name="overrides">
<xs:complexType>
<xs:sequence>
<xs:element name="change-overrides" type="change-overrides" maxOccurs="unbounded" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@@ -0,0 +1,51 @@
// Signature format: 2.0
package com.android.server.compat.overrides {
public class ChangeOverrides {
ctor public ChangeOverrides();
method public long getChangeId();
method public com.android.server.compat.overrides.ChangeOverrides.Deferred getDeferred();
method public com.android.server.compat.overrides.ChangeOverrides.Validated getValidated();
method public void setChangeId(long);
method public void setDeferred(com.android.server.compat.overrides.ChangeOverrides.Deferred);
method public void setValidated(com.android.server.compat.overrides.ChangeOverrides.Validated);
}
public static class ChangeOverrides.Deferred {
ctor public ChangeOverrides.Deferred();
method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue();
}
public static class ChangeOverrides.Validated {
ctor public ChangeOverrides.Validated();
method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue();
}
public class OverrideValue {
ctor public OverrideValue();
method public boolean getEnabled();
method public String getPackageName();
method public void setEnabled(boolean);
method public void setPackageName(String);
}
public class Overrides {
ctor public Overrides();
method public java.util.List<com.android.server.compat.overrides.ChangeOverrides> getChangeOverrides();
}
public class XmlParser {
ctor public XmlParser();
method public static com.android.server.compat.overrides.Overrides read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
}
public class XmlWriter implements java.io.Closeable {
ctor public XmlWriter(java.io.PrintWriter);
method public void close();
method public static void write(com.android.server.compat.overrides.XmlWriter, com.android.server.compat.overrides.Overrides) throws java.io.IOException;
}
}

View File

@@ -0,0 +1 @@
// Signature format: 2.0

View File

@@ -44,6 +44,8 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.UUID;
@RunWith(AndroidJUnit4.class)
@@ -69,6 +71,10 @@ public class CompatConfigTest {
os.close();
}
private String readFile(File file) throws IOException {
return new String(Files.readAllBytes(Paths.get(file.toURI())));
}
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -499,4 +505,86 @@ public class CompatConfigTest {
assertThat(compatConfig.isChangeEnabled(1236L,
ApplicationInfoBuilder.create().withTargetSdk(1).build())).isTrue();
}
@Test
public void testSaveOverrides() throws Exception {
File overridesFile = new File(createTempDir(), "overrides.xml");
CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
.addDisabledChangeWithId(1L)
.addEnableSinceSdkChangeWithId(2, 2L)
.build();
compatConfig.forceNonDebuggableFinalForTest(true);
compatConfig.initOverrides(overridesFile);
when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt()))
.thenReturn(ApplicationInfoBuilder.create()
.withPackageName("foo.bar")
.debuggable()
.build());
when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt()))
.thenThrow(new NameNotFoundException());
compatConfig.addOverride(1L, "foo.bar", true);
compatConfig.addOverride(2L, "bar.baz", false);
assertThat(readFile(overridesFile)).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<overrides>\n"
+ " <change-overrides changeId=\"1\">\n"
+ " <validated>\n"
+ " <override-value packageName=\"foo.bar\" enabled=\"true\">\n"
+ " </override-value>\n"
+ " </validated>\n"
+ " <deferred>\n"
+ " </deferred>\n"
+ " </change-overrides>\n"
+ " <change-overrides changeId=\"2\">\n"
+ " <validated>\n"
+ " </validated>\n"
+ " <deferred>\n"
+ " <override-value packageName=\"bar.baz\" enabled=\"false\">\n"
+ " </override-value>\n"
+ " </deferred>\n"
+ " </change-overrides>\n"
+ "</overrides>\n");
}
@Test
public void testLoadOverrides() throws Exception {
File tempDir = createTempDir();
File overridesFile = new File(tempDir, "overrides.xml");
// Change 1 is enabled for foo.bar (validated)
// Change 2 is disabled for bar.baz (deferred)
String xmlData = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<overrides>"
+ "<change-overrides changeId=\"1\">"
+ "<deferred/>"
+ "<validated>"
+ "<override-value packageName=\"foo.bar\" enabled=\"true\"/>"
+ "</validated>"
+ "</change-overrides>"
+ "<change-overrides changeId=\"2\">"
+ "<deferred>"
+ "<override-value packageName=\"bar.baz\" enabled=\"false\"/>"
+ "</deferred>"
+ "<validated/>"
+ "</change-overrides>"
+ "</overrides>";
writeToFile(tempDir, "overrides.xml", xmlData);
CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
.addDisabledChangeWithId(1L)
.addEnableSinceSdkChangeWithId(2, 2L)
.build();
compatConfig.forceNonDebuggableFinalForTest(true);
compatConfig.initOverrides(overridesFile);
ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
.withPackageName("foo.bar")
.debuggable()
.build();
when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt()))
.thenReturn(applicationInfo);
when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt()))
.thenThrow(new NameNotFoundException());
assertThat(compatConfig.isChangeEnabled(1L, applicationInfo)).isTrue();
assertThat(compatConfig.willChangeBeEnabled(2L, "bar.baz")).isFalse();
}
}