diff options
16 files changed, 377 insertions, 11 deletions
diff --git a/services/core/Android.bp b/services/core/Android.bp index 5e7752af55b5..63d0ff5d48f1 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -91,6 +91,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", diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index 9ba957ef27ae..e3757dfc6a59 100644 --- a/services/core/java/com/android/server/compat/CompatChange.java +++ b/services/core/java/com/android/server/compat/CompatChange.java @@ -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(") diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index 69686a2e4678..6b77b9d4ce39 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -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; } diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp index b7d6424450f3..3690afc1bc41 100644 --- a/services/core/xsd/Android.bp +++ b/services/core/xsd/Android.bp @@ -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", diff --git a/services/core/xsd/platform-compat-schema/OWNERS b/services/core/xsd/platform-compat/OWNERS index f8c3520e9fa8..f8c3520e9fa8 100644 --- a/services/core/xsd/platform-compat-schema/OWNERS +++ b/services/core/xsd/platform-compat/OWNERS diff --git a/services/core/xsd/platform-compat-config.xsd b/services/core/xsd/platform-compat/config/platform-compat-config.xsd index a62e2c385766..a62e2c385766 100644 --- a/services/core/xsd/platform-compat-config.xsd +++ b/services/core/xsd/platform-compat/config/platform-compat-config.xsd diff --git a/services/core/xsd/platform-compat-schema/current.txt b/services/core/xsd/platform-compat/config/schema/current.txt index fb8bbefd8374..fb8bbefd8374 100644 --- a/services/core/xsd/platform-compat-schema/current.txt +++ b/services/core/xsd/platform-compat/config/schema/current.txt diff --git a/services/core/xsd/platform-compat-schema/last_current.txt b/services/core/xsd/platform-compat/config/schema/last_current.txt index e69de29bb2d1..e69de29bb2d1 100644 --- a/services/core/xsd/platform-compat-schema/last_current.txt +++ b/services/core/xsd/platform-compat/config/schema/last_current.txt diff --git a/services/core/xsd/platform-compat-schema/last_removed.txt b/services/core/xsd/platform-compat/config/schema/last_removed.txt index e69de29bb2d1..e69de29bb2d1 100644 --- a/services/core/xsd/platform-compat-schema/last_removed.txt +++ b/services/core/xsd/platform-compat/config/schema/last_removed.txt diff --git a/services/core/xsd/platform-compat-schema/removed.txt b/services/core/xsd/platform-compat/config/schema/removed.txt index d802177e249b..d802177e249b 100644 --- a/services/core/xsd/platform-compat-schema/removed.txt +++ b/services/core/xsd/platform-compat/config/schema/removed.txt diff --git a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd new file mode 100644 index 000000000000..e27e1b8ca89d --- /dev/null +++ b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd @@ -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> diff --git a/services/core/xsd/platform-compat/overrides/schema/current.txt b/services/core/xsd/platform-compat/overrides/schema/current.txt new file mode 100644 index 000000000000..08b82072747b --- /dev/null +++ b/services/core/xsd/platform-compat/overrides/schema/current.txt @@ -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; + } + +} + diff --git a/services/core/xsd/platform-compat/overrides/schema/last_current.txt b/services/core/xsd/platform-compat/overrides/schema/last_current.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/services/core/xsd/platform-compat/overrides/schema/last_current.txt diff --git a/services/core/xsd/platform-compat/overrides/schema/last_removed.txt b/services/core/xsd/platform-compat/overrides/schema/last_removed.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/services/core/xsd/platform-compat/overrides/schema/last_removed.txt diff --git a/services/core/xsd/platform-compat/overrides/schema/removed.txt b/services/core/xsd/platform-compat/overrides/schema/removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/services/core/xsd/platform-compat/overrides/schema/removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java index ac8dc341999a..a53ff9bc7fdc 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -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(); + } } |